Skip to content

Commit

Permalink
Fix sphinx-doc#8545: autodoc: a __slots__ attribute is not documented…
Browse files Browse the repository at this point in the history
… even having docstring

To avoid filtering __slots__ attributes having docstring at
filter_members(), this passes docstring captured at get_class_members()
to the filter_members() via ObjectMember.
  • Loading branch information
tk0miya committed Dec 16, 2020
1 parent f5aa072 commit a766ffe
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 7 deletions.
1 change: 1 addition & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ Bugs fixed
information of TypeVars
* #8493: autodoc: references to builtins not working in class aliases
* #8522: autodoc: ``__bool__`` method could be called
* #8545: autodoc: a __slots__ attribute is not documented even having docstring
* #8477: autosummary: non utf-8 reST files are generated when template contains
multibyte characters
* #8501: autosummary: summary extraction splits text after "el at." unexpectedly
Expand Down
19 changes: 14 additions & 5 deletions sphinx/ext/autodoc/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,11 @@ class ObjectMember(tuple):
def __new__(cls, name: str, obj: Any, **kwargs: Any) -> Any:
return super().__new__(cls, (name, obj)) # type: ignore

def __init__(self, name: str, obj: Any, skipped: bool = False) -> None:
def __init__(self, name: str, obj: Any, docstring: Optional[str] = None,
skipped: bool = False) -> None:
self.__name__ = name
self.object = obj
self.docstring = docstring
self.skipped = skipped


Expand Down Expand Up @@ -709,6 +711,11 @@ def is_filtered_inherited_member(name: str) -> bool:
cls_doc = self.get_attr(cls, '__doc__', None)
if cls_doc == doc:
doc = None

if isinstance(obj, ObjectMember) and obj.docstring:
# hack for ClassDocumenter to inject docstring via ObjectMember
doc = obj.docstring

has_doc = bool(doc)

metadata = extract_metadata(doc)
Expand Down Expand Up @@ -1585,16 +1592,18 @@ def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]:
selected = []
for name in self.options.members: # type: str
if name in members:
selected.append((name, members[name].value))
selected.append(ObjectMember(name, members[name].value,
docstring=members[name].docstring))
else:
logger.warning(__('missing attribute %s in object %s') %
(name, self.fullname), type='autodoc')
return False, selected
elif self.options.inherited_members:
return False, [(m.name, m.value) for m in members.values()]
return False, [ObjectMember(m.name, m.value, docstring=m.docstring)
for m in members.values()]
else:
return False, [(m.name, m.value) for m in members.values()
if m.class_ == self.object]
return False, [ObjectMember(m.name, m.value, docstring=m.docstring)
for m in members.values() if m.class_ == self.object]

def get_doc(self, encoding: str = None, ignore: int = None) -> List[List[str]]:
if encoding is not None:
Expand Down
5 changes: 3 additions & 2 deletions sphinx/ext/autodoc/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -312,9 +312,10 @@ def get_class_members(subject: Any, objpath: List[str], attrgetter: Callable,
if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows
namespace = '.'.join(objpath)
for (ns, name) in analyzer.find_attr_docs():
for (ns, name), docstring in analyzer.attr_docs.items():
if namespace == ns and name not in members:
members[name] = ClassAttribute(subject, name, INSTANCEATTR)
members[name] = ClassAttribute(subject, name, INSTANCEATTR,
'\n'.join(docstring))

return members

Expand Down
6 changes: 6 additions & 0 deletions tests/roots/test-ext-autodoc/target/slots.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
class Foo:
"""docstring"""

__slots__ = ['attr']


class Bar:
"""docstring"""

__slots__ = {'attr1': 'docstring of attr1',
'attr2': 'docstring of attr2',
'attr3': None}
Expand All @@ -12,4 +16,6 @@ def __init__(self):


class Baz:
"""docstring"""

__slots__ = 'attr'
6 changes: 6 additions & 0 deletions tests/test_ext_autodoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,8 @@ def test_slots(app):
'.. py:class:: Bar()',
' :module: target.slots',
'',
' docstring',
'',
'',
' .. py:attribute:: Bar.attr1',
' :module: target.slots',
Expand All @@ -1192,6 +1194,8 @@ def test_slots(app):
'.. py:class:: Baz()',
' :module: target.slots',
'',
' docstring',
'',
'',
' .. py:attribute:: Baz.attr',
' :module: target.slots',
Expand All @@ -1200,6 +1204,8 @@ def test_slots(app):
'.. py:class:: Foo()',
' :module: target.slots',
'',
' docstring',
'',
'',
' .. py:attribute:: Foo.attr',
' :module: target.slots',
Expand Down
26 changes: 26 additions & 0 deletions tests/test_ext_autodoc_autoclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,32 @@ def test_decorators(app):
]


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


@pytest.mark.skipif(sys.version_info < (3, 7), reason='python 3.7+ is required.')
@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_show_inheritance_for_subclass_of_generic_type(app):
Expand Down

0 comments on commit a766ffe

Please sign in to comment.