From 3956cf2249d27ed63e8381c07dfde36f6c96f78f Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Wed, 8 Jun 2022 15:45:27 +0100 Subject: [PATCH 1/2] Fix documenting inherited attributes --- sphinx/ext/autodoc/__init__.py | 3 ++- sphinx/ext/autodoc/importer.py | 9 +++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index e16ab8ce5c5..642e0cfd805 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1670,7 +1670,8 @@ def add_directive_header(self, sig: str) -> None: self.add_line(' ' + _('Bases: %s') % ', '.join(base_classes), sourcename) def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: - members = get_class_members(self.object, self.objpath, self.get_attr) + members = get_class_members(self.object, self.objpath, self.get_attr, + self.config.autodoc_inherit_docstrings) if not want_all: if not self.options.members: return False, [] # type: ignore diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index 85c1e81be64..d392ae75ddf 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -205,8 +205,8 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable, return members -def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable - ) -> Dict[str, "ObjectMember"]: +def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable, + inherit_docstrings: bool = True) -> Dict[str, "ObjectMember"]: """Get members and attributes of target class.""" from sphinx.ext.autodoc import INSTANCEATTR, ObjectMember @@ -290,6 +290,11 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable elif (ns == qualname and docstring and isinstance(members[name], ObjectMember) and not members[name].docstring): + if cls != subject and not inherit_docstrings: + # If we are in the MRO of the class and not the class itself, + # and we do not want to inherit docstrings, then skip setting + # the docstring below + continue # attribute is already known, because dir(subject) enumerates it. # But it has no docstring yet members[name].docstring = '\n'.join(docstring) From 29edce9243046962f5f024d510315133448dd3e1 Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 14 Jun 2022 02:49:07 +0900 Subject: [PATCH 2/2] test: Add testcase for autodoc_inherit_docstring and attributes (refs: #10539) --- .../test-ext-autodoc/target/inheritance.py | 3 + tests/test_ext_autodoc.py | 8 +++ tests/test_ext_autodoc_automodule.py | 7 ++ tests/test_ext_autodoc_configs.py | 66 +++++++++++++++++++ 4 files changed, 84 insertions(+) diff --git a/tests/roots/test-ext-autodoc/target/inheritance.py b/tests/roots/test-ext-autodoc/target/inheritance.py index 485a943c872..85d95cd742e 100644 --- a/tests/roots/test-ext-autodoc/target/inheritance.py +++ b/tests/roots/test-ext-autodoc/target/inheritance.py @@ -1,4 +1,7 @@ class Base(object): + #: docstring + inheritedattr = None + def inheritedmeth(self): """Inherited function.""" diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index ecd2bbce6fc..a35c6f640e5 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -549,6 +549,7 @@ def test_autodoc_members(app): actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', ' .. py:method:: Base.inheritedclassmeth()', ' .. py:method:: Base.inheritedmeth()', ' .. py:method:: Base.inheritedstaticmeth(cls)' @@ -569,6 +570,7 @@ def test_autodoc_members(app): actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', ' .. py:method:: Base.inheritedclassmeth()', ' .. py:method:: Base.inheritedmeth()', ' .. py:method:: Base.inheritedstaticmeth(cls)' @@ -601,6 +603,7 @@ def test_autodoc_exclude_members(app): actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', ' .. py:method:: Base.inheritedclassmeth()' ] @@ -618,6 +621,7 @@ def test_autodoc_exclude_members(app): actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', ' .. py:method:: Base.inheritedclassmeth()' ] @@ -628,6 +632,7 @@ def test_autodoc_exclude_members(app): actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', ' .. py:method:: Base.inheritedclassmeth()', ' .. py:method:: Base.inheritedstaticmeth(cls)' ] @@ -639,6 +644,7 @@ def test_autodoc_exclude_members(app): actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', ' .. py:method:: Base.inheritedclassmeth()', ] @@ -648,6 +654,7 @@ def test_autodoc_exclude_members(app): actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', ' .. py:method:: Base.inheritedclassmeth()', ] @@ -658,6 +665,7 @@ def test_autodoc_exclude_members(app): actual = do_autodoc(app, 'class', 'target.inheritance.Base', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Base()', + ' .. py:attribute:: Base.inheritedattr', ' .. py:method:: Base.inheritedclassmeth()', ' .. py:method:: Base.inheritedmeth()', ' .. py:method:: Base.inheritedstaticmeth(cls)' diff --git a/tests/test_ext_autodoc_automodule.py b/tests/test_ext_autodoc_automodule.py index 8208d86f661..71b23679d96 100644 --- a/tests/test_ext_autodoc_automodule.py +++ b/tests/test_ext_autodoc_automodule.py @@ -133,6 +133,13 @@ def test_automodule_inherited_members(app): ' :module: target.inheritance', '', '', + ' .. py:attribute:: Base.inheritedattr', + ' :module: target.inheritance', + ' :value: None', + '', + ' docstring', + '', + '', ' .. py:method:: Base.inheritedclassmeth()', ' :module: target.inheritance', ' :classmethod:', diff --git a/tests/test_ext_autodoc_configs.py b/tests/test_ext_autodoc_configs.py index 0011f450b5a..f40f3354e11 100644 --- a/tests/test_ext_autodoc_configs.py +++ b/tests/test_ext_autodoc_configs.py @@ -277,6 +277,72 @@ def test_autodoc_inherit_docstrings(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autodoc_inherit_docstrings_for_inherited_members(app): + options = {"members": None, + "inherited-members": None} + + assert app.config.autodoc_inherit_docstrings is True # default + actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options) + assert list(actual) == [ + '', + '.. py:class:: Derived()', + ' :module: target.inheritance', + '', + '', + ' .. py:attribute:: Derived.inheritedattr', + ' :module: target.inheritance', + ' :value: None', + '', + ' docstring', + '', + '', + ' .. py:method:: Derived.inheritedclassmeth()', + ' :module: target.inheritance', + ' :classmethod:', + '', + ' Inherited class method.', + '', + '', + ' .. py:method:: Derived.inheritedmeth()', + ' :module: target.inheritance', + '', + ' Inherited function.', + '', + '', + ' .. py:method:: Derived.inheritedstaticmeth(cls)', + ' :module: target.inheritance', + ' :staticmethod:', + '', + ' Inherited static method.', + '', + ] + + # disable autodoc_inherit_docstrings + app.config.autodoc_inherit_docstrings = False + actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options) + assert list(actual) == [ + '', + '.. py:class:: Derived()', + ' :module: target.inheritance', + '', + '', + ' .. py:method:: Derived.inheritedclassmeth()', + ' :module: target.inheritance', + ' :classmethod:', + '', + ' Inherited class method.', + '', + '', + ' .. py:method:: Derived.inheritedstaticmeth(cls)', + ' :module: target.inheritance', + ' :staticmethod:', + '', + ' Inherited static method.', + '', + ] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_docstring_signature(app): options = {"members": None, "special-members": "__init__, __new__"}