def test_set_sorting_fallback(): set_ = set((None, 1)) description = inspect.object_description(set_) if PY3: assert description in ("{1, None}", "{None, 1}") else: assert description in ("set([1, None])", "set([None, 1])")
def test_frozenset_sorting_fallback(): frozenset_ = frozenset((None, 1)) description = inspect.object_description(frozenset_) if PY3: assert description in ("frozenset({1, None})", "frozenset({None, 1})") else: assert description in ("frozenset([1, None])", "frozenset([None, 1])")
def test_set_sorting(): set_ = set("gfedcba") description = inspect.object_description(set_) if PY3: assert description == "{'a', 'b', 'c', 'd', 'e', 'f', 'g'}" else: assert description == "set(['a', 'b', 'c', 'd', 'e', 'f', 'g'])"
def restify(cls: Optional[Type], mode: str = 'fully-qualified-except-typing') -> str: """Convert python class to a reST reference. :param mode: Specify a method how annotations will be stringified. 'fully-qualified-except-typing' Show the module name and qualified name of the annotation except the "typing" module. 'smart' Show the name of the annotation. """ from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading from sphinx.util import inspect # lazy loading if mode == 'smart': modprefix = '~' else: modprefix = '' try: if cls is None or cls is NoneType: return ':py:obj:`None`' elif cls is Ellipsis: return '...' elif isinstance(cls, str): return cls elif ismockmodule(cls): return ':py:class:`%s%s`' % (modprefix, cls.__name__) elif ismock(cls): return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__) elif is_invalid_builtin_class(cls): return ':py:class:`%s%s`' % (modprefix, INVALID_BUILTIN_CLASSES[cls]) elif inspect.isNewType(cls): if sys.version_info > (3, 10): # newtypes have correct module info since Python 3.10+ return ':py:class:`%s%s.%s`' % (modprefix, cls.__module__, cls.__name__) else: return ':py:class:`%s`' % cls.__name__ elif UnionType and isinstance(cls, UnionType): if len(cls.__args__) > 1 and None in cls.__args__: args = ' | '.join(restify(a, mode) for a in cls.__args__ if a) return 'Optional[%s]' % args else: return ' | '.join(restify(a, mode) for a in cls.__args__) elif cls.__module__ in ('__builtin__', 'builtins'): if hasattr(cls, '__args__'): return ':py:class:`%s`\\ [%s]' % ( cls.__name__, ', '.join(restify(arg, mode) for arg in cls.__args__), ) else: return ':py:class:`%s`' % cls.__name__ else: return _restify_py37(cls, mode) except (AttributeError, TypeError): return inspect.object_description(cls)
def test_frozenset_sorting(): frozenset_ = frozenset("gfedcba") description = inspect.object_description(frozenset_) if PY3: assert description == "frozenset({'a', 'b', 'c', 'd', 'e', 'f', 'g'})" else: assert description == "frozenset(['a', 'b', 'c', 'd', 'e', 'f', 'g'])"
def test_dict_customtype(): class CustomType: def __init__(self, value): self._value = value def __repr__(self): return "<CustomType(%r)>" % self._value dictionary = {CustomType(2): 2, CustomType(1): 1} description = inspect.object_description(dictionary) # Type is unsortable, just check that it does not crash assert "<CustomType(2)>: 2" in description
def add_directive_header(self, sig: str): """ Add the directive's header. :param sig: """ sourcename = self.get_sourcename() no_value = self.options.get("no-value", False) no_type = self.options.get("no-type", False) if not self.options.get("annotation", ''): ClassLevelDocumenter.add_directive_header(self, sig) # data descriptors do not have useful values if not no_value and not self._datadescriptor: if "value" in self.options: self.add_line(" :value: " + self.options["value"], sourcename) else: with suppress(ValueError): if self.object is not INSTANCEATTR: objrepr = object_description(self.object) self.add_line(" :value: " + objrepr, sourcename) self.add_line('', sourcename) if not no_type: if "type" in self.options: self.add_line(type_template % self.options["type"], sourcename) else: # obtain type annotation for this attribute the_type = get_variable_type(self) if not the_type.strip(): obj_type = type(self.object) if obj_type is object: return try: the_type = format_annotation(obj_type) except Exception: return line = type_template % the_type self.add_line(line, sourcename) else: super().add_directive_header(sig)
def add_directive_header(self, sig: str): """ Add the directive's header. :param sig: """ sourcename = self.get_sourcename() no_value = self.options.get("no-value", False) no_type = self.options.get("no-type", False) if not self.options.get("annotation", ''): ModuleLevelDocumenter.add_directive_header(self, sig) if not no_value: if "value" in self.options: self.add_line(f" :value: {self.options['value']}", sourcename) else: with suppress(ValueError): if self.object is not UNINITIALIZED_ATTR: objrepr = object_description(self.object) self.add_line(f" :value: {objrepr}", sourcename) self.add_line('', sourcename) if not no_type: if "type" in self.options: the_type = self.options["type"] else: # obtain type annotation for this data the_type = get_variable_type(self) if not the_type.strip(): obj_type = type(self.object) if obj_type is object: return try: the_type = format_annotation(obj_type) except Exception: return line = type_template % the_type self.add_line(line, sourcename) else: super().add_directive_header(sig)
def add_directive_header(self, sig): MatClassLevelDocumenter.add_directive_header(self, sig) if not self.options.annotation: if not self._datadescriptor: try: objrepr = object_description(self.object.default) # display default except ValueError: pass else: self.add_line(' :annotation: = ' + objrepr, '<autodoc>') elif self.options.annotation is SUPPRESS: pass else: self.add_line(' :annotation: %s' % self.options.annotation, '<autodoc>')
def add_directive_header(self, sig): MatClassLevelDocumenter.add_directive_header(self, sig) if not self.options.annotation: if not self._datadescriptor: try: objrepr = object_description(self.object.default) # display default except ValueError: pass else: self.add_line(u' :annotation: = ' + objrepr, '<autodoc>') elif self.options.annotation is SUPPRESS: pass else: self.add_line(u' :annotation: %s' % self.options.annotation, '<autodoc>')
def add_directive_header(self, sig): ModuleLevelDocumenter.add_directive_header(self, sig) if not self.options.annotation: try: objrepr = object_description(self.object) # PATCH: truncate the value if longer than 50 characters if len(objrepr) > 50: objrepr = objrepr[:50] + "..." except ValueError: pass else: self.add_line(u' :annotation: = ' + objrepr, '<autodoc>') elif self.options.annotation is SUPPRESS: pass else: self.add_line(u' :annotation: %s' % self.options.annotation, '<autodoc>')
def add_directive_header(self, sig: str) -> None: # pragma: no cover (<Py37) """ Add the directive header and options to the generated content. """ super().add_directive_header(sig) sourcename = self.get_sourcename() if not self.options.annotation: # obtain annotation for this data try: annotations = get_type_hints(self.parent) except NameError: # Failed to evaluate ForwardRef (maybe TYPE_CHECKING) annotations = safe_getattr(self.parent, "__annotations__", {}) except TypeError: annotations = {} except KeyError: # a broken class found (refs: https://github.com/sphinx-doc/sphinx/issues/8084) annotations = {} except AttributeError: # AttributeError is raised on 3.5.2 (fixed by 3.5.3) annotations = {} if self.objpath[-1] in annotations: objrepr = stringify_typehint(annotations.get(self.objpath[-1])) self.add_line(" :type: " + objrepr, sourcename) else: key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) if self.analyzer and key in self.analyzer.annotations: self.add_line(" :type: " + self.analyzer.annotations[key], sourcename) try: if self.object is UNINITIALIZED_ATTR: pass else: objrepr = object_description(self.object) self.add_line(" :value: " + objrepr, sourcename) except ValueError: pass elif self.options.annotation is SUPPRESS: pass else: self.add_line(" :annotation: %s" % self.options.annotation, sourcename)
def add_directive_header(self, sig): # type: (unicode) -> None ModuleLevelDocumenter.add_directive_header(self, sig) sourcename = self.get_sourcename() if not self.options.annotation: try: objrepr = object_description(self.object) # PATCH: truncate the value if longer than 50 characters if len(objrepr) > 50: objrepr = objrepr[:50] + "..." except ValueError: pass else: self.add_line(u' :annotation: = ' + objrepr, sourcename) elif self.options.annotation is SUPPRESS: pass else: self.add_line(u' :annotation: %s' % self.options.annotation, sourcename)
def add_directive_header(self, sig): auto.ModuleLevelDocumenter.add_directive_header(self, sig) if not self.options.annotation: try: # objrepr = inspect.safe_repr(self.object) objrepr = inspect.object_description(self.object) # PATCH: truncate the value if longer than length_limit characters if len(objrepr) > length_limit: objrepr = objrepr[:length_limit] + "..." except ValueError: pass else: self.add_line(u' :annotation: = ' + objrepr, '<autodoc>') elif self.options.annotation is auto.SUPPRESS: pass else: self.add_line(u' :annotation: %s' % self.options.annotation, '<autodoc>')
def restify(cls: Optional[Type]) -> str: """Convert python class to a reST reference.""" from sphinx.util import inspect # lazy loading try: if cls is None or cls is NoneType: return ':py:obj:`None`' elif cls is Ellipsis: return '...' elif isinstance(cls, str): return cls elif cls in INVALID_BUILTIN_CLASSES: return ':py:class:`%s`' % INVALID_BUILTIN_CLASSES[cls] elif inspect.isNewType(cls): if sys.version_info > (3, 10): # newtypes have correct module info since Python 3.10+ print(cls, type(cls), dir(cls)) return ':py:class:`%s.%s`' % (cls.__module__, cls.__name__) else: return ':py:class:`%s`' % cls.__name__ elif UnionType and isinstance(cls, UnionType): if len(cls.__args__) > 1 and None in cls.__args__: args = ' | '.join(restify(a) for a in cls.__args__ if a) return 'Optional[%s]' % args else: return ' | '.join(restify(a) for a in cls.__args__) elif cls.__module__ in ('__builtin__', 'builtins'): if hasattr(cls, '__args__'): return ':py:class:`%s`\\ [%s]' % ( cls.__name__, ', '.join(restify(arg) for arg in cls.__args__), ) else: return ':py:class:`%s`' % cls.__name__ else: if sys.version_info >= (3, 7): # py37+ return _restify_py37(cls) else: return _restify_py36(cls) except (AttributeError, TypeError): return inspect.object_description(cls)
def test_set_sorting_fallback(): set_ = {None, 1} description = inspect.object_description(set_) assert description in ("{1, None}", "{None, 1}")
def test_dictionary_sorting(): dictionary = {"c": 3, "a": 1, "d": 2, "b": 4} description = inspect.object_description(dictionary) assert description == "{'a': 1, 'b': 4, 'c': 3, 'd': 2}"
def lazy_object_description(object): return object_description(lazy_repr(object))
def get_doc(self, encoding=None, ignore=1): """Override to treat the data value as the docstring. """ from sphinx.util.docstrings import prepare_docstring from sphinx.util.inspect import object_description docstring = object_description(self.object) return [prepare_docstring(docstring, ignore)]
def formatargspec(function, args, varargs=None, varkw=None, defaults=None, kwonlyargs=(), kwonlydefaults={}, annotations={}): # type: (Callable, Tuple[str, ...], str, str, Any, Tuple, Dict, Dict[str, Any]) -> str """Return a string representation of an ``inspect.FullArgSpec`` tuple. An enhanced version of ``inspect.formatargspec()`` that handles typing annotations better. """ warnings.warn( 'formatargspec() is now deprecated. ' 'Please use sphinx.util.inspect.Signature instead.', RemovedInSphinx20Warning) def format_arg_with_annotation(name): # type: (str) -> str if name in annotations: return '%s: %s' % (name, format_annotation(get_annotation(name))) return name def get_annotation(name): # type: (str) -> str value = annotations[name] if isinstance(value, string_types): return introspected_hints.get(name, value) else: return value introspected_hints = ( typing.get_type_hints(function) # type: ignore if typing and hasattr(function, '__code__') else {}) fd = StringIO() fd.write('(') formatted = [] defaults_start = len(args) - len(defaults) if defaults else len(args) for i, arg in enumerate(args): arg_fd = StringIO() if isinstance(arg, list): # support tupled arguments list (only for py2): def foo((x, y)) arg_fd.write('(') arg_fd.write(format_arg_with_annotation(arg[0])) for param in arg[1:]: arg_fd.write(', ') arg_fd.write(format_arg_with_annotation(param)) arg_fd.write(')') else: arg_fd.write(format_arg_with_annotation(arg)) if defaults and i >= defaults_start: arg_fd.write(' = ' if arg in annotations else '=') arg_fd.write(object_description( defaults[i - defaults_start])) # type: ignore formatted.append(arg_fd.getvalue()) if varargs: formatted.append('*' + format_arg_with_annotation(varargs)) if kwonlyargs: if not varargs: formatted.append('*') for kwarg in kwonlyargs: arg_fd = StringIO() arg_fd.write(format_arg_with_annotation(kwarg)) if kwonlydefaults and kwarg in kwonlydefaults: arg_fd.write(' = ' if kwarg in annotations else '=') arg_fd.write(object_description( kwonlydefaults[kwarg])) # type: ignore formatted.append(arg_fd.getvalue()) if varkw: formatted.append('**' + format_arg_with_annotation(varkw)) fd.write(', '.join(formatted)) fd.write(')') if 'return' in annotations: fd.write(' -> ') fd.write(format_annotation(get_annotation('return'))) return fd.getvalue()
def formatargspec(function, args, varargs=None, varkw=None, defaults=None, kwonlyargs=(), kwonlydefaults={}, annotations={}): # type: (Callable, Tuple[str, ...], str, str, Any, Tuple, Dict, Dict[str, Any]) -> str """Return a string representation of an ``inspect.FullArgSpec`` tuple. An enhanced version of ``inspect.formatargspec()`` that handles typing annotations better. """ warnings.warn('formatargspec() is now deprecated. ' 'Please use sphinx.util.inspect.Signature instead.', RemovedInSphinx20Warning) def format_arg_with_annotation(name): # type: (str) -> str if name in annotations: return '%s: %s' % (name, format_annotation(get_annotation(name))) return name def get_annotation(name): # type: (str) -> str value = annotations[name] if isinstance(value, string_types): return introspected_hints.get(name, value) else: return value introspected_hints = (typing.get_type_hints(function) # type: ignore if typing and hasattr(function, '__code__') else {}) fd = StringIO() fd.write('(') formatted = [] defaults_start = len(args) - len(defaults) if defaults else len(args) for i, arg in enumerate(args): arg_fd = StringIO() if isinstance(arg, list): # support tupled arguments list (only for py2): def foo((x, y)) arg_fd.write('(') arg_fd.write(format_arg_with_annotation(arg[0])) for param in arg[1:]: arg_fd.write(', ') arg_fd.write(format_arg_with_annotation(param)) arg_fd.write(')') else: arg_fd.write(format_arg_with_annotation(arg)) if defaults and i >= defaults_start: arg_fd.write(' = ' if arg in annotations else '=') arg_fd.write(object_description(defaults[i - defaults_start])) # type: ignore formatted.append(arg_fd.getvalue()) if varargs: formatted.append('*' + format_arg_with_annotation(varargs)) if kwonlyargs: if not varargs: formatted.append('*') for kwarg in kwonlyargs: arg_fd = StringIO() arg_fd.write(format_arg_with_annotation(kwarg)) if kwonlydefaults and kwarg in kwonlydefaults: arg_fd.write(' = ' if kwarg in annotations else '=') arg_fd.write(object_description(kwonlydefaults[kwarg])) # type: ignore formatted.append(arg_fd.getvalue()) if varkw: formatted.append('**' + format_arg_with_annotation(varkw)) fd.write(', '.join(formatted)) fd.write(')') if 'return' in annotations: fd.write(' -> ') fd.write(format_annotation(get_annotation('return'))) return fd.getvalue()
def process_docstring(app: Sphinx, what: str, name: str, obj: Any, options: Any, lines: List[str]) -> None: """Process docstring after Sphinx. See `autodoc-process-docstring <https://www.sphinx-doc.org/en/master/ usage/extensions/autodoc.html#event-autodoc-process-docstring>`_ """ # original_obj = obj if isinstance(obj, property): obj = obj.fget if not callable(obj): return if inspect.isclass(obj): obj = getattr(obj, '__init__', getattr(obj, '__new__', None)) # obj = getattr(obj, '__init__') obj = inspect.unwrap(obj) rm_first_arg = what in ['method', 'property', 'class' ] and not isstaticmethod(obj) first_argname = next(iter(Signature( unwrap_all(obj)).parameters)) if rm_first_arg else None if first_argname and first_argname.endswith('_'): first_argname = '{}\\_'.format(first_argname[:-1]) default_args = get_default_args(obj) for argname, (default, is_keyword_only) in default_args.items(): # what if default has \ default = ':code:`{}`'.format(object_description(default)) # TODO # should be arguments strip = app.config.docstring_default_arg_strip_matching docstring_default_arg_parenthesis = False # Search for parameters # TODO Test case: empty param searchfor = [ ':{} {}:'.format(field, argname) for field in param_fields ] param_found, param_start, param_end, param_matched, param_text = ( match_field(lines, searchfor, include_blank=app.config. docstring_default_arg_after_directives)) if param_found: if app.config.docstring_default_arg_substitution not in ' '.join( param_text): # Extracts all the flags for head, tail in app.config.docstring_default_arg_flags: tail_found, is_end, t_start = rfind_substring_in_paragraph( param_text, tail, strip, app.config. docstring_default_arg_flags_multiline_matching)[:3] if tail_found and is_end: head_found, _, h_start, h_end = ( rfind_substring_in_paragraph( param_text, head, strip, app.config. docstring_default_arg_flags_multiline_matching) ) if head_found: # what if default has \ if h_end[0] == t_start[0]: default = param_text[ h_end[0]][h_end[1]:t_start[1]] else: default = ' '.join( [param_text[h_end[0]][h_end[1]:]] + param_text[h_end[0] + 1:t_start[0]] + [param_text[t_start[0]][:t_start[1]]]) if strip: default = default.strip() lines[param_start + h_start[0]] = ( lines[param_start + h_start[0]][:len(param_matched) + 1 + h_start[1]]) del lines[param_start + h_start[0] + 1:param_end] param_end = param_start + h_start[0] + 1 break if strip: lines[param_end - 1] = rstrip_min(lines[param_end - 1], len(param_matched) + 1) if docstring_default_arg_parenthesis: raise NotImplementedError else: # To prevent insertion into Note directives or so lines.insert( param_end, ' ' * len(param_matched) + ' {} {}'.format( app.config.docstring_default_arg_substitution, default)) elif app.config.always_document_default_args and ( not rm_first_arg or argname != first_argname): # Since ``kwargs`` (no default args) might come # after ``argname``, it will not be in ``default_args``. # Need to generate the full args list. next_start, next_type = find_next_arg(lines, get_args(obj), argname) if docstring_default_arg_parenthesis: raise NotImplementedError else: lines.insert( next_start, ':{} {}: {} {}'.format( 'keyword' if is_keyword_only and (next_type is None or next_type in kw_fields) else 'param', argname, app.config.docstring_default_arg_substitution, default)) # Search for type type_found, type_start, type_end, type_matched, type_text = ( match_field( lines, [':{} {}:'.format(field, argname) for field in type_fields], include_blank=False)) if type_found: type_text = ' '.join(type_text) if strip: type_text = type_text.rstrip() lines[type_end - 1] = rstrip_min(lines[type_end - 1], len(type_matched) + 1) if not type_text.endswith('optional'): if not type_text.strip(): lines[type_start] = '{} optional'.format(type_matched) elif '`' in type_text: # TODO check \` escape lines[type_end - 1] += ', *optional*' else: # Do not insert newline to prevent whitespace before ',' lines[type_end - 1] += ', optional' elif param_found or app.config.always_document_default_args and ( not rm_first_arg or argname != first_argname): # insert type before param param_start, param_type = find_curr_arg(lines, get_args(obj), argname) assert any(lines[param_start].startswith(search) for search in searchfor) lines.insert( param_start, ':{}type {}: optional'.format( 'kw' if param_type in kw_fields else '', argname))
def test_frozenset_sorting_fallback(): frozenset_ = frozenset((None, 1)) description = inspect.object_description(frozenset_) assert description in ("frozenset({1, None})", "frozenset({None, 1})")
def test_frozenset_sorting(): frozenset_ = frozenset("gfedcba") description = inspect.object_description(frozenset_) assert description == "frozenset({'a', 'b', 'c', 'd', 'e', 'f', 'g'})"
def test_set_sorting_fallback(): set_ = set((None, 1)) description = inspect.object_description(set_) assert description in ("{1, None}", "{None, 1}")
def test_object_description_enum(): class MyEnum(enum.Enum): FOO = 1 BAR = 2 assert inspect.object_description(MyEnum.FOO) == "MyEnum.FOO"