diff --git a/CHANGES b/CHANGES index 9547c4bad1f..4e44a3330a1 100644 --- a/CHANGES +++ b/CHANGES @@ -25,7 +25,8 @@ Features added Bugs fixed ---------- -* #9866: autodoc: doccoment for the imported class was ignored +* #9866: autodoc: doccomment for the imported class was ignored +* #9883: autodoc: doccomment for the alias to mocked object was ignored * #9878: mathjax: MathJax configuration is placed after loading MathJax itself * #9857: Generated RFC links use outdated base url diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 157e57e3ad5..a4d5884e89e 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -751,7 +751,7 @@ def is_filtered_inherited_member(name: str, obj: Any) -> bool: isprivate = membername.startswith('_') keep = False - if ismock(member): + if ismock(member) and (namespace, membername) not in attr_docs: # mocked module or object pass elif self.options.exclude_members and membername in self.options.exclude_members: @@ -2009,7 +2009,8 @@ def add_directive_header(self, sig: str) -> None: self.add_line(' :type: ' + objrepr, sourcename) try: - if self.options.no_value or self.should_suppress_value_header(): + if (self.options.no_value or self.should_suppress_value_header() or + ismock(self.object)): pass else: objrepr = object_description(self.object) @@ -2528,11 +2529,11 @@ def is_function_or_method(obj: Any) -> bool: @classmethod def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any ) -> bool: - if inspect.isattributedescriptor(member): + if isinstance(parent, ModuleDocumenter): + return False + elif inspect.isattributedescriptor(member): return True - elif (not isinstance(parent, ModuleDocumenter) and - not inspect.isroutine(member) and - not isinstance(member, type)): + elif not inspect.isroutine(member) and not isinstance(member, type): return True else: return False @@ -2625,7 +2626,8 @@ def add_directive_header(self, sig: str) -> None: self.add_line(' :type: ' + objrepr, sourcename) try: - if self.options.no_value or self.should_suppress_value_header(): + if (self.options.no_value or self.should_suppress_value_header() or + ismock(self.object)): pass else: objrepr = object_description(self.object) diff --git a/sphinx/ext/autodoc/mock.py b/sphinx/ext/autodoc/mock.py index 62f36da7956..ec8c0108738 100644 --- a/sphinx/ext/autodoc/mock.py +++ b/sphinx/ext/autodoc/mock.py @@ -170,7 +170,8 @@ def ismock(subject: Any) -> bool: try: # check the object is mocked object __mro__ = safe_getattr(type(subject), '__mro__', []) - if len(__mro__) > 2 and __mro__[1] is _MockObject: + if len(__mro__) > 2 and __mro__[-2] is _MockObject: + # A mocked object has a MRO that ends with (..., _MockObject, object). return True except AttributeError: pass diff --git a/tests/roots/test-ext-autodoc/target/need_mocks.py b/tests/roots/test-ext-autodoc/target/need_mocks.py index a2995418410..6f75dc3802e 100644 --- a/tests/roots/test-ext-autodoc/target/need_mocks.py +++ b/tests/roots/test-ext-autodoc/target/need_mocks.py @@ -22,6 +22,10 @@ def func(arg: missing_module.Class): class TestAutodoc(object): """TestAutodoc docstring.""" + + #: docstring + Alias = missing_module2.Class + @missing_name def decoratedMethod(self): """TestAutodoc::decoratedMethod docstring""" @@ -34,3 +38,6 @@ class Inherited(missing_module.Class): sphinx.missing_module4.missing_function(len(missing_name2)) + +#: docstring +Alias = missing_module2.Class diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index e04cd83b681..643899286a4 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -536,7 +536,7 @@ def test_mocked_module_imports(app, warning): sys.modules.pop('target', None) # unload target module to clear the module cache # no autodoc_mock_imports - options = {"members": 'TestAutodoc,decoratedFunction,func'} + options = {"members": 'TestAutodoc,decoratedFunction,func,Alias'} actual = do_autodoc(app, 'module', 'target.need_mocks', options) assert list(actual) == [] assert "autodoc: failed to import module 'need_mocks'" in warning.getvalue() @@ -557,12 +557,24 @@ def test_mocked_module_imports(app, warning): '.. py:module:: target.need_mocks', '', '', + '.. py:data:: Alias', + ' :module: target.need_mocks', + '', + ' docstring', + '', + '', '.. py:class:: TestAutodoc()', ' :module: target.need_mocks', '', ' TestAutodoc docstring.', '', '', + ' .. py:attribute:: TestAutodoc.Alias', + ' :module: target.need_mocks', + '', + ' docstring', + '', + '', ' .. py:method:: TestAutodoc.decoratedMethod()', ' :module: target.need_mocks', '', diff --git a/tests/test_ext_autodoc_mock.py b/tests/test_ext_autodoc_mock.py index 497bd8a6b1a..f3a9e2dc187 100644 --- a/tests/test_ext_autodoc_mock.py +++ b/tests/test_ext_autodoc_mock.py @@ -146,6 +146,7 @@ class Inherited(mod1.Class): assert ismock(mod1) is True assert ismock(mod1.Class) is True + assert ismock(mod1.submod.Class) is True assert ismock(Inherited) is False assert ismock(mod2) is False