Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #8041: autodoc: An ivar on super class is not shown unexpectedly #8042

Merged
merged 1 commit into from Aug 7, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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',
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: type_comment in super class is still not supported.

'',
'',
' .. 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