Skip to content

Commit

Permalink
Merge pull request #8042 from tk0miya/8041_ivar_on_superclass_not_shown
Browse files Browse the repository at this point in the history
Fix #8041: autodoc: An ivar on super class is not shown unexpectedly
  • Loading branch information
tk0miya committed Aug 7, 2020
2 parents 5aa774b + 88b2ec6 commit 697dff3
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 15 deletions.
2 changes: 2 additions & 0 deletions CHANGES
Expand Up @@ -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
Expand Down
17 changes: 13 additions & 4 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -18,6 +18,7 @@
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

Expand Down Expand Up @@ -1605,8 +1606,12 @@ def add_directive_header(self, sig: str) -> None:
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:
Expand Down Expand Up @@ -1971,8 +1976,12 @@ def add_directive_header(self, sig: str) -> None:
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:
Expand Down
30 changes: 26 additions & 4 deletions sphinx/ext/autodoc/importer.py
Expand Up @@ -18,6 +18,10 @@
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__)


Expand Down Expand Up @@ -158,6 +162,24 @@ def get_module_members(module: Any) -> List[Tuple[str, Any]]:
('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."""
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/roots/test-ext-autodoc/target/typed_vars.py
Expand Up @@ -28,4 +28,4 @@ def __init__(self):


class Derived(Class):
pass
attr7: int
48 changes: 42 additions & 6 deletions tests/test_ext_autodoc.py
Expand Up @@ -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',
'',
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 697dff3

Please sign in to comment.