Merge pull request #8042 from tk0miya/8041_ivar_on_superclass_not_shown

Fix #8041: autodoc: An ivar on super class is not shown unexpectedly
This commit is contained in:
Takeshi KOMIYA 2020-08-08 00:51:46 +09:00 committed by GitHub
commit 697dff31ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 15 deletions

View File

@ -71,6 +71,8 @@ Bugs fixed
when ``:inherited-members:`` option given
* #8032: autodoc: A type hint for the instance variable defined at parent class
is not shown in the document of the derived class
* #8041: autodoc: An annotated instance variable on super class is not
documented when derived class has other annotated instance variables
* #7839: autosummary: cannot handle umlauts in function names
* #7865: autosummary: Failed to extract summary line when abbreviations found
* #7866: autosummary: Failed to extract correct summary line when docstring

View File

@ -18,6 +18,7 @@ from types import ModuleType
from typing import (
Any, Callable, Dict, Iterator, List, Optional, Sequence, Set, Tuple, Type, TypeVar, Union
)
from typing import get_type_hints
from docutils.statemachine import StringList
@ -1605,8 +1606,12 @@ class DataDocumenter(ModuleLevelDocumenter):
sourcename = self.get_sourcename()
if not self.options.annotation:
# obtain annotation for this data
annotations = getattr(self.parent, '__annotations__', {})
if annotations and self.objpath[-1] in annotations:
try:
annotations = get_type_hints(self.parent)
except TypeError:
annotations = {}
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
else:
@ -1971,8 +1976,12 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
sourcename = self.get_sourcename()
if not self.options.annotation:
# obtain type annotation for this attribute
annotations = getattr(self.parent, '__annotations__', {})
if annotations and self.objpath[-1] in annotations:
try:
annotations = get_type_hints(self.parent)
except TypeError:
annotations = {}
if self.objpath[-1] in annotations:
objrepr = stringify_typehint(annotations.get(self.objpath[-1]))
self.add_line(' :type: ' + objrepr, sourcename)
else:

View File

@ -18,6 +18,10 @@ from sphinx.pycode import ModuleAnalyzer
from sphinx.util import logging
from sphinx.util.inspect import isclass, isenumclass, safe_getattr
if False:
# For type annotation
from typing import Type # NOQA
logger = logging.getLogger(__name__)
@ -158,6 +162,24 @@ Attribute = NamedTuple('Attribute', [('name', str),
('value', Any)])
def _getmro(obj: Any) -> Tuple["Type", ...]:
"""Get __mro__ from given *obj* safely."""
__mro__ = safe_getattr(obj, '__mro__', None)
if isinstance(__mro__, tuple):
return __mro__
else:
return tuple()
def _getannotations(obj: Any) -> Mapping[str, Any]:
"""Get __annotations__ from given *obj* safely."""
__annotations__ = safe_getattr(obj, '__annotations__', None)
if isinstance(__annotations__, Mapping):
return __annotations__
else:
return {}
def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
analyzer: ModuleAnalyzer = None) -> Dict[str, Attribute]:
"""Get members and attributes of target object."""
@ -199,11 +221,11 @@ def get_object_members(subject: Any, objpath: List[str], attrgetter: Callable,
continue
# annotation only member (ex. attr: int)
if hasattr(subject, '__annotations__') and isinstance(subject.__annotations__, Mapping):
for name in subject.__annotations__:
name = unmangle(subject, name)
for i, cls in enumerate(_getmro(subject)):
for name in _getannotations(cls):
name = unmangle(cls, name)
if name and name not in members:
members[name] = Attribute(name, True, INSTANCEATTR)
members[name] = Attribute(name, i == 0, INSTANCEATTR)
if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows

View File

@ -28,4 +28,4 @@ class Class:
class Derived(Class):
pass
attr7: int

View File

@ -1580,12 +1580,7 @@ def test_autodoc_typed_instance_variables(app):
' :module: target.typed_vars',
'',
'',
' .. py:attribute:: Derived.attr2',
' :module: target.typed_vars',
' :type: int',
'',
'',
' .. py:attribute:: Derived.descr4',
' .. py:attribute:: Derived.attr7',
' :module: target.typed_vars',
' :type: int',
'',
@ -1615,6 +1610,47 @@ def test_autodoc_typed_instance_variables(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_autodoc_typed_inherited_instance_variables(app):
options = {"members": None,
"undoc-members": True,
"inherited-members": True}
actual = do_autodoc(app, 'class', 'target.typed_vars.Derived', options)
assert list(actual) == [
'',
'.. py:class:: Derived()',
' :module: target.typed_vars',
'',
'',
' .. py:attribute:: Derived.attr1',
' :module: target.typed_vars',
' :type: int',
' :value: 0',
'',
'',
' .. py:attribute:: Derived.attr2',
' :module: target.typed_vars',
' :type: int',
'',
'',
' .. py:attribute:: Derived.attr3',
' :module: target.typed_vars',
' :value: 0',
'',
'',
' .. py:attribute:: Derived.attr7',
' :module: target.typed_vars',
' :type: int',
'',
'',
' .. py:attribute:: Derived.descr4',
' :module: target.typed_vars',
' :type: int',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_GenericAlias(app):
options = {"members": None,