Skip to content

Commit

Permalink
Fix sphinx-doc#904: autodoc: An instance attribute cause a crash of a…
Browse files Browse the repository at this point in the history
…utofunction

So far, autofunction only checks the existence of the class variable.
As a result, it crashes with AttributeError if an instance variable is
specified.

This adds an exisitence for instance attributes via ModuleAnalyzer
before getattr-check.
  • Loading branch information
tk0miya committed Jul 12, 2020
1 parent 6059693 commit a856448
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 8 deletions.
1 change: 1 addition & 0 deletions CHANGES
Expand Up @@ -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
Expand Down
33 changes: 26 additions & 7 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -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:
Expand Down
15 changes: 14 additions & 1 deletion tests/test_ext_autodoc.py
Expand Up @@ -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) == [
Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit a856448

Please sign in to comment.