diff --git a/CHANGES b/CHANGES index c2854f2ac..4a7521d2c 100644 --- a/CHANGES +++ b/CHANGES @@ -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 diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 6763b693c..6e0960de4 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -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 diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 732b33c3b..dd4686dd0 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -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__, - 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 [] + def __init__(self, *args: Any, **kwargs: Any) -> None: + warnings.warn("%s is deprecated." % self.__class__.__name__, + RemovedInSphinx50Warning, stacklevel=2) + 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')) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 14bd1c789..77bf1f75d 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -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) diff --git a/tests/test_ext_autodoc_autoattribute.py b/tests/test_ext_autodoc_autoattribute.py index 4787530de..a0d5e5b6f 100644 --- a/tests/test_ext_autodoc_autoattribute.py +++ b/tests/test_ext_autodoc_autoattribute.py @@ -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')