diff options
Diffstat (limited to 'sphinx/ext/autodoc/__init__.py')
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 221 |
1 files changed, 108 insertions, 113 deletions
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index e18dc47b9..98a3cc85d 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -17,13 +17,12 @@ from inspect import Parameter from types import ModuleType from typing import Any, Callable, Dict, Iterator, List, Sequence, Set, Tuple, Type, Union from typing import TYPE_CHECKING -from unittest.mock import patch from docutils.statemachine import StringList import sphinx from sphinx.application import Sphinx -from sphinx.config import ENUM +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 @@ -32,7 +31,7 @@ from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import inspect from sphinx.util import logging -from sphinx.util import rpartition +from sphinx.util import split_full_qualified_name 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 @@ -311,7 +310,8 @@ class Documenter: modname = None parents = [] - self.modname, self.objpath = self.resolve_name(modname, parents, path, base) + 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 @@ -419,8 +419,15 @@ class Documenter: directive = getattr(self, 'directivetype', self.objtype) name = self.format_name() sourcename = self.get_sourcename() - self.add_line('.. %s:%s:: %s%s' % (domain, directive, name, sig), - 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: @@ -893,8 +900,14 @@ class ModuleLevelDocumenter(Documenter): ) -> Tuple[str, List[str]]: if modname is None: if path: - modname = path.rstrip('.') - else: + stripped = path.rstrip('.') + modname, qualname = split_full_qualified_name(stripped) + if qualname: + parents = qualname.split(".") + else: + parents = [] + + if modname is None: # if documenting a toplevel object without explicit module, # it can be contained in another auto directive ... modname = self.env.temp_data.get('autodoc:module') @@ -927,8 +940,13 @@ class ClassLevelDocumenter(Documenter): # ... if still None, there's no way to know if mod_cls is None: return None, [] - modname, cls = rpartition(mod_cls, '.') - parents = [cls] + + try: + modname, qualname = split_full_qualified_name(mod_cls) + parents = qualname.split(".") if qualname else [] + except ImportError: + parents = mod_cls.split(".") + # if the module name is still missing, get it like above if not modname: modname = self.env.temp_data.get('autodoc:module') @@ -1026,43 +1044,19 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) - unwrapped = inspect.unwrap(self.object) - if ((inspect.isbuiltin(unwrapped) or inspect.ismethoddescriptor(unwrapped)) and - not inspect.is_cython_function_or_method(unwrapped)): - # cannot introspect arguments of a C function or method - return None try: - if (not inspect.isfunction(unwrapped) and - not inspect.ismethod(unwrapped) and - not inspect.isbuiltin(unwrapped) and - not inspect.is_cython_function_or_method(unwrapped) and - not inspect.isclass(unwrapped) and - hasattr(unwrapped, '__call__')): - self.env.app.emit('autodoc-before-process-signature', - unwrapped.__call__, False) - sig = inspect.signature(unwrapped.__call__) + self.env.app.emit('autodoc-before-process-signature', self.object, False) + if inspect.is_singledispatch_function(self.object): + sig = inspect.signature(self.object, follow_wrapped=True) else: - self.env.app.emit('autodoc-before-process-signature', unwrapped, False) - sig = inspect.signature(unwrapped) + sig = inspect.signature(self.object) args = stringify_signature(sig, **kwargs) - except TypeError: - if (inspect.is_builtin_class_method(unwrapped, '__new__') and - inspect.is_builtin_class_method(unwrapped, '__init__')): - raise TypeError('%r is a builtin class' % unwrapped) - - # if a class should be documented as function (yay duck - # typing) we try to use the constructor signature as function - # signature without the first argument. - try: - self.env.app.emit('autodoc-before-process-signature', - unwrapped.__new__, True) - sig = inspect.signature(unwrapped.__new__, bound_method=True) - args = stringify_signature(sig, show_return_annotation=False, **kwargs) - except TypeError: - self.env.app.emit('autodoc-before-process-signature', - unwrapped.__init__, True) - sig = inspect.signature(unwrapped.__init__, bound_method=True) - args = stringify_signature(sig, show_return_annotation=False, **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 @@ -1074,41 +1068,28 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ def add_directive_header(self, sig: str) -> None: sourcename = self.get_sourcename() - if inspect.is_singledispatch_function(self.object): - self.add_singledispatch_directive_header(sig) - else: - super().add_directive_header(sig) + super().add_directive_header(sig) if inspect.iscoroutinefunction(self.object): self.add_line(' :async:', sourcename) - def add_singledispatch_directive_header(self, sig: str) -> None: - sourcename = self.get_sourcename() - - # intercept generated directive headers - # TODO: It is very hacky to use mock to intercept header generation - with patch.object(self, 'add_line') as add_line: - super().add_directive_header(sig) - - # output first line of header - self.add_line(*add_line.call_args_list[0][0]) + def format_signature(self, **kwargs: Any) -> str: + sig = super().format_signature(**kwargs) + sigs = [sig] - # inserts 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) + 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 - self.add_line(' %s%s' % (self.format_name(), - documenter.format_signature()), - sourcename) + documenter = FunctionDocumenter(self.directive, '') + documenter.object = func + sigs.append(documenter.format_signature()) - # output remains of directive header - for call in add_line.call_args_list[1:]: - self.add_line(*call[0]) + 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.""" @@ -1448,18 +1429,33 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: if self.env.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) - unwrapped = inspect.unwrap(self.object) - if ((inspect.isbuiltin(unwrapped) or inspect.ismethoddescriptor(unwrapped)) and - not inspect.is_cython_function_or_method(unwrapped)): - # can never get arguments of a C function or method + 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) + + meth = self.parent.__dict__.get(self.objpath[-1], None) + if meth and inspect.is_singledispatch_method(meth): + sig = inspect.signature(self.object, bound_method=True, + follow_wrapped=True) + else: + sig = inspect.signature(self.object, bound_method=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 - if inspect.isstaticmethod(unwrapped, cls=self.parent, name=self.object_name): - self.env.app.emit('autodoc-before-process-signature', unwrapped, False) - sig = inspect.signature(unwrapped, bound_method=False) - else: - self.env.app.emit('autodoc-before-process-signature', unwrapped, True) - sig = inspect.signature(unwrapped, bound_method=True) - args = stringify_signature(sig, **kwargs) + except ValueError: + args = '' if self.env.config.strip_signature_backslash: # escape backslashes for reST @@ -1467,11 +1463,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return args def add_directive_header(self, sig: str) -> None: - meth = self.parent.__dict__.get(self.objpath[-1]) - if inspect.is_singledispatch_method(meth): - self.add_singledispatch_directive_header(sig) - else: - super().add_directive_header(sig) + super().add_directive_header(sig) sourcename = self.get_sourcename() obj = self.parent.__dict__.get(self.object_name, self.object) @@ -1489,34 +1481,26 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: def document_members(self, all_members: bool = False) -> None: pass - def add_singledispatch_directive_header(self, sig: str) -> None: - sourcename = self.get_sourcename() - - # intercept generated directive headers - # TODO: It is very hacky to use mock to intercept header generation - with patch.object(self, 'add_line') as add_line: - super().add_directive_header(sig) - - # output first line of header - self.add_line(*add_line.call_args_list[0][0]) + def format_signature(self, **kwargs: Any) -> str: + sig = super().format_signature(**kwargs) + sigs = [sig] - # inserts signature of singledispatch'ed functions meth = self.parent.__dict__.get(self.objpath[-1]) - for typ, func in meth.dispatcher.registry.items(): - if typ is object: - pass # default implementation. skipped. - else: - self.annotate_to_first_argument(func, typ) + 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.object = func - self.add_line(' %s%s' % (self.format_name(), - documenter.format_signature()), - sourcename) + documenter = MethodDocumenter(self.directive, '') + documenter.parent = self.parent + documenter.object = func + documenter.objpath = [None] + sigs.append(documenter.format_signature()) - # output remains of directive header - for call in add_line.call_args_list[1:]: - self.add_line(*call[0]) + 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.""" @@ -1753,6 +1737,14 @@ def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any: 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) @@ -1768,7 +1760,8 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_autodocumenter(SlotsAttributeDocumenter) app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init')) - app.add_config_value('autodoc_member_order', 'alphabetic', True) + 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) @@ -1781,6 +1774,8 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_event('autodoc-process-signature') app.add_event('autodoc-skip-member') + app.connect('config-inited', migrate_autodoc_member_order) + app.setup_extension('sphinx.ext.autodoc.type_comment') app.setup_extension('sphinx.ext.autodoc.typehints') |