From 70bb2262d6677e5bcf2c85786b5f410a3a0f68aa Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Tue, 22 Dec 2020 02:21:27 +0900 Subject: [PATCH] Fix #8567: autodoc: Instance attributes are incorrectly added to Parent class The instance attributes on subclasses are shown on the document of parent class unexpectedly because of autodoc modifies `__annotations__` in place. This fix creates a copy of `__annotations__` attribute and attach it to the subclass. --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 13 +++++++++---- tests/test_ext_autodoc.py | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index 6125b83a1d..7241391ae0 100644 --- a/CHANGES +++ b/CHANGES @@ -19,6 +19,7 @@ Bugs fixed * #8559: autodoc: AttributeError is raised when using forward-reference type annotations * #8568: autodoc: TypeError is raised on checking slots attribute +* #8567: autodoc: Instance attributes are incorrectly added to Parent class Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 4bf97cc851..05696448db 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1856,13 +1856,14 @@ def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: def update_annotations(self, parent: Any) -> None: """Update __annotations__ to support type_comment and so on.""" try: - annotations = inspect.getannotations(parent) + annotations = dict(inspect.getannotations(parent)) + parent.__annotations__ = annotations analyzer = ModuleAnalyzer.for_module(self.modname) analyzer.analyze() for (classname, attrname), annotation in analyzer.annotations.items(): if classname == '' and attrname not in annotations: - annotations[attrname] = annotation # type: ignore + annotations[attrname] = annotation except AttributeError: pass @@ -2292,7 +2293,8 @@ def isinstanceattribute(self) -> bool: def update_annotations(self, parent: Any) -> None: """Update __annotations__ to support type_comment and so on.""" try: - annotations = inspect.getannotations(parent) + annotations = dict(inspect.getannotations(parent)) + parent.__annotations__ = annotations for cls in inspect.getmro(parent): try: @@ -2303,11 +2305,14 @@ def update_annotations(self, parent: Any) -> None: analyzer.analyze() for (classname, attrname), annotation in analyzer.annotations.items(): if classname == qualname and attrname not in annotations: - annotations[attrname] = annotation # type: ignore + annotations[attrname] = annotation except (AttributeError, PycodeError): pass except AttributeError: pass + except TypeError: + # Failed to set __annotations__ (built-in, extensions, etc.) + pass def import_object(self, raiseerror: bool = False) -> bool: try: diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 392ad1a683..1fa0c1d7df 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -690,6 +690,7 @@ def test_autodoc_special_members(app): actual = do_autodoc(app, 'class', 'target.Class', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Class(arg)', + ' .. py:attribute:: Class.__annotations__', ' .. py:attribute:: Class.__dict__', ' .. py:method:: Class.__init__(arg)', ' .. py:attribute:: Class.__module__',