diff options
author | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2021-01-23 20:08:19 +0300 |
---|---|---|
committer | Takeshi KOMIYA <i.tkomiya@gmail.com> | 2021-01-24 08:18:00 +0300 |
commit | a78c6b799f0dab414dadc79fa3920d0581c01279 (patch) | |
tree | 3d25df9bc31db18c7d3f1c5e6793ce0b8ab3a425 /sphinx/ext | |
parent | 3a0a6556c59a7b31586dd97b43101f8dbfd2ef63 (diff) |
Fix #8134: autodoc: crashes when mocked decorator takes arguments
autodoc crashed when a decorator in mocked module takes arguments
because mock system returns the first argument for the decorator as a
decorated object.
This changes the approach for mocking decorators that remembers
arguments for each decoration, and fetch the latest argument on
generating document.
Diffstat (limited to 'sphinx/ext')
-rw-r--r-- | sphinx/ext/autodoc/__init__.py | 6 | ||||
-rw-r--r-- | sphinx/ext/autodoc/importer.py | 4 | ||||
-rw-r--r-- | sphinx/ext/autodoc/mock.py | 27 |
3 files changed, 29 insertions, 8 deletions
diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 83c7d28c4..1490d1551 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -27,7 +27,7 @@ from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warni from sphinx.environment import BuildEnvironment from sphinx.ext.autodoc.importer import (get_class_members, get_object_members, import_module, import_object) -from sphinx.ext.autodoc.mock import ismock, mock +from sphinx.ext.autodoc.mock import ismock, mock, undecorate from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import inspect, logging @@ -422,6 +422,8 @@ class Documenter: attrgetter=self.get_attr, warningiserror=self.config.autodoc_warningiserror) self.module, self.parent, self.object_name, self.object = ret + if ismock(self.object): + self.object = undecorate(self.object) return True except ImportError as exc: if raiseerror: @@ -1054,6 +1056,8 @@ class ModuleDocumenter(Documenter): for name in dir(self.object): try: value = safe_getattr(self.object, name, None) + if ismock(value): + value = undecorate(value) docstring = attr_docs.get(('', name), []) members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) except AttributeError: diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index ffcb27ecc..d7c9b93f5 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -15,6 +15,7 @@ from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tup from sphinx.deprecation import (RemovedInSphinx40Warning, RemovedInSphinx50Warning, deprecated_alias) +from sphinx.ext.autodoc.mock import ismock, undecorate from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import logging from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass, @@ -285,6 +286,9 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable for name in dir(subject): try: value = attrgetter(subject, name) + if ismock(value): + value = undecorate(value) + unmangled = unmangle(subject, name) if unmangled and unmangled not in members: if name in obj_dict: diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index 3d4f76410..d3f4291c2 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -13,7 +13,7 @@ import os import sys from importlib.abc import Loader, MetaPathFinder from importlib.machinery import ModuleSpec -from types import FunctionType, MethodType, ModuleType +from types import ModuleType from typing import Any, Generator, Iterator, List, Sequence, Tuple, Union from sphinx.util import logging @@ -27,6 +27,7 @@ class _MockObject: __display_name__ = '_MockObject' __sphinx_mock__ = True + __sphinx_decorator_args__ = () # type: Tuple[Any, ...] def __new__(cls, *args: Any, **kwargs: Any) -> Any: if len(args) == 3 and isinstance(args[1], tuple): @@ -60,18 +61,19 @@ class _MockObject: return _make_subclass(key, self.__display_name__, self.__class__)() def __call__(self, *args: Any, **kwargs: Any) -> Any: - if args and type(args[0]) in [type, FunctionType, MethodType]: - # Appears to be a decorator, pass through unchanged - return args[0] - return self + call = self.__class__() + call.__sphinx_decorator_args__ = args + return call def __repr__(self) -> str: return self.__display_name__ def _make_subclass(name: str, module: str, superclass: Any = _MockObject, - attributes: Any = None) -> Any: - attrs = {'__module__': module, '__display_name__': module + '.' + name} + attributes: Any = None, decorator_args: Tuple = ()) -> Any: + attrs = {'__module__': module, + '__display_name__': module + '.' + name, + '__sphinx_decorator_args__': decorator_args} attrs.update(attributes or {}) return type(name, (superclass,), attrs) @@ -172,3 +174,14 @@ def ismock(subject: Any) -> bool: pass return False + + +def undecorate(subject: _MockObject) -> Any: + """Unwrap mock if *subject* is decorated by mocked object. + + If not decorated, returns given *subject* itself. + """ + if ismock(subject) and subject.__sphinx_decorator_args__: + return subject.__sphinx_decorator_args__[0] + else: + return subject |