diff --git a/CHANGES b/CHANGES index f42d1431b08..d2208af1fd2 100644 --- a/CHANGES +++ b/CHANGES @@ -27,6 +27,7 @@ Bugs fixed ---------- * #7886: autodoc: TypeError is raised on mocking generic-typed classes +* #904: autodoc: An instance attribute cause a crash of autofunction directive * #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 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 9300a2ccea7..16ef0e5afb1 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -1869,15 +1869,34 @@ def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: def document_members(self, all_members: bool = False) -> None: pass + def isinstanceattribute(self) -> bool: + """Check the subject is an instance attribute.""" + try: + analyzer = ModuleAnalyzer.for_module(self.modname) + attr_docs = analyzer.find_attr_docs() + if self.objpath: + key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) + if key in attr_docs: + return True + + return False + except PycodeError: + return False + def import_object(self) -> Any: - ret = super().import_object() - if inspect.isenumattribute(self.object): - self.object = self.object.value - if inspect.isattributedescriptor(self.object): - self._datadescriptor = True - else: - # if it's not a data descriptor + if self.isinstanceattribute(): + self.object = INSTANCEATTR self._datadescriptor = False + ret = True + else: + ret = super().import_object() + if inspect.isenumattribute(self.object): + self.object = self.object.value + if inspect.isattributedescriptor(self.object): + self._datadescriptor = True + else: + # if it's not a data descriptor + self._datadescriptor = False return ret def get_real_modname(self) -> str: diff --git a/tests/test_ext_autodoc.py b/tests/test_ext_autodoc.py index 7b4823a2f8c..d3524ac9dee 100644 --- a/tests/test_ext_autodoc.py +++ b/tests/test_ext_autodoc.py @@ -1047,7 +1047,7 @@ def test_class_attributes(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') -def test_instance_attributes(app): +def test_autoclass_instance_attributes(app): options = {"members": None} actual = do_autodoc(app, 'class', 'target.InstAttCls', options) assert list(actual) == [ @@ -1120,6 +1120,19 @@ def test_instance_attributes(app): ] +@pytest.mark.sphinx('html', testroot='ext-autodoc') +def test_autoattribute_instance_attributes(app): + actual = do_autodoc(app, 'attribute', 'target.InstAttCls.ia1') + assert list(actual) == [ + '', + '.. py:attribute:: InstAttCls.ia1', + ' :module: target', + '', + ' Doc comment for instance attribute InstAttCls.ia1', + '' + ] + + @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_slots(app): options = {"members": None,