This commit is contained in:
Takeshi KOMIYA 2020-07-16 23:59:22 +09:00
parent 03e1070888
commit 610ab926a4
3 changed files with 73 additions and 26 deletions

View File

@ -41,6 +41,7 @@ Bugs fixed
* #7935: autodoc: function signature is not shown when the function has a * #7935: autodoc: function signature is not shown when the function has a
parameter having ``inspect._empty`` as its default value parameter having ``inspect._empty`` as its default value
* #7901: autodoc: type annotations for overloaded functions are not resolved * #7901: autodoc: type annotations for overloaded functions are not resolved
* #904: autodoc: An instance attribute cause a crash of autofunction directive
* #7839: autosummary: cannot handle umlauts in function names * #7839: autosummary: cannot handle umlauts in function names
* #7865: autosummary: Failed to extract summary line when abbreviations found * #7865: autosummary: Failed to extract summary line when abbreviations found
* #7866: autosummary: Failed to extract correct summary line when docstring * #7866: autosummary: Failed to extract correct summary line when docstring

View File

@ -336,7 +336,7 @@ class Documenter:
('.' + '.'.join(self.objpath) if self.objpath else '') ('.' + '.'.join(self.objpath) if self.objpath else '')
return True return True
def import_object(self) -> bool: def import_object(self, raiseerror: bool = False) -> bool:
"""Import the object given by *self.modname* and *self.objpath* and set """Import the object given by *self.modname* and *self.objpath* and set
it as *self.object*. it as *self.object*.
@ -350,9 +350,12 @@ class Documenter:
self.module, self.parent, self.object_name, self.object = ret self.module, self.parent, self.object_name, self.object = ret
return True return True
except ImportError as exc: except ImportError as exc:
logger.warning(exc.args[0], type='autodoc', subtype='import_object') if raiseerror:
self.env.note_reread() raise
return False else:
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
self.env.note_reread()
return False
def get_real_modname(self) -> str: def get_real_modname(self) -> str:
"""Get the real module name of an object to document. """Get the real module name of an object to document.
@ -892,7 +895,7 @@ class ModuleDocumenter(Documenter):
type='autodoc') type='autodoc')
return ret return ret
def import_object(self) -> Any: def import_object(self, raiseerror: bool = False) -> bool:
def is_valid_module_all(__all__: Any) -> bool: def is_valid_module_all(__all__: Any) -> bool:
"""Check the given *__all__* is valid for a module.""" """Check the given *__all__* is valid for a module."""
if (isinstance(__all__, (list, tuple)) and if (isinstance(__all__, (list, tuple)) and
@ -901,7 +904,7 @@ class ModuleDocumenter(Documenter):
else: else:
return False return False
ret = super().import_object() ret = super().import_object(raiseerror)
if not self.options.ignore_module_all: if not self.options.ignore_module_all:
__all__ = getattr(self.object, '__all__', None) __all__ = getattr(self.object, '__all__', None)
@ -1297,8 +1300,8 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
) -> bool: ) -> bool:
return isinstance(member, type) return isinstance(member, type)
def import_object(self) -> Any: def import_object(self, raiseerror: bool = False) -> bool:
ret = super().import_object() ret = super().import_object(raiseerror)
# if the class is documented under another name, document it # if the class is documented under another name, document it
# as data/attribute # as data/attribute
if ret: if ret:
@ -1612,7 +1615,7 @@ class DataDeclarationDocumenter(DataDocumenter):
isattr and isattr and
member is INSTANCEATTR) member is INSTANCEATTR)
def import_object(self) -> bool: def import_object(self, raiseerror: bool = False) -> bool:
"""Never import anything.""" """Never import anything."""
# disguise as a data # disguise as a data
self.objtype = 'data' self.objtype = 'data'
@ -1711,8 +1714,8 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
return inspect.isroutine(member) and \ return inspect.isroutine(member) and \
not isinstance(parent, ModuleDocumenter) not isinstance(parent, ModuleDocumenter)
def import_object(self) -> Any: def import_object(self, raiseerror: bool = False) -> bool:
ret = super().import_object() ret = super().import_object(raiseerror)
if not ret: if not ret:
return ret return ret
@ -1879,15 +1882,42 @@ class AttributeDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter):
def document_members(self, all_members: bool = False) -> None: def document_members(self, all_members: bool = False) -> None:
pass pass
def import_object(self) -> Any: def isinstanceattribute(self) -> bool:
ret = super().import_object() """Check the subject is an instance attribute."""
if inspect.isenumattribute(self.object): try:
self.object = self.object.value analyzer = ModuleAnalyzer.for_module(self.modname)
if inspect.isattributedescriptor(self.object): attr_docs = analyzer.find_attr_docs()
self._datadescriptor = True if self.objpath:
else: key = ('.'.join(self.objpath[:-1]), self.objpath[-1])
# if it's not a data descriptor if key in attr_docs:
self._datadescriptor = False return True
return False
except PycodeError:
return False
def import_object(self, raiseerror: bool = False) -> bool:
try:
ret = super().import_object(raiseerror=True)
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
except ImportError as exc:
if self.isinstanceattribute():
self.object = INSTANCEATTR
self._datadescriptor = False
ret = True
elif raiseerror:
raise
else:
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
self.env.note_reread()
ret = False
return ret return ret
def get_real_modname(self) -> str: def get_real_modname(self) -> str:
@ -1994,7 +2024,7 @@ class InstanceAttributeDocumenter(AttributeDocumenter):
isattr and isattr and
member is INSTANCEATTR) member is INSTANCEATTR)
def import_object(self) -> bool: def import_object(self, raiseerror: bool = False) -> bool:
"""Never import anything.""" """Never import anything."""
# disguise as an attribute # disguise as an attribute
self.objtype = 'attribute' self.objtype = 'attribute'
@ -2025,7 +2055,7 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
"""This documents only SLOTSATTR members.""" """This documents only SLOTSATTR members."""
return member is SLOTSATTR return member is SLOTSATTR
def import_object(self) -> Any: def import_object(self, raiseerror: bool = False) -> bool:
"""Never import anything.""" """Never import anything."""
# disguise as an attribute # disguise as an attribute
self.objtype = 'attribute' self.objtype = 'attribute'
@ -2039,9 +2069,12 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
self.module, _, _, self.parent = ret self.module, _, _, self.parent = ret
return True return True
except ImportError as exc: except ImportError as exc:
logger.warning(exc.args[0], type='autodoc', subtype='import_object') if raiseerror:
self.env.note_reread() raise
return False else:
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
self.env.note_reread()
return False
def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]: def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]:
"""Decode and return lines of the docstring(s) for the object.""" """Decode and return lines of the docstring(s) for the object."""

View File

@ -1047,7 +1047,7 @@ def test_class_attributes(app):
@pytest.mark.sphinx('html', testroot='ext-autodoc') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_instance_attributes(app): def test_autoclass_instance_attributes(app):
options = {"members": None} options = {"members": None}
actual = do_autodoc(app, 'class', 'target.InstAttCls', options) actual = do_autodoc(app, 'class', 'target.InstAttCls', options)
assert list(actual) == [ 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') @pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_slots(app): def test_slots(app):
options = {"members": None, options = {"members": None,