Merge pull request #8481 from tk0miya/8480_slots_attributes

Fix #8480: autoattribute could not create document for __slots__ attributes
This commit is contained in:
Takeshi KOMIYA 2020-11-24 21:53:48 +09:00 committed by GitHub
commit c941b9cb14
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 100 additions and 51 deletions

View File

@ -16,6 +16,7 @@ Deprecated
* The ``follow_wrapped`` argument of ``sphinx.util.inspect.signature()``
* ``sphinx.ext.autodoc.Documenter.get_object_members()``
* ``sphinx.ext.autodoc.DataDeclarationDocumenter``
* ``sphinx.ext.autodoc.SlotsAttributeDocumenter``
* ``sphinx.ext.autodoc.TypeVarDocumenter``
* ``sphinx.ext.autodoc.importer._getannotations()``
* ``sphinx.pycode.ModuleAnalyzer.parse()``
@ -52,6 +53,8 @@ Bugs fixed
type annotated variables
* #8443: autodoc: autoattribute directive can't create document for PEP-526
based uninitalized variables
* #8480: autodoc: autoattribute could not create document for __slots__
attributes
* #8452: autodoc: autodoc_type_aliases doesn't work when autodoc_typehints is
set to "description"
* #8460: autodoc: autodata and autoattribute directives do not display type

View File

@ -41,6 +41,11 @@ The following is a list of deprecated interfaces.
- 5.0
- ``sphinx.ext.autodoc.DataDocumenter``
* - ``sphinx.ext.autodoc.SlotsAttributeDocumenter``
- 3.4
- 5.0
- ``sphinx.ext.autodoc.AttributeDocumenter``
* - ``sphinx.ext.autodoc.TypeVarDocumenter``
- 3.4
- 5.0

View File

@ -1702,7 +1702,9 @@ class ExceptionDocumenter(ClassDocumenter):
class DataDocumenterMixinBase:
# define types of instance variables
parent = None # type: Any
object = None # type: Any
objpath = None # type: List[str]
def should_suppress_directive_header(self) -> bool:
"""Check directive header should be suppressed."""
@ -2097,7 +2099,54 @@ class SingledispatchMethodDocumenter(MethodDocumenter):
super().__init__(*args, **kwargs)
class AttributeDocumenter(NewTypeMixin, TypeVarMixin, # type: ignore
class SlotsMixin(DataDocumenterMixinBase):
"""
Mixin for AttributeDocumenter to provide the feature for supporting __slots__.
"""
def isslotsattribute(self) -> bool:
"""Check the subject is an attribute in __slots__."""
try:
__slots__ = inspect.getslots(self.parent)
if __slots__ and self.objpath[-1] in __slots__:
return True
else:
return False
except (AttributeError, ValueError):
return False
def import_object(self, raiseerror: bool = False) -> bool:
ret = super().import_object(raiseerror) # type: ignore
if self.isslotsattribute():
self.object = SLOTSATTR
return ret
def should_suppress_directive_header(self) -> bool:
if self.object is SLOTSATTR:
self._datadescriptor = True
return True
else:
return super().should_suppress_directive_header()
def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]:
if self.object is SLOTSATTR:
try:
__slots__ = inspect.getslots(self.parent)
if __slots__ and __slots__.get(self.objpath[-1]):
docstring = prepare_docstring(__slots__[self.objpath[-1]])
return [docstring]
else:
return []
except (AttributeError, ValueError) as exc:
logger.warning(__('Invalid __slots__ found on %s. Ignored.'),
(self.parent.__qualname__, exc), type='autodoc')
return []
else:
return super().get_doc(encoding, ignore) # type: ignore
class AttributeDocumenter(NewTypeMixin, SlotsMixin, TypeVarMixin, # type: ignore
DocstringStripSignatureMixin, ClassLevelDocumenter):
"""
Specialized Documenter subclass for attributes.
@ -2333,52 +2382,10 @@ class SlotsAttributeDocumenter(AttributeDocumenter):
# must be higher than AttributeDocumenter
priority = 11
@classmethod
def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any
) -> bool:
"""This documents only SLOTSATTR members."""
return member is SLOTSATTR
def import_object(self, raiseerror: bool = False) -> bool:
"""Never import anything."""
# disguise as an attribute
self.objtype = 'attribute'
self._datadescriptor = True
with mock(self.config.autodoc_mock_imports):
try:
ret = import_object(self.modname, self.objpath[:-1], 'class',
attrgetter=self.get_attr,
warningiserror=self.config.autodoc_warningiserror)
self.module, _, _, self.parent = ret
return True
except ImportError as exc:
if raiseerror:
raise
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]]:
"""Decode and return lines of the docstring(s) for the object."""
if ignore is not None:
warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated."
% self.__class__.__name__,
def __init__(self, *args: Any, **kwargs: Any) -> None:
warnings.warn("%s is deprecated." % self.__class__.__name__,
RemovedInSphinx50Warning, stacklevel=2)
name = self.objpath[-1]
try:
__slots__ = inspect.getslots(self.parent)
if __slots__ and isinstance(__slots__.get(name, None), str):
docstring = prepare_docstring(__slots__[name])
return [docstring]
else:
return []
except (AttributeError, ValueError) as exc:
logger.warning(__('Invalid __slots__ found on %s. Ignored.'),
(self.parent.__qualname__, exc), type='autodoc')
return []
super().__init__(*args, **kwargs)
class NewTypeAttributeDocumenter(AttributeDocumenter):
@ -2435,7 +2442,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_autodocumenter(AttributeDocumenter)
app.add_autodocumenter(PropertyDocumenter)
app.add_autodocumenter(InstanceAttributeDocumenter)
app.add_autodocumenter(SlotsAttributeDocumenter)
app.add_autodocumenter(NewTypeAttributeDocumenter)
app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init'))

View File

@ -91,13 +91,13 @@ def setup_documenters(app: Any) -> None:
InstanceAttributeDocumenter, MethodDocumenter,
ModuleDocumenter, NewTypeAttributeDocumenter,
NewTypeDataDocumenter, PropertyDocumenter,
SingledispatchFunctionDocumenter, SlotsAttributeDocumenter)
SingledispatchFunctionDocumenter)
documenters = [
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter,
NewTypeDataDocumenter, AttributeDocumenter, InstanceAttributeDocumenter,
DecoratorDocumenter, PropertyDocumenter, SlotsAttributeDocumenter,
GenericAliasDocumenter, SingledispatchFunctionDocumenter,
DecoratorDocumenter, PropertyDocumenter, GenericAliasDocumenter,
SingledispatchFunctionDocumenter,
] # type: List[Type[Documenter]]
for documenter in documenters:
app.registry.add_documenter(documenter.objtype, documenter)

View File

@ -72,6 +72,41 @@ def test_autoattribute_instance_variable(app):
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoattribute_slots_variable_list(app):
actual = do_autodoc(app, 'attribute', 'target.slots.Foo.attr')
assert list(actual) == [
'',
'.. py:attribute:: Foo.attr',
' :module: target.slots',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoattribute_slots_variable_dict(app):
actual = do_autodoc(app, 'attribute', 'target.slots.Bar.attr1')
assert list(actual) == [
'',
'.. py:attribute:: Bar.attr1',
' :module: target.slots',
'',
' docstring of attr1',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoattribute_slots_variable_str(app):
actual = do_autodoc(app, 'attribute', 'target.slots.Baz.attr')
assert list(actual) == [
'',
'.. py:attribute:: Baz.attr',
' :module: target.slots',
'',
]
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autoattribute_NewType(app):
actual = do_autodoc(app, 'attribute', 'target.typevar.Class.T6')