Skip to content

Commit

Permalink
Merge pull request #8801 from tk0miya/8800_docstring_for_uninitialize…
Browse files Browse the repository at this point in the history
…d_attribute_on_superclass_ignored

Fix #8800: autodoc: Uninitialized attributes in superclass are recognized as undocumented
  • Loading branch information
tk0miya committed Feb 1, 2021
2 parents 7ca279e + 6d8c918 commit ce7be36
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 8 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ Bugs fixed
contains invalid type comments
* #8693: autodoc: Default values for overloaded functions are rendered as string
* #8134: autodoc: crashes when mocked decorator takes arguments
* #8800: autodoc: Uninitialized attributes in superclass are recognized as
undocumented
* #8306: autosummary: mocked modules are documented as empty page when using
:recursive: option
* #8232: graphviz: Image node is not rendered if graph file is in subdirectory
Expand Down
27 changes: 19 additions & 8 deletions sphinx/ext/autodoc/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,24 +294,35 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable

try:
for cls in getmro(subject):
try:
modname = safe_getattr(cls, '__module__')
qualname = safe_getattr(cls, '__qualname__')
analyzer = ModuleAnalyzer.for_module(modname)
analyzer.analyze()
except AttributeError:
qualname = None
analyzer = None
except PycodeError:
analyzer = None

# annotation only member (ex. attr: int)
for name in getannotations(cls):
name = unmangle(cls, name)
if name and name not in members:
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls)
if analyzer and (qualname, name) in analyzer.attr_docs:
docstring = '\n'.join(analyzer.attr_docs[qualname, name])
else:
docstring = None

members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
docstring=docstring)

# append instance attributes (cf. self.attr1) if analyzer knows
try:
modname = safe_getattr(cls, '__module__')
qualname = safe_getattr(cls, '__qualname__')
analyzer = ModuleAnalyzer.for_module(modname)
analyzer.analyze()
if analyzer:
for (ns, name), docstring in analyzer.attr_docs.items():
if ns == qualname and name not in members:
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
docstring='\n'.join(docstring))
except (AttributeError, PycodeError):
pass
except AttributeError:
pass

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class Base:
attr1: int #: docstring
attr2: str


class Derived(Base):
attr3: int #: docstring
attr4: str
67 changes: 67 additions & 0 deletions tests/test_ext_autodoc_autoclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,73 @@ def test_inherited_instance_variable(app):
]


@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_uninitialized_attributes(app):
options = {"members": None,
"inherited-members": True}
actual = do_autodoc(app, 'class', 'target.uninitialized_attributes.Derived', options)
assert list(actual) == [
'',
'.. py:class:: Derived()',
' :module: target.uninitialized_attributes',
'',
'',
' .. py:attribute:: Derived.attr1',
' :module: target.uninitialized_attributes',
' :type: int',
'',
' docstring',
'',
'',
' .. py:attribute:: Derived.attr3',
' :module: target.uninitialized_attributes',
' :type: int',
'',
' docstring',
'',
]


@pytest.mark.skipif(sys.version_info < (3, 6), reason='py36+ is available since python3.6.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_undocumented_uninitialized_attributes(app):
options = {"members": None,
"inherited-members": True,
"undoc-members": True}
actual = do_autodoc(app, 'class', 'target.uninitialized_attributes.Derived', options)
assert list(actual) == [
'',
'.. py:class:: Derived()',
' :module: target.uninitialized_attributes',
'',
'',
' .. py:attribute:: Derived.attr1',
' :module: target.uninitialized_attributes',
' :type: int',
'',
' docstring',
'',
'',
' .. py:attribute:: Derived.attr2',
' :module: target.uninitialized_attributes',
' :type: str',
'',
'',
' .. py:attribute:: Derived.attr3',
' :module: target.uninitialized_attributes',
' :type: int',
'',
' docstring',
'',
'',
' .. py:attribute:: Derived.attr4',
' :module: target.uninitialized_attributes',
' :type: str',
'',
]


def test_decorators(app):
actual = do_autodoc(app, 'class', 'target.decorator.Baz')
assert list(actual) == [
Expand Down

0 comments on commit ce7be36

Please sign in to comment.