diff --git a/docfx_yaml/extension.py b/docfx_yaml/extension.py index 9d1741e..d12b080 100644 --- a/docfx_yaml/extension.py +++ b/docfx_yaml/extension.py @@ -943,56 +943,77 @@ def _update_friendly_package_name(path): if _type in [METHOD, FUNCTION, CLASS]: argspec = inspect.getfullargspec(obj) # noqa type_map = {} - if argspec.annotations: - for annotation in argspec.annotations: - if annotation == "return": + + annotations = getattr(argspec, 'annotations', []) + for annotation in argspec.annotations: + if annotation == "return": + continue + try: + type_map[annotation] = _extract_type_name( + argspec.annotations[annotation]) + except AttributeError: + print(f"Could not parse argument information for {annotation}.") + continue + + # Retrieve arguments from various sources like `argspec.args` and + # `argspec.kwonlyargs` for positional/keyword arguments. + original_args = argspec.args + # Stores default information for kwonly arguments. Unlike + # inspect.defaults, which is a tuple of default values, kw defaults + # is a dict[name, defaultvalue]. + kw_defaults = {} + if argspec.kwonlyargs: + original_args.extend(argspec.kwonlyargs) + if argspec.kwonlydefaults: + kw_defaults.update(argspec.kwonlydefaults) + arg_count += len(original_args) + + # Retrieve the default values and convert to a list. + defaults = list(getattr(argspec, 'defaults', [])) + # Counter used to extract default values. + default_index = -len(argspec.args) + len(defaults) + for arg in original_args: + # Ignore adding in entry for "self" or if docstring cannot be + # formed for current arg, such as docstring is missing or type + # annotation is not given. + if arg == 'cls' or arg not in type_map: + default_index += 1 + continue + + arg_map = { + 'id': arg, + 'var_type': type_map[arg], + } + + if arg in argspec.args: + # default index will be between 0 and len(defaults) if we + # processed args without defaults, and now only have + # args with default values to assign. + if default_index < 0 or default_index >= len(defaults): continue - try: - type_map[annotation] = _extract_type_name( - argspec.annotations[annotation]) - except AttributeError: - print(f"Could not parse argument information for {annotation}.") + # Only add defaultValue when str(default) doesn't + # contain object address string, for example: + # (object at 0x) or at 0x7fed4d57b5e0, + # otherwise inspect.getargspec method will return wrong + # defaults which contain object address for some, + # like sys.stdout. + default_string = str(defaults[default_index]) + if 'at 0x' in default_string: continue + arg_map['defaultValue'] = default_string + if arg in kw_defaults: + # Cast to string, otherwise different types may be stored as + # value instead. + arg_map['defaultValue'] = str(kw_defaults[arg]) - # Add up the number of arguments. `argspec.args` contains a list of - # all the arguments from the function. - arg_count += len(argspec.args) - for arg in argspec.args: - arg_map = {} - # Ignore adding in entry for "self" - if arg != 'cls': - arg_map['id'] = arg - if arg in type_map: - arg_map['var_type'] = type_map[arg] - args.append(arg_map) + default_index += 1 + args.append(arg_map) if argspec.varargs: args.append({'id': argspec.varargs}) if argspec.varkw: args.append({'id': argspec.varkw}) - if argspec.defaults: - # Attempt to add default values to arguments. - try: - for count, default in enumerate(argspec.defaults): - # Find the first index which default arguments start at. - # Every argument after this offset_count all have default values. - offset_count = len(argspec.defaults) - # Find the index of the current default value argument - index = len(args) + count - offset_count - - # Only add defaultValue when str(default) doesn't - # contain object address string, for example: - # (object at 0x) or at 0x7fed4d57b5e0, - # otherwise inspect.getargspec method will return wrong - # defaults which contain object address for some, - # like sys.stdout. - default_string = str(default) - if 'at 0x' not in default_string: - args[index]['defaultValue'] = default_string - # If we cannot find the argument, it is missing a type and was taken out intentionally. - except IndexError: - pass try: if len(lines) == 0: lines = inspect.getdoc(obj)