Merge pull request #8548 from tk0miya/741_inherited_ivar

Fix #741: autodoc: inherited-members doesn't support instance attributes on super class
This commit is contained in:
Takeshi KOMIYA 2020-12-19 00:31:03 +09:00 committed by GitHub
commit 57ed10c680
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 95 additions and 20 deletions

View File

@ -80,6 +80,8 @@ Bugs fixed
* #8067: autodoc: A typehint for the instance variable having type_comment on * #8067: autodoc: A typehint for the instance variable having type_comment on
super class is not displayed super class is not displayed
* #8545: autodoc: a __slots__ attribute is not documented even having docstring * #8545: autodoc: a __slots__ attribute is not documented even having docstring
* #741: autodoc: inherited-members doesn't work for instance attributes on super
class
* #8477: autosummary: non utf-8 reST files are generated when template contains * #8477: autosummary: non utf-8 reST files are generated when template contains
multibyte characters multibyte characters
* #8501: autosummary: summary extraction splits text after "el at." unexpectedly * #8501: autosummary: summary extraction splits text after "el at." unexpectedly

View File

@ -1584,7 +1584,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename) self.add_line(' ' + _('Bases: %s') % ', '.join(bases), sourcename)
def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:
members = get_class_members(self.object, self.objpath, self.get_attr, self.analyzer) members = get_class_members(self.object, self.objpath, self.get_attr)
if not want_all: if not want_all:
if not self.options.members: if not self.options.members:
return False, [] # type: ignore return False, [] # type: ignore

View File

@ -14,7 +14,7 @@ import warnings
from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tuple from typing import Any, Callable, Dict, List, Mapping, NamedTuple, Optional, Tuple
from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.pycode import ModuleAnalyzer from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.util import logging from sphinx.util import logging
from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass, from sphinx.util.inspect import (getannotations, getmro, getslots, isclass, isenumclass,
safe_getattr) safe_getattr)
@ -251,8 +251,8 @@ class ClassAttribute:
self.docstring = docstring self.docstring = docstring
def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable, def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable
analyzer: ModuleAnalyzer = None) -> Dict[str, ClassAttribute]: ) -> Dict[str, ClassAttribute]:
"""Get members and attributes of target class.""" """Get members and attributes of target class."""
from sphinx.ext.autodoc import INSTANCEATTR from sphinx.ext.autodoc import INSTANCEATTR
@ -297,8 +297,9 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable,
except AttributeError: except AttributeError:
continue continue
# annotation only member (ex. attr: int) try:
for cls in getmro(subject): for cls in getmro(subject):
# annotation only member (ex. attr: int)
try: try:
for name in getannotations(cls): for name in getannotations(cls):
name = unmangle(cls, name) name = unmangle(cls, name)
@ -307,13 +308,20 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable,
except AttributeError: except AttributeError:
pass pass
if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows # append instance attributes (cf. self.attr1) if analyzer knows
namespace = '.'.join(objpath) try:
modname = safe_getattr(cls, '__module__')
qualname = safe_getattr(cls, '__qualname__')
analyzer = ModuleAnalyzer.for_module(modname)
analyzer.analyze()
for (ns, name), docstring in analyzer.attr_docs.items(): for (ns, name), docstring in analyzer.attr_docs.items():
if namespace == ns and name not in members: if ns == qualname and name not in members:
members[name] = ClassAttribute(subject, name, INSTANCEATTR, members[name] = ClassAttribute(cls, name, INSTANCEATTR,
'\n'.join(docstring)) '\n'.join(docstring))
except (AttributeError, PycodeError):
pass
except AttributeError:
pass
return members return members

View File

@ -0,0 +1,10 @@
class Foo:
def __init__(self):
self.attr1 = None #: docstring foo
self.attr2 = None #: docstring foo
class Bar(Foo):
def __init__(self):
self.attr2 = None #: docstring bar
self.attr3 = None #: docstring bar

View File

@ -51,6 +51,61 @@ def test_classes(app):
] ]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_instance_variable(app):
options = {'members': True}
actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options)
assert list(actual) == [
'',
'.. py:class:: Bar()',
' :module: target.instance_variable',
'',
'',
' .. py:attribute:: Bar.attr2',
' :module: target.instance_variable',
'',
' docstring bar',
'',
'',
' .. py:attribute:: Bar.attr3',
' :module: target.instance_variable',
'',
' docstring bar',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_inherited_instance_variable(app):
options = {'members': True,
'inherited-members': True}
actual = do_autodoc(app, 'class', 'target.instance_variable.Bar', options)
assert list(actual) == [
'',
'.. py:class:: Bar()',
' :module: target.instance_variable',
'',
'',
' .. py:attribute:: Bar.attr1',
' :module: target.instance_variable',
'',
' docstring foo',
'',
'',
' .. py:attribute:: Bar.attr2',
' :module: target.instance_variable',
'',
' docstring bar',
'',
'',
' .. py:attribute:: Bar.attr3',
' :module: target.instance_variable',
'',
' docstring bar',
'',
]
def test_decorators(app): def test_decorators(app):
actual = do_autodoc(app, 'class', 'target.decorator.Baz') actual = do_autodoc(app, 'class', 'target.decorator.Baz')
assert list(actual) == [ assert list(actual) == [