Skip to content

Commit

Permalink
Merge pull request #6388 from tk0miya/6325_autodoc_supports_slots
Browse files Browse the repository at this point in the history
Close #6325: autodoc: Support attributes in __slots__
  • Loading branch information
tk0miya committed May 25, 2019
2 parents 38aacdf + 277aba9 commit 6e795a0
Show file tree
Hide file tree
Showing 5 changed files with 112 additions and 1 deletion.
2 changes: 2 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ Features added
``imported-members`` option
* #4777: autodoc: Support coroutine
* #744: autodoc: Support abstractmethod
* #6325: autodoc: Support attributes in __slots__. For dict-style __slots__,
autodoc considers values as a docstring of the attribute
* #6212 autosummary: Add :confval:`autosummary_imported_members` to display
imported members on autosummary
* #6271: ``make clean`` is catastrophically broken if building into '.'
Expand Down
51 changes: 51 additions & 0 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ def identity(x):

ALL = object()
INSTANCEATTR = object()
SLOTSATTR = object()


def members_option(arg):
Expand Down Expand Up @@ -1493,6 +1494,55 @@ def add_content(self, more_content, no_docstring=False):
super().add_content(more_content, no_docstring=True)


class SlotsAttributeDocumenter(AttributeDocumenter):
"""
Specialized Documenter subclass for attributes that cannot be imported
because they are attributes in __slots__.
"""
objtype = 'slotsattribute'
directivetype = 'attribute'
member_order = 60

# must be higher than AttributeDocumenter
priority = 11

@classmethod
def can_document_member(cls, member, membername, isattr, parent):
# type: (Any, str, bool, Any) -> bool
"""This documents only SLOTSATTR members."""
return member is SLOTSATTR

def import_object(self):
# type: () -> bool
"""Never import anything."""
# disguise as an attribute
self.objtype = 'attribute'
self._datadescriptor = True

with mock(self.env.config.autodoc_mock_imports):
try:
ret = import_object(self.modname, self.objpath[:-1], 'class',
attrgetter=self.get_attr,
warningiserror=self.env.config.autodoc_warningiserror)
self.module, _, _, self.parent = ret
return True
except ImportError as exc:
logger.warning(exc.args[0], type='autodoc', subtype='import_object')
self.env.note_reread()
return False

def get_doc(self, encoding=None, ignore=1):
# type: (str, int) -> List[List[str]]
"""Decode and return lines of the docstring(s) for the object."""
name = self.objpath[-1]
__slots__ = safe_getattr(self.parent, '__slots__', [])
if isinstance(__slots__, dict) and isinstance(__slots__.get(name), str):
docstring = prepare_docstring(__slots__[name])
return [docstring]
else:
return []


def get_documenters(app):
# type: (Sphinx) -> Dict[str, Type[Documenter]]
"""Returns registered Documenter classes"""
Expand Down Expand Up @@ -1554,6 +1604,7 @@ def setup(app):
app.add_autodocumenter(AttributeDocumenter)
app.add_autodocumenter(PropertyDocumenter)
app.add_autodocumenter(InstanceAttributeDocumenter)
app.add_autodocumenter(SlotsAttributeDocumenter)

app.add_config_value('autoclass_content', 'class', True)
app.add_config_value('autodoc_member_order', 'alphabetic', True)
Expand Down
9 changes: 8 additions & 1 deletion sphinx/ext/autodoc/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from sphinx.deprecation import RemovedInSphinx40Warning, deprecated_alias
from sphinx.util import logging
from sphinx.util.inspect import isenumclass, safe_getattr
from sphinx.util.inspect import isclass, isenumclass, safe_getattr

if False:
# For type annotation
Expand Down Expand Up @@ -127,6 +127,13 @@ def get_object_members(subject, objpath, attrgetter, analyzer=None):
if name not in superclass.__dict__:
members[name] = Attribute(name, True, value)

# members in __slots__
if isclass(subject) and hasattr(subject, '__slots__'):
from sphinx.ext.autodoc import SLOTSATTR

for name in subject.__slots__:
members[name] = Attribute(name, True, SLOTSATTR)

# other members
for name in dir(subject):
try:
Expand Down
11 changes: 11 additions & 0 deletions tests/roots/test-ext-autodoc/target/slots.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Foo:
__slots__ = ['attr']


class Bar:
__slots__ = {'attr1': 'docstring of attr1',
'attr2': 'docstring of attr2',
'attr3': None}

def __init__(self):
self.attr2 = None #: docstring of instance attr2
40 changes: 40 additions & 0 deletions tests/test_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,46 @@ def test_instance_attributes(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_slots(app):
options = {"members": None,
"undoc-members": True}
actual = do_autodoc(app, 'module', 'target.slots', options)
assert list(actual) == [
'',
'.. py:module:: target.slots',
'',
'',
'.. py:class:: Bar()',
' :module: target.slots',
'',
' ',
' .. py:attribute:: Bar.attr1',
' :module: target.slots',
' ',
' docstring of attr1',
' ',
' ',
' .. py:attribute:: Bar.attr2',
' :module: target.slots',
' ',
' docstring of instance attr2',
' ',
' ',
' .. py:attribute:: Bar.attr3',
' :module: target.slots',
' ',
'',
'.. py:class:: Foo',
' :module: target.slots',
'',
' ',
' .. py:attribute:: Foo.attr',
' :module: target.slots',
' ',
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_enum_class(app):
options = {"members": None,
Expand Down

0 comments on commit 6e795a0

Please sign in to comment.