Source code for sphinx.ext.autodoc

"""
    sphinx.ext.autodoc
    ~~~~~~~~~~~~~~~~~~

    Automatically insert docstrings for functions, classes or whole modules into
    the doctree, thus avoiding duplication between docstrings and documentation
    for those who like elaborate docstrings.

    :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""

import importlib
import re
import warnings
from inspect import Parameter, Signature
from types import ModuleType
from typing import (
    Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
)
from typing import TYPE_CHECKING

from docutils.statemachine import StringList

import sphinx
from sphinx.application import Sphinx
from sphinx.config import Config, ENUM
from sphinx.deprecation import RemovedInSphinx50Warning
from sphinx.environment import BuildEnvironment
from sphinx.ext.autodoc.importer import import_object, get_module_members, get_object_members
from sphinx.ext.autodoc.mock import mock
from sphinx.locale import _, __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import inspect
from sphinx.util import logging
from sphinx.util.docstrings import extract_metadata, prepare_docstring
from sphinx.util.inspect import getdoc, object_description, safe_getattr, stringify_signature
from sphinx.util.typing import stringify as stringify_typehint

if TYPE_CHECKING:
    from sphinx.ext.autodoc.directive import DocumenterBridge


logger = logging.getLogger(__name__)


# This type isn't exposed directly in any modules, but can be found
# here in most Python versions
MethodDescriptorType = type(type.__subclasses__)


#: extended signature RE: with explicit module name separated by ::
py_ext_sig_re = re.compile(
    r'''^ ([\w.]+::)?            # explicit module name
          ([\w.]+\.)?            # module and/or class name(s)
          (\w+)  \s*             # thing name
          (?: \((.*)\)           # optional: arguments
           (?:\s* -> \s* (.*))?  #           return annotation
          )? $                   # and nothing more
          ''', re.VERBOSE)


def identity(x: Any) -> Any:
    return x


ALL = object()
UNINITIALIZED_ATTR = object()
INSTANCEATTR = object()
SLOTSATTR = object()


def members_option(arg: Any) -> Union[object, List[str]]:
    """Used to convert the :members: option to auto directives."""
    if arg is None or arg is True:
        return ALL
    return [x.strip() for x in arg.split(',') if x.strip()]


def members_set_option(arg: Any) -> Union[object, Set[str]]:
    """Used to convert the :members: option to auto directives."""
    if arg is None:
        return ALL
    return {x.strip() for x in arg.split(',') if x.strip()}


def inherited_members_option(arg: Any) -> Union[object, Set[str]]:
    """Used to convert the :members: option to auto directives."""
    if arg is None:
        return 'object'
    else:
        return arg


def member_order_option(arg: Any) -> Optional[str]:
    """Used to convert the :members: option to auto directives."""
    if arg is None:
        return None
    elif arg in ('alphabetical', 'bysource', 'groupwise'):
        return arg
    else:
        raise ValueError(__('invalid value for member-order option: %s') % arg)


SUPPRESS = object()


def annotation_option(arg: Any) -> Any:
    if arg is None:
        # suppress showing the representation of the object
        return SUPPRESS
    else:
        return arg


def bool_option(arg: Any) -> bool:
    """Used to convert flag options to auto directives.  (Instead of
    directives.flag(), which returns None).
    """
    return True


def merge_special_members_option(options: Dict) -> None:
    """Merge :special-members: option to :members: option."""
    if 'special-members' in options and options['special-members'] is not ALL:
        if options.get('members') is ALL:
            pass
        elif options.get('members'):
            for member in options['special-members']:
                if member not in options['members']:
                    options['members'].append(member)
        else:
            options['members'] = options['special-members']


# Some useful event listener factories for autodoc-process-docstring.

[docs]def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable: """Return a listener that removes the first *pre* and last *post* lines of every docstring. If *what* is a sequence of strings, only docstrings of a type in *what* will be processed. Use like this (e.g. in the ``setup()`` function of :file:`conf.py`):: from sphinx.ext.autodoc import cut_lines app.connect('autodoc-process-docstring', cut_lines(4, what=['module'])) This can (and should) be used in place of :confval:`automodule_skip_lines`. """ def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str] ) -> None: if what and what_ not in what: return del lines[:pre] if post: # remove one trailing blank line. if lines and not lines[-1]: lines.pop(-1) del lines[-post:] # make sure there is a blank line at the end if lines and lines[-1]: lines.append('') return process
[docs]def between(marker: str, what: Sequence[str] = None, keepempty: bool = False, exclude: bool = False) -> Callable: """Return a listener that either keeps, or if *exclude* is True excludes, lines between lines that match the *marker* regular expression. If no line matches, the resulting docstring would be empty, so no change will be made unless *keepempty* is true. If *what* is a sequence of strings, only docstrings of a type in *what* will be processed. """ marker_re = re.compile(marker) def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str] ) -> None: if what and what_ not in what: return deleted = 0 delete = not exclude orig_lines = lines[:] for i, line in enumerate(orig_lines): if delete: lines.pop(i - deleted) deleted += 1 if marker_re.match(line): delete = not delete if delete: lines.pop(i - deleted) deleted += 1 if not lines and not keepempty: lines[:] = orig_lines # make sure there is a blank line at the end if lines and lines[-1]: lines.append('') return process
# This class is used only in ``sphinx.ext.autodoc.directive``, # But we define this class here to keep compatibility (see #4538) class Options(dict): """A dict/attribute hybrid that returns None on nonexisting keys.""" def __getattr__(self, name: str) -> Any: try: return self[name.replace('_', '-')] except KeyError: return None class Documenter: """ A Documenter knows how to autodocument a single object type. When registered with the AutoDirective, it will be used to document objects of that type when needed by autodoc. Its *objtype* attribute selects what auto directive it is assigned to (the directive name is 'auto' + objtype), and what directive it generates by default, though that can be overridden by an attribute called *directivetype*. A Documenter has an *option_spec* that works like a docutils directive's; in fact, it will be used to parse an auto directive's options that matches the documenter. """ #: name by which the directive is called (auto...) and the default #: generated directive name objtype = 'object' #: indentation by which to indent the directive content content_indent = ' ' #: priority if multiple documenters return True from can_document_member priority = 0 #: order if autodoc_member_order is set to 'groupwise' member_order = 0 #: true if the generated content may contain titles titles_allowed = False option_spec = {'noindex': bool_option} # type: Dict[str, Callable] def get_attr(self, obj: Any, name: str, *defargs: Any) -> Any: """getattr() override for types such as Zope interfaces.""" return autodoc_attrgetter(self.env.app, obj, name, *defargs) @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: """Called to see if a member can be documented by this documenter.""" raise NotImplementedError('must be implemented in subclasses') def __init__(self, directive: "DocumenterBridge", name: str, indent: str = '') -> None: self.directive = directive self.env = directive.env # type: BuildEnvironment self.options = directive.genopt self.name = name self.indent = indent # the module and object path within the module, and the fully # qualified name (all set after resolve_name succeeds) self.modname = None # type: str self.module = None # type: ModuleType self.objpath = None # type: List[str] self.fullname = None # type: str # extra signature items (arguments and return annotation, # also set after resolve_name succeeds) self.args = None # type: str self.retann = None # type: str # the object to document (set after import_object succeeds) self.object = None # type: Any self.object_name = None # type: str # the parent/owner of the object to document self.parent = None # type: Any # the module analyzer to get at attribute docs, or None self.analyzer = None # type: ModuleAnalyzer @property def documenters(self) -> Dict[str, Type["Documenter"]]: """Returns registered Documenter classes""" return self.env.app.registry.documenters def add_line(self, line: str, source: str, *lineno: int) -> None: """Append one line of generated reST to the output.""" if line.strip(): # not a blank line self.directive.result.append(self.indent + line, source, *lineno) else: self.directive.result.append('', source, *lineno) def resolve_name(self, modname: str, parents: Any, path: str, base: Any ) -> Tuple[str, List[str]]: """Resolve the module and name of the object to document given by the arguments and the current module/class. Must return a pair of the module name and a chain of attributes; for example, it would return ``('zipfile', ['ZipFile', 'open'])`` for the ``zipfile.ZipFile.open`` method. """ raise NotImplementedError('must be implemented in subclasses') def parse_name(self) -> bool: """Determine what module to import and what attribute to document. Returns True and sets *self.modname*, *self.objpath*, *self.fullname*, *self.args* and *self.retann* if parsing and resolving was successful. """ # first, parse the definition -- auto directives for classes and # functions can contain a signature which is then used instead of # an autogenerated one try: explicit_modname, path, base, args, retann = \ py_ext_sig_re.match(self.name).groups() except AttributeError: logger.warning(__('invalid signature for auto%s (%r)') % (self.objtype, self.name), type='autodoc') return False # support explicit module and class name separation via :: if explicit_modname is not None: modname = explicit_modname[:-2] parents = path.rstrip('.').split('.') if path else [] else: modname = None parents = [] with mock(self.env.config.autodoc_mock_imports): self.modname, self.objpath = self.resolve_name(modname, parents, path, base) if not self.modname: return False self.args = args self.retann = retann self.fullname = (self.modname or '') + \ ('.' + '.'.join(self.objpath) if self.objpath else '') return True def import_object(self) -> bool: """Import the object given by *self.modname* and *self.objpath* and set it as *self.object*. Returns True if successful, False if an error occurred. """ with mock(self.env.config.autodoc_mock_imports): try: ret = import_object(self.modname, self.objpath, self.objtype, attrgetter=self.get_attr, warningiserror=self.env.config.autodoc_warningiserror) self.module, self.parent, self.object_name, self.object = ret return True except ImportError as exc: logger.warning(exc.args[0], type='autodoc', subtype='import_object') self.env.note_reread() return False def get_real_modname(self) -> str: """Get the real module name of an object to document. It can differ from the name of the module through which the object was imported. """ return self.get_attr(self.object, '__module__', None) or self.modname def check_module(self) -> bool: """Check if *self.object* is really defined in the module given by *self.modname*. """ if self.options.imported_members: return True subject = inspect.unpartial(self.object) modname = self.get_attr(subject, '__module__', None) if modname and modname != self.modname: return False return True def format_args(self, **kwargs: Any) -> str: """Format the argument signature of *self.object*. Should return None if the object does not have a signature. """ return None def format_name(self) -> str: """Format the name of *self.object*. This normally should be something that can be parsed by the generated directive, but doesn't need to be (Sphinx will display it unparsed then). """ # normally the name doesn't contain the module (except for module # directives of course) return '.'.join(self.objpath) or self.modname def _call_format_args(self, **kwargs: Any) -> str: if kwargs: try: return self.format_args(**kwargs) except TypeError: # avoid chaining exceptions, by putting nothing here pass # retry without arguments for old documenters return self.format_args() def format_signature(self, **kwargs: Any) -> str: """Format the signature (arguments and return annotation) of the object. Let the user process it via the ``autodoc-process-signature`` event. """ if self.args is not None: # signature given explicitly args = "(%s)" % self.args retann = self.retann else: # try to introspect the signature try: retann = None args = self._call_format_args(**kwargs) if args: matched = re.match(r'^(\(.*\))\s+->\s+(.*)$', args) if matched: args = matched.group(1) retann = matched.group(2) except Exception as exc: logger.warning(__('error while formatting arguments for %s: %s'), self.fullname, exc, type='autodoc') args = None result = self.env.events.emit_firstresult('autodoc-process-signature', self.objtype, self.fullname, self.object, self.options, args, retann) if result: args, retann = result if args is not None: return args + ((' -> %s' % retann) if retann else '') else: return '' def add_directive_header(self, sig: str) -> None: """Add the directive header and options to the generated content.""" domain = getattr(self, 'domain', 'py') directive = getattr(self, 'directivetype', self.objtype) name = self.format_name() sourcename = self.get_sourcename() # one signature per line, indented by column prefix = '.. %s:%s:: ' % (domain, directive) for i, sig_line in enumerate(sig.split("\n")): self.add_line('%s%s%s' % (prefix, name, sig_line), sourcename) if i == 0: prefix = " " * len(prefix) if self.options.noindex: self.add_line(' :noindex:', sourcename) if self.objpath: # Be explicit about the module, this is necessary since .. class:: # etc. don't support a prepended module name self.add_line(' :module: %s' % self.modname, sourcename) def get_doc(self, ignore: int = None) -> List[List[str]]: """Decode and return lines of the docstring(s) for the object.""" if ignore is not None: warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, RemovedInSphinx50Warning, stacklevel=2) docstring = getdoc(self.object, self.get_attr, self.env.config.autodoc_inherit_docstrings, self.parent, self.object_name) if docstring: tab_width = self.directive.state.document.settings.tab_width return [prepare_docstring(docstring, ignore, tab_width)] return [] def process_doc(self, docstrings: List[List[str]]) -> Iterator[str]: """Let the user process the docstrings before adding them.""" for docstringlines in docstrings: if self.env.app: # let extensions preprocess docstrings self.env.app.emit('autodoc-process-docstring', self.objtype, self.fullname, self.object, self.options, docstringlines) yield from docstringlines def get_sourcename(self) -> str: if self.analyzer: return '%s:docstring of %s' % (self.analyzer.srcname, self.fullname) return 'docstring of %s' % self.fullname def add_content(self, more_content: Any, no_docstring: bool = False) -> None: """Add content from docstrings, attribute documentation and user.""" # set sourcename and add content from attribute documentation sourcename = self.get_sourcename() if self.analyzer: attr_docs = self.analyzer.find_attr_docs() if self.objpath: key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) if key in attr_docs: no_docstring = True # make a copy of docstring for attributes to avoid cache # the change of autodoc-process-docstring event. docstrings = [list(attr_docs[key])] for i, line in enumerate(self.process_doc(docstrings)): self.add_line(line, sourcename, i) # add content from docstrings if not no_docstring: docstrings = self.get_doc() if not docstrings: # append at least a dummy docstring, so that the event # autodoc-process-docstring is fired and can add some # content if desired docstrings.append([]) for i, line in enumerate(self.process_doc(docstrings)): self.add_line(line, sourcename, i) # add additional content (e.g. from document), if present if more_content: for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]: """Return `(members_check_module, members)` where `members` is a list of `(membername, member)` pairs of the members of *self.object*. If *want_all* is True, return all members. Else, only return those members given by *self.options.members* (which may also be none). """ members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer) if not want_all: if not self.options.members: return False, [] # specific members given selected = [] for name in self.options.members: if name in members: selected.append((name, members[name].value)) else: logger.warning(__('missing attribute %s in object %s') % (name, self.fullname), type='autodoc') return False, selected elif self.options.inherited_members: return False, [(m.name, m.value) for m in members.values()] else: return False, [(m.name, m.value) for m in members.values() if m.directly_defined] def filter_members(self, members: List[Tuple[str, Any]], want_all: bool ) -> List[Tuple[str, Any, bool]]: """Filter the given member list. Members are skipped if - they are private (except if given explicitly or the private-members option is set) - they are special methods (except if given explicitly or the special-members option is set) - they are undocumented (except if the undoc-members option is set) The user can override the skipping decision by connecting to the ``autodoc-skip-member`` event. """ def is_filtered_inherited_member(name: str) -> bool: if inspect.isclass(self.object): for cls in self.object.__mro__: if cls.__name__ == self.options.inherited_members and cls != self.object: # given member is a member of specified *super class* return True elif name in cls.__dict__: return False return False ret = [] # search for members in source code too namespace = '.'.join(self.objpath) # will be empty for modules if self.analyzer: attr_docs = self.analyzer.find_attr_docs() else: attr_docs = {} # process members and determine which to skip for (membername, member) in members: # if isattr is True, the member is documented as an attribute if member is INSTANCEATTR: isattr = True else: isattr = False doc = getdoc(member, self.get_attr, self.env.config.autodoc_inherit_docstrings, self.parent, self.object_name) if not isinstance(doc, str): # Ignore non-string __doc__ doc = None # if the member __doc__ is the same as self's __doc__, it's just # inherited and therefore not the member's doc cls = self.get_attr(member, '__class__', None) if cls: cls_doc = self.get_attr(cls, '__doc__', None) if cls_doc == doc: doc = None has_doc = bool(doc) metadata = extract_metadata(doc) if 'private' in metadata: # consider a member private if docstring has "private" metadata isprivate = True elif 'public' in metadata: # consider a member public if docstring has "public" metadata isprivate = False else: isprivate = membername.startswith('_') keep = False if safe_getattr(member, '__sphinx_mock__', False): # mocked module or object pass elif want_all and membername.startswith('__') and \ membername.endswith('__') and len(membername) > 4: # special __methods__ if self.options.special_members is ALL: if membername == '__doc__': keep = False elif is_filtered_inherited_member(membername): keep = False else: keep = has_doc or self.options.undoc_members elif self.options.special_members: if membername in self.options.special_members: keep = has_doc or self.options.undoc_members elif (namespace, membername) in attr_docs: if want_all and isprivate: # ignore members whose name starts with _ by default keep = self.options.private_members else: # keep documented attributes keep = True isattr = True elif want_all and isprivate: # ignore members whose name starts with _ by default keep = self.options.private_members and \ (has_doc or self.options.undoc_members) else: if self.options.members is ALL and is_filtered_inherited_member(membername): keep = False else: # ignore undocumented members if :undoc-members: is not given keep = has_doc or self.options.undoc_members # give the user a chance to decide whether this member # should be skipped if self.env.app: # let extensions preprocess docstrings try: skip_user = self.env.app.emit_firstresult( 'autodoc-skip-member', self.objtype, membername, member, not keep, self.options) if skip_user is not None: keep = not skip_user except Exception as exc: logger.warning(__('autodoc: failed to determine %r to be documented, ' 'the following exception was raised:\n%s'), member, exc, type='autodoc') keep = False if keep: ret.append((membername, member, isattr)) return ret def document_members(self, all_members: bool = False) -> None: """Generate reST for member documentation. If *all_members* is True, do all members, else those given by *self.options.members*. """ # set current namespace for finding members self.env.temp_data['autodoc:module'] = self.modname if self.objpath: self.env.temp_data['autodoc:class'] = self.objpath[0] want_all = all_members or self.options.inherited_members or \ self.options.members is ALL # find out which members are documentable members_check_module, members = self.get_object_members(want_all) # remove members given by exclude-members if self.options.exclude_members: members = [ (membername, member) for (membername, member) in members if ( self.options.exclude_members is ALL or membername not in self.options.exclude_members ) ] # document non-skipped members memberdocumenters = [] # type: List[Tuple[Documenter, bool]] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] if not classes: # don't know how to document this member continue # prefer the documenter with the highest priority classes.sort(key=lambda cls: cls.priority) # give explicitly separated module name, so that members # of inner classes can be documented full_mname = self.modname + '::' + \ '.'.join(self.objpath + [mname]) documenter = classes[-1](self.directive, full_mname, self.indent) memberdocumenters.append((documenter, isattr)) member_order = self.options.member_order or self.env.config.autodoc_member_order memberdocumenters = self.sort_members(memberdocumenters, member_order) for documenter, isattr in memberdocumenters: documenter.generate( all_members=True, real_modname=self.real_modname, check_module=members_check_module and not isattr) # reset current objects self.env.temp_data['autodoc:module'] = None self.env.temp_data['autodoc:class'] = None def sort_members(self, documenters: List[Tuple["Documenter", bool]], order: str) -> List[Tuple["Documenter", bool]]: """Sort the given member list.""" if order == 'groupwise': # sort by group; alphabetically within groups documenters.sort(key=lambda e: (e[0].member_order, e[0].name)) elif order == 'bysource': if self.analyzer: # sort by source order, by virtue of the module analyzer tagorder = self.analyzer.tagorder def keyfunc(entry: Tuple[Documenter, bool]) -> int: fullname = entry[0].name.split('::')[1] return tagorder.get(fullname, len(tagorder)) documenters.sort(key=keyfunc) else: # Assume that member discovery order matches source order. # This is a reasonable assumption in Python 3.6 and up, where # module.__dict__ is insertion-ordered. pass else: # alphabetical documenters.sort(key=lambda e: e[0].name) return documenters def generate(self, more_content: Any = None, real_modname: str = None, check_module: bool = False, all_members: bool = False) -> None: """Generate reST for the object given by *self.name*, and possibly for its members. If *more_content* is given, include that content. If *real_modname* is given, use that module name to find attribute docs. If *check_module* is True, only generate if the object is defined in the module name it is imported from. If *all_members* is True, document all members. """ if not self.parse_name(): # need a module to import logger.warning( __('don\'t know which module to import for autodocumenting ' '%r (try placing a "module" or "currentmodule" directive ' 'in the document, or giving an explicit module name)') % self.name, type='autodoc') return # now, import the module and get object to document if not self.import_object(): return # If there is no real module defined, figure out which to use. # The real module is used in the module analyzer to look up the module # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. guess_modname = self.get_real_modname() self.real_modname = real_modname or guess_modname # type: str # try to also get a source code analyzer for attribute docs try: self.analyzer = ModuleAnalyzer.for_module(self.real_modname) # parse right now, to get PycodeErrors on parsing (results will # be cached anyway) self.analyzer.find_attr_docs() except PycodeError as exc: logger.debug('[autodoc] module analyzer failed: %s', exc) # no source file -- e.g. for builtin and C modules self.analyzer = None # at least add the module.__file__ as a dependency if hasattr(self.module, '__file__') and self.module.__file__: self.directive.filename_set.add(self.module.__file__) else: self.directive.filename_set.add(self.analyzer.srcname) if self.real_modname != guess_modname: # Add module to dependency list if target object is defined in other module. try: analyzer = ModuleAnalyzer.for_module(guess_modname) self.directive.filename_set.add(analyzer.srcname) except PycodeError: pass # check __module__ of object (for members not given explicitly) if check_module: if not self.check_module(): return sourcename = self.get_sourcename() # make sure that the result starts with an empty line. This is # necessary for some situations where another directive preprocesses # reST and no starting newline is present self.add_line('', sourcename) # format the object's signature, if any try: sig = self.format_signature() except Exception as exc: logger.warning(__('error while formatting signature for %s: %s'), self.fullname, exc, type='autodoc') return # generate the directive header and options, if applicable self.add_directive_header(sig) self.add_line('', sourcename) # e.g. the module directive doesn't have content self.indent += self.content_indent # add all content (from docstrings, attribute docs etc.) self.add_content(more_content) # document members, if possible self.document_members(all_members) class ModuleDocumenter(Documenter): """ Specialized Documenter subclass for modules. """ objtype = 'module' content_indent = '' titles_allowed = True option_spec = { 'members': members_option, 'undoc-members': bool_option, 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, 'platform': identity, 'deprecated': bool_option, 'member-order': member_order_option, 'exclude-members': members_set_option, 'private-members': bool_option, 'special-members': members_option, 'imported-members': bool_option, 'ignore-module-all': bool_option } # type: Dict[str, Callable] def __init__(self, *args: Any) -> None: super().__init__(*args) merge_special_members_option(self.options) self.__all__ = None @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: # don't document submodules automatically return False def resolve_name(self, modname: str, parents: Any, path: str, base: Any ) -> Tuple[str, List[str]]: if modname is not None: logger.warning(__('"::" in automodule name doesn\'t make sense'), type='autodoc') return (path or '') + base, [] def parse_name(self) -> bool: ret = super().parse_name() if self.args or self.retann: logger.warning(__('signature arguments or return annotation ' 'given for automodule %s') % self.fullname, type='autodoc') return ret def import_object(self) -> Any: def is_valid_module_all(__all__: Any) -> bool: """Check the given *__all__* is valid for a module.""" if (isinstance(__all__, (list, tuple)) and all(isinstance(e, str) for e in __all__)): return True else: return False ret = super().import_object() if not self.options.ignore_module_all: __all__ = getattr(self.object, '__all__', None) if is_valid_module_all(__all__): # valid __all__ found. copy it to self.__all__ self.__all__ = __all__ elif __all__: # invalid __all__ found. logger.warning(__('__all__ should be a list of strings, not %r ' '(in module %s) -- ignoring __all__') % (__all__, self.fullname), type='autodoc') return ret def add_directive_header(self, sig: str) -> None: Documenter.add_directive_header(self, sig) sourcename = self.get_sourcename() # add some module-specific options if self.options.synopsis: self.add_line(' :synopsis: ' + self.options.synopsis, sourcename) if self.options.platform: self.add_line(' :platform: ' + self.options.platform, sourcename) if self.options.deprecated: self.add_line(' :deprecated:', sourcename) def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, Any]]]: if want_all: if self.__all__: memberlist = self.__all__ else: # for implicit module members, check __module__ to avoid # documenting imported objects return True, get_module_members(self.object) else: memberlist = self.options.members or [] ret = [] for mname in memberlist: try: ret.append((mname, safe_getattr(self.object, mname))) except AttributeError: logger.warning( __('missing attribute mentioned in :members: or __all__: ' 'module %s, attribute %s') % (safe_getattr(self.object, '__name__', '???'), mname), type='autodoc' ) return False, ret def sort_members(self, documenters: List[Tuple["Documenter", bool]], order: str) -> List[Tuple["Documenter", bool]]: if order == 'bysource' and self.__all__: # Sort alphabetically first (for members not listed on the __all__) documenters.sort(key=lambda e: e[0].name) # Sort by __all__ def keyfunc(entry: Tuple[Documenter, bool]) -> int: name = entry[0].name.split('::')[1] if self.__all__ and name in self.__all__: return self.__all__.index(name) else: return len(self.__all__) documenters.sort(key=keyfunc) return documenters else: return super().sort_members(documenters, order) class ModuleLevelDocumenter(Documenter): """ Specialized Documenter subclass for objects on module level (functions, classes, data/constants). """ def resolve_name(self, modname: str, parents: Any, path: str, base: Any ) -> Tuple[str, List[str]]: if modname is None: if path: modname = path.rstrip('.') else: # if documenting a toplevel object without explicit module, # it can be contained in another auto directive ... modname = self.env.temp_data.get('autodoc:module') # ... or in the scope of a module directive if not modname: modname = self.env.ref_context.get('py:module') # ... else, it stays None, which means invalid return modname, parents + [base] class ClassLevelDocumenter(Documenter): """ Specialized Documenter subclass for objects on class level (methods, attributes). """ def resolve_name(self, modname: str, parents: Any, path: str, base: Any ) -> Tuple[str, List[str]]: if modname is None: if path: mod_cls = path.rstrip('.') else: mod_cls = None # if documenting a class-level object without path, # there must be a current class, either from a parent # auto directive ... mod_cls = self.env.temp_data.get('autodoc:class') # ... or from a class directive if mod_cls is None: mod_cls = self.env.ref_context.get('py:class') # ... if still None, there's no way to know if mod_cls is None: return None, [] modname, sep, cls = mod_cls.rpartition('.') parents = [cls] # if the module name is still missing, get it like above if not modname: modname = self.env.temp_data.get('autodoc:module') if not modname: modname = self.env.ref_context.get('py:module') # ... else, it stays None, which means invalid return modname, parents + [base] class DocstringSignatureMixin: """ Mixin for FunctionDocumenter and MethodDocumenter to provide the feature of reading the signature from the docstring. """ _new_docstrings = None # type: List[List[str]] _signatures = None # type: List[str] def _find_signature(self) -> Tuple[str, str]: # candidates of the object name valid_names = [self.objpath[-1]] # type: ignore if isinstance(self, ClassDocumenter): valid_names.append('__init__') if hasattr(self.object, '__mro__'): valid_names.extend(cls.__name__ for cls in self.object.__mro__) docstrings = self.get_doc() self._new_docstrings = docstrings[:] self._signatures = [] result = None for i, doclines in enumerate(docstrings): for j, line in enumerate(doclines): if not line: # no lines in docstring, no match break if line.endswith('\\'): multiline = True line = line.rstrip('\\').rstrip() else: multiline = False # match first line of docstring against signature RE match = py_ext_sig_re.match(line) if not match: continue exmod, path, base, args, retann = match.groups() # the base name must match ours if base not in valid_names: continue # re-prepare docstring to ignore more leading indentation tab_width = self.directive.state.document.settings.tab_width # type: ignore self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[j + 1:]), tabsize=tab_width) if result is None: # first signature result = args, retann else: # subsequent signatures self._signatures.append("(%s) -> %s" % (args, retann)) if multiline: # the signature have multiple signatures on docstring continue else: # don't look any further break if result: # finish the loop when signature found break return result def get_doc(self, ignore: int = None) -> List[List[str]]: if self._new_docstrings is not None: return self._new_docstrings return super().get_doc(ignore) # type: ignore def format_signature(self, **kwargs: Any) -> str: if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore # only act if a signature is not explicitly given already, and if # the feature is enabled result = self._find_signature() if result is not None: self.args, self.retann = result sig = super().format_signature(**kwargs) # type: ignore if self._signatures: return "\n".join([sig] + self._signatures) else: return sig class DocstringStripSignatureMixin(DocstringSignatureMixin): """ Mixin for AttributeDocumenter to provide the feature of stripping any function signature from the docstring. """ def format_signature(self, **kwargs: Any) -> str: if self.args is None and self.env.config.autodoc_docstring_signature: # type: ignore # only act if a signature is not explicitly given already, and if # the feature is enabled result = self._find_signature() if result is not None: # Discarding _args is a only difference with # DocstringSignatureMixin.format_signature. # Documenter.format_signature use self.args value to format. _args, self.retann = result return super().format_signature(**kwargs) class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore """ Specialized Documenter subclass for functions. """ objtype = 'function' member_order = 30 @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: # supports functions, builtins and bound methods exported at the module level return (inspect.isfunction(member) or inspect.isbuiltin(member) or (inspect.isroutine(member) and isinstance(parent, ModuleDocumenter))) def format_args(self, **kwargs: Any) -> str: if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) try: self.env.app.emit('autodoc-before-process-signature', self.object, False) sig = inspect.signature(self.object, follow_wrapped=True) args = stringify_signature(sig, **kwargs) except TypeError as exc: logger.warning(__("Failed to get a function signature for %s: %s"), self.fullname, exc) return None except ValueError: args = '' if self.env.config.strip_signature_backslash: # escape backslashes for reST args = args.replace('\\', '\\\\') return args def document_members(self, all_members: bool = False) -> None: pass def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() super().add_directive_header(sig) if inspect.iscoroutinefunction(self.object): self.add_line(' :async:', sourcename) def format_signature(self, **kwargs: Any) -> str: sigs = [] if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads: # Use signatures for overloaded functions instead of the implementation function. overloaded = True else: overloaded = False sig = super().format_signature(**kwargs) sigs.append(sig) if inspect.is_singledispatch_function(self.object): # append signature of singledispatch'ed functions for typ, func in self.object.registry.items(): if typ is object: pass # default implementation. skipped. else: self.annotate_to_first_argument(func, typ) documenter = FunctionDocumenter(self.directive, '') documenter.object = func documenter.objpath = [None] sigs.append(documenter.format_signature()) if overloaded: for overload in self.analyzer.overloads.get('.'.join(self.objpath)): sig = stringify_signature(overload, **kwargs) sigs.append(sig) return "\n".join(sigs) def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: """Annotate type hint to the first argument of function if needed.""" try: sig = inspect.signature(func) except TypeError as exc: logger.warning(__("Failed to get a function signature for %s: %s"), self.fullname, exc) return except ValueError: return if len(sig.parameters) == 0: return params = list(sig.parameters.values()) if params[0].annotation is Parameter.empty: params[0] = params[0].replace(annotation=typ) try: func.__signature__ = sig.replace(parameters=params) # type: ignore except TypeError: # failed to update signature (ex. built-in or extension types) return class SingledispatchFunctionDocumenter(FunctionDocumenter): """ Used to be a specialized Documenter subclass for singledispatch'ed functions. Retained for backwards compatibility, now does the same as the FunctionDocumenter """ class DecoratorDocumenter(FunctionDocumenter): """ Specialized Documenter subclass for decorator functions. """ objtype = 'decorator' # must be lower than FunctionDocumenter priority = -1 def format_args(self, **kwargs: Any) -> Any: args = super().format_args(**kwargs) if ',' in args: return args else: return None # Types which have confusing metaclass signatures it would be best not to show. # These are listed by name, rather than storing the objects themselves, to avoid # needing to import the modules. _METACLASS_CALL_BLACKLIST = [ 'enum.EnumMeta.__call__', ] class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: ignore """ Specialized Documenter subclass for classes. """ objtype = 'class' member_order = 20 option_spec = { 'members': members_option, 'undoc-members': bool_option, 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'member-order': member_order_option, 'exclude-members': members_set_option, 'private-members': bool_option, 'special-members': members_option, } # type: Dict[str, Callable] _signature_class = None # type: Any _signature_method_name = None # type: str def __init__(self, *args: Any) -> None: super().__init__(*args) merge_special_members_option(self.options) @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: return isinstance(member, type) def import_object(self) -> Any: ret = super().import_object() # if the class is documented under another name, document it # as data/attribute if ret: if hasattr(self.object, '__name__'): self.doc_as_attr = (self.objpath[-1] != self.object.__name__) else: self.doc_as_attr = True return ret def _get_signature(self) -> Tuple[Optional[Any], Optional[str], Optional[Signature]]: def get_user_defined_function_or_method(obj: Any, attr: str) -> Any: """ Get the `attr` function or method from `obj`, if it is user-defined. """ if inspect.is_builtin_class_method(obj, attr): return None attr = self.get_attr(obj, attr, None) if not (inspect.ismethod(attr) or inspect.isfunction(attr)): return None return attr # This sequence is copied from inspect._signature_from_callable. # ValueError means that no signature could be found, so we keep going. # First, let's see if it has an overloaded __call__ defined # in its metaclass call = get_user_defined_function_or_method(type(self.object), '__call__') if call is not None: if "{0.__module__}.{0.__qualname__}".format(call) in _METACLASS_CALL_BLACKLIST: call = None if call is not None: self.env.app.emit('autodoc-before-process-signature', call, True) try: sig = inspect.signature(call, bound_method=True) return type(self.object), '__call__', sig except ValueError: pass # Now we check if the 'obj' class has a '__new__' method new = get_user_defined_function_or_method(self.object, '__new__') if new is not None: self.env.app.emit('autodoc-before-process-signature', new, True) try: sig = inspect.signature(new, bound_method=True) return self.object, '__new__', sig except ValueError: pass # Finally, we should have at least __init__ implemented init = get_user_defined_function_or_method(self.object, '__init__') if init is not None: self.env.app.emit('autodoc-before-process-signature', init, True) try: sig = inspect.signature(init, bound_method=True) return self.object, '__init__', sig except ValueError: pass # None of the attributes are user-defined, so fall back to let inspect # handle it. # We don't know the exact method that inspect.signature will read # the signature from, so just pass the object itself to our hook. self.env.app.emit('autodoc-before-process-signature', self.object, False) try: sig = inspect.signature(self.object, bound_method=False) return None, None, sig except ValueError: pass # Still no signature: happens e.g. for old-style classes # with __init__ in C and no `__text_signature__`. return None, None, None def format_args(self, **kwargs: Any) -> str: if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) try: self._signature_class, self._signature_method_name, sig = self._get_signature() except TypeError as exc: # __signature__ attribute contained junk logger.warning(__("Failed to get a constructor signature for %s: %s"), self.fullname, exc) return None if sig is None: return None return stringify_signature(sig, show_return_annotation=False, **kwargs) def format_signature(self, **kwargs: Any) -> str: if self.doc_as_attr: return '' sig = super().format_signature() overloaded = False qualname = None # TODO: recreate analyzer for the module of class (To be clear, owner of the method) if self._signature_class and self._signature_method_name and self.analyzer: qualname = '.'.join([self._signature_class.__qualname__, self._signature_method_name]) if qualname in self.analyzer.overloads: overloaded = True sigs = [] if overloaded: # Use signatures for overloaded methods instead of the implementation method. for overload in self.analyzer.overloads.get(qualname): parameters = list(overload.parameters.values()) overload = overload.replace(parameters=parameters[1:], return_annotation=Parameter.empty) sig = stringify_signature(overload, **kwargs) sigs.append(sig) else: sigs.append(sig) return "\n".join(sigs) def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() if self.doc_as_attr: self.directivetype = 'attribute' super().add_directive_header(sig) if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(' :final:', sourcename) # add inheritance info, if wanted if not self.doc_as_attr and self.options.show_inheritance: sourcename = self.get_sourcename() self.add_line('', sourcename) if hasattr(self.object, '__bases__') and len(self.object.__bases__): bases = [':class:`%s`' % b.__name__ if b.__module__ in ('__builtin__', 'builtins') else ':class:`%s.%s`' % (b.__module__, b.__qualname__) for b in self.object.__bases__] self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename) def get_doc(self, ignore: int = None) -> List[List[str]]: lines = getattr(self, '_new_docstrings', None) if lines is not None: return lines content = self.env.config.autoclass_content docstrings = [] attrdocstring = self.get_attr(self.object, '__doc__', None) if attrdocstring: docstrings.append(attrdocstring) # for classes, what the "docstring" is can be controlled via a # config value; the default is only the class docstring if content in ('both', 'init'): __init__ = self.get_attr(self.object, '__init__', None) initdocstring = getdoc(__init__, self.get_attr, self.env.config.autodoc_inherit_docstrings, self.parent, self.object_name) # for new-style classes, no __init__ means default __init__ if (initdocstring is not None and (initdocstring == object.__init__.__doc__ or # for pypy initdocstring.strip() == object.__init__.__doc__)): # for !pypy initdocstring = None if not initdocstring: # try __new__ __new__ = self.get_attr(self.object, '__new__', None) initdocstring = getdoc(__new__, self.get_attr, self.env.config.autodoc_inherit_docstrings, self.parent, self.object_name) # for new-style classes, no __new__ means default __new__ if (initdocstring is not None and (initdocstring == object.__new__.__doc__ or # for pypy initdocstring.strip() == object.__new__.__doc__)): # for !pypy initdocstring = None if initdocstring: if content == 'init': docstrings = [initdocstring] else: docstrings.append(initdocstring) tab_width = self.directive.state.document.settings.tab_width return [prepare_docstring(docstring, ignore, tab_width) for docstring in docstrings] def add_content(self, more_content: Any, no_docstring: bool = False) -> None: if self.doc_as_attr: classname = safe_getattr(self.object, '__qualname__', None) if not classname: classname = safe_getattr(self.object, '__name__', None) if classname: module = safe_getattr(self.object, '__module__', None) parentmodule = safe_getattr(self.parent, '__module__', None) if module and module != parentmodule: classname = str(module) + '.' + str(classname) content = StringList([_('alias of :class:`%s`') % classname], source='') super().add_content(content, no_docstring=True) else: super().add_content(more_content) def document_members(self, all_members: bool = False) -> None: if self.doc_as_attr: return super().document_members(all_members) def generate(self, more_content: Any = None, real_modname: str = None, check_module: bool = False, all_members: bool = False) -> None: # Do not pass real_modname and use the name from the __module__ # attribute of the class. # If a class gets imported into the module real_modname # the analyzer won't find the source of the class, if # it looks in real_modname. return super().generate(more_content=more_content, check_module=check_module, all_members=all_members) class ExceptionDocumenter(ClassDocumenter): """ Specialized ClassDocumenter subclass for exceptions. """ objtype = 'exception' member_order = 10 # needs a higher priority than ClassDocumenter priority = 10 @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: return isinstance(member, type) and issubclass(member, BaseException) class DataDocumenter(ModuleLevelDocumenter): """ Specialized Documenter subclass for data items. """ objtype = 'data' member_order = 40 priority = -10 option_spec = dict(ModuleLevelDocumenter.option_spec) option_spec["annotation"] = annotation_option @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: return isinstance(parent, ModuleDocumenter) and isattr def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() if not self.options.annotation: # obtain annotation for this data annotations = getattr(self.parent, '__annotations__', {}) if annotations and 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 document_members(self, all_members: bool = False) -> None: pass def get_real_modname(self) -> str: return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname class DataDeclarationDocumenter(DataDocumenter): """ Specialized Documenter subclass for data that cannot be imported because they are declared without initial value (refs: PEP-526). """ objtype = 'datadecl' directivetype = 'data' member_order = 60 # must be higher than AttributeDocumenter priority = 11 @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: """This documents only INSTANCEATTR members.""" return (isinstance(parent, ModuleDocumenter) and isattr and member is INSTANCEATTR) def import_object(self) -> bool: """Never import anything.""" # disguise as a data self.objtype = 'data' self.object = UNINITIALIZED_ATTR try: # import module to obtain type annotation self.parent = importlib.import_module(self.modname) except ImportError: pass return True def add_content(self, more_content: Any, no_docstring: bool = False) -> None: """Never try to get a docstring from the object.""" super().add_content(more_content, no_docstring=True) class GenericAliasDocumenter(DataDocumenter): """ Specialized Documenter subclass for GenericAliases. """ objtype = 'genericalias' directivetype = 'data' priority = DataDocumenter.priority + 1 @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: return inspect.isgenericalias(member) def add_directive_header(self, sig: str) -> None: self.options.annotation = SUPPRESS # type: ignore super().add_directive_header(sig) def add_content(self, more_content: Any, no_docstring: bool = False) -> None: name = stringify_typehint(self.object) content = StringList([_('alias of %s') % name], source='') super().add_content(content) class TypeVarDocumenter(DataDocumenter): """ Specialized Documenter subclass for TypeVars. """ objtype = 'typevar' directivetype = 'data' priority = DataDocumenter.priority + 1 @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: return isinstance(member, TypeVar) and isattr # type: ignore def add_directive_header(self, sig: str) -> None: self.options.annotation = SUPPRESS # type: ignore super().add_directive_header(sig) def get_doc(self, ignore: int = None) -> List[List[str]]: if ignore is not None: warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, RemovedInSphinx50Warning, stacklevel=2) if self.object.__doc__ != TypeVar.__doc__: return super().get_doc() else: return [] def add_content(self, more_content: Any, no_docstring: bool = False) -> None: attrs = [repr(self.object.__name__)] for constraint in self.object.__constraints__: attrs.append(stringify_typehint(constraint)) if self.object.__covariant__: attrs.append("covariant=True") if self.object.__contravariant__: attrs.append("contravariant=True") content = StringList([_('alias of TypeVar(%s)') % ", ".join(attrs)], source='') super().add_content(content) class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for methods (normal, static and class). """ objtype = 'method' directivetype = 'method' member_order = 50 priority = 1 # must be more than FunctionDocumenter @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: return inspect.isroutine(member) and \ not isinstance(parent, ModuleDocumenter) def import_object(self) -> Any: ret = super().import_object() if not ret: return ret # to distinguish classmethod/staticmethod obj = self.parent.__dict__.get(self.object_name) if obj is None: obj = self.object if (inspect.isclassmethod(obj) or inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name)): # document class and static members before ordinary ones self.member_order = self.member_order - 1 return ret def format_args(self, **kwargs: Any) -> str: if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) try: if self.object == object.__init__ and self.parent != object: # Classes not having own __init__() method are shown as no arguments. # # Note: The signature of object.__init__() is (self, /, *args, **kwargs). # But it makes users confused. args = '()' else: if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): self.env.app.emit('autodoc-before-process-signature', self.object, False) sig = inspect.signature(self.object, bound_method=False) else: self.env.app.emit('autodoc-before-process-signature', self.object, True) sig = inspect.signature(self.object, bound_method=True, follow_wrapped=True) args = stringify_signature(sig, **kwargs) except TypeError as exc: logger.warning(__("Failed to get a method signature for %s: %s"), self.fullname, exc) return None except ValueError: args = '' if self.env.config.strip_signature_backslash: # escape backslashes for reST args = args.replace('\\', '\\\\') return args def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() obj = self.parent.__dict__.get(self.object_name, self.object) if inspect.isabstractmethod(obj): self.add_line(' :abstractmethod:', sourcename) if inspect.iscoroutinefunction(obj): self.add_line(' :async:', sourcename) if inspect.isclassmethod(obj): self.add_line(' :classmethod:', sourcename) if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name): self.add_line(' :staticmethod:', sourcename) if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(' :final:', sourcename) def document_members(self, all_members: bool = False) -> None: pass def format_signature(self, **kwargs: Any) -> str: sigs = [] if self.analyzer and '.'.join(self.objpath) in self.analyzer.overloads: # Use signatures for overloaded methods instead of the implementation method. overloaded = True else: overloaded = False sig = super().format_signature(**kwargs) sigs.append(sig) meth = self.parent.__dict__.get(self.objpath[-1]) if inspect.is_singledispatch_method(meth): # append signature of singledispatch'ed functions for typ, func in meth.dispatcher.registry.items(): if typ is object: pass # default implementation. skipped. else: self.annotate_to_first_argument(func, typ) documenter = MethodDocumenter(self.directive, '') documenter.parent = self.parent documenter.object = func documenter.objpath = [None] sigs.append(documenter.format_signature()) if overloaded: for overload in self.analyzer.overloads.get('.'.join(self.objpath)): if not inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): parameters = list(overload.parameters.values()) overload = overload.replace(parameters=parameters[1:]) sig = stringify_signature(overload, **kwargs) sigs.append(sig) return "\n".join(sigs) def annotate_to_first_argument(self, func: Callable, typ: Type) -> None: """Annotate type hint to the first argument of function if needed.""" try: sig = inspect.signature(func) except TypeError as exc: logger.warning(__("Failed to get a method signature for %s: %s"), self.fullname, exc) return except ValueError: return if len(sig.parameters) == 1: return params = list(sig.parameters.values()) if params[1].annotation is Parameter.empty: params[1] = params[1].replace(annotation=typ) try: func.__signature__ = sig.replace(parameters=params) # type: ignore except TypeError: # failed to update signature (ex. built-in or extension types) return class SingledispatchMethodDocumenter(MethodDocumenter): """ Used to be a specialized Documenter subclass for singledispatch'ed methods. Retained for backwards compatibility, now does the same as the MethodDocumenter """ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for attributes. """ objtype = 'attribute' member_order = 60 option_spec = dict(ModuleLevelDocumenter.option_spec) option_spec["annotation"] = annotation_option # must be higher than the MethodDocumenter, else it will recognize # some non-data descriptors as methods priority = 10 @staticmethod def is_function_or_method(obj: Any) -> bool: return inspect.isfunction(obj) or inspect.isbuiltin(obj) or inspect.ismethod(obj) @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: if inspect.isattributedescriptor(member): return True elif (not isinstance(parent, ModuleDocumenter) and not inspect.isroutine(member) and not isinstance(member, type)): return True else: return False def document_members(self, all_members: bool = False) -> None: pass def import_object(self) -> Any: ret = super().import_object() if inspect.isenumattribute(self.object): self.object = self.object.value if inspect.isattributedescriptor(self.object): self._datadescriptor = True else: # if it's not a data descriptor self._datadescriptor = False return ret def get_real_modname(self) -> str: return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() if not self.options.annotation: # obtain type annotation for this attribute annotations = getattr(self.parent, '__annotations__', {}) if annotations and 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) # data descriptors do not have useful values if not self._datadescriptor: try: if self.object is INSTANCEATTR: 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 get_doc(self, ignore: int = None) -> List[List[str]]: try: # Disable `autodoc_inherit_docstring` temporarily to avoid to obtain # a docstring from the value which descriptor returns unexpectedly. # ref: https://github.com/sphinx-doc/sphinx/issues/7805 orig = self.env.config.autodoc_inherit_docstrings self.env.config.autodoc_inherit_docstrings = False # type: ignore return super().get_doc(ignore) finally: self.env.config.autodoc_inherit_docstrings = orig # type: ignore def add_content(self, more_content: Any, no_docstring: bool = False) -> None: if not self._datadescriptor: # if it's not a data descriptor, its docstring is very probably the # wrong thing to display no_docstring = True super().add_content(more_content, no_docstring) class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore """ Specialized Documenter subclass for properties. """ objtype = 'property' directivetype = 'method' member_order = 60 # before AttributeDocumenter priority = AttributeDocumenter.priority + 1 @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: return inspect.isproperty(member) and isinstance(parent, ClassDocumenter) def document_members(self, all_members: bool = False) -> None: pass def get_real_modname(self) -> str: return self.get_attr(self.parent or self.object, '__module__', None) \ or self.modname def add_directive_header(self, sig: str) -> None: super().add_directive_header(sig) sourcename = self.get_sourcename() if inspect.isabstractmethod(self.object): self.add_line(' :abstractmethod:', sourcename) self.add_line(' :property:', sourcename) class InstanceAttributeDocumenter(AttributeDocumenter): """ Specialized Documenter subclass for attributes that cannot be imported because they are instance attributes (e.g. assigned in __init__). """ objtype = 'instanceattribute' directivetype = 'attribute' member_order = 60 # must be higher than AttributeDocumenter priority = 11 @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: """This documents only INSTANCEATTR members.""" return (not isinstance(parent, ModuleDocumenter) and isattr and member is INSTANCEATTR) def import_object(self) -> bool: """Never import anything.""" # disguise as an attribute self.objtype = 'attribute' self.object = INSTANCEATTR self._datadescriptor = False return True def add_content(self, more_content: Any, no_docstring: bool = False) -> None: """Never try to get a docstring from the object.""" super().add_content(more_content, no_docstring=True) class SlotsAttributeDocumenter(AttributeDocumenter): """ Specialized Documenter subclass for attributes that cannot be imported because they are attributes in __slots__. """ objtype = 'slotsattribute' directivetype = 'attribute' member_order = 60 # must be higher than AttributeDocumenter priority = 11 @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: """This documents only SLOTSATTR members.""" return member is SLOTSATTR def import_object(self) -> Any: """Never import anything.""" # disguise as an attribute self.objtype = 'attribute' self._datadescriptor = True with mock(self.env.config.autodoc_mock_imports): try: ret = import_object(self.modname, self.objpath[:-1], 'class', attrgetter=self.get_attr, warningiserror=self.env.config.autodoc_warningiserror) self.module, _, _, self.parent = ret return True except ImportError as exc: logger.warning(exc.args[0], type='autodoc', subtype='import_object') self.env.note_reread() return False def get_doc(self, ignore: int = None) -> List[List[str]]: """Decode and return lines of the docstring(s) for the object.""" if ignore is not None: warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." % self.__class__.__name__, RemovedInSphinx50Warning, stacklevel=2) name = self.objpath[-1] __slots__ = safe_getattr(self.parent, '__slots__', []) if isinstance(__slots__, dict) and isinstance(__slots__.get(name), str): docstring = prepare_docstring(__slots__[name]) return [docstring] else: return [] def get_documenters(app: Sphinx) -> Dict[str, Type[Documenter]]: """Returns registered Documenter classes""" warnings.warn("get_documenters() is deprecated.", RemovedInSphinx50Warning, stacklevel=2) return app.registry.documenters def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any: """Alternative getattr() for types""" for typ, func in app.registry.autodoc_attrgettrs.items(): if isinstance(obj, typ): return func(obj, name, *defargs) return safe_getattr(obj, name, *defargs) def migrate_autodoc_member_order(app: Sphinx, config: Config) -> None: if config.autodoc_member_order == 'alphabetic': # RemovedInSphinx50Warning logger.warning(__('autodoc_member_order now accepts "alphabetical" ' 'instead of "alphabetic". Please update your setting.')) config.autodoc_member_order = 'alphabetical' # type: ignore def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(ModuleDocumenter) app.add_autodocumenter(ClassDocumenter) app.add_autodocumenter(ExceptionDocumenter) app.add_autodocumenter(DataDocumenter) app.add_autodocumenter(DataDeclarationDocumenter) app.add_autodocumenter(GenericAliasDocumenter) app.add_autodocumenter(TypeVarDocumenter) app.add_autodocumenter(FunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(PropertyDocumenter) app.add_autodocumenter(InstanceAttributeDocumenter) app.add_autodocumenter(SlotsAttributeDocumenter) app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init')) app.add_config_value('autodoc_member_order', 'alphabetical', True, ENUM('alphabetic', 'alphabetical', 'bysource', 'groupwise')) app.add_config_value('autodoc_default_options', {}, True) app.add_config_value('autodoc_docstring_signature', True, True) app.add_config_value('autodoc_mock_imports', [], True) app.add_config_value('autodoc_typehints', "signature", True, ENUM("signature", "description", "none")) app.add_config_value('autodoc_warningiserror', True, True) app.add_config_value('autodoc_inherit_docstrings', True, True) app.add_event('autodoc-before-process-signature') app.add_event('autodoc-process-docstring') app.add_event('autodoc-process-signature') app.add_event('autodoc-skip-member') app.connect('config-inited', migrate_autodoc_member_order, priority=800) app.setup_extension('sphinx.ext.autodoc.type_comment') app.setup_extension('sphinx.ext.autodoc.typehints') return {'version': sphinx.__display_version__, 'parallel_read_safe': True}