Skip to content

Commit

Permalink
Close sphinx-doc#5923: autodoc: allow not to document inherited membe…
Browse files Browse the repository at this point in the history
…rs of specific super class
  • Loading branch information
tk0miya committed Nov 30, 2019
1 parent 8c7faed commit 5f6c571
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 12 deletions.
4 changes: 4 additions & 0 deletions CHANGES
Expand Up @@ -14,6 +14,8 @@ Incompatible changes
* Drop features and APIs deprecated in 1.8.x
* #247: autosummary: stub files are overwritten automatically by default. see
:confval:`autosummary_generate_overwrite` to change the behavior
* #5923: autodoc: the members of ``object`` class are not documented by default
when ``:inherited-members:`` and ``:special-members:`` are given.

Deprecated
----------
Expand All @@ -23,6 +25,8 @@ Features added

* #247: autosummary: Add :confval:`autosummary_generate_overwrite` to overwrite
old stub file
* #5923: autodoc: ``:inherited-members:`` option takes a name of anchestor class
not to document inherited members of the class and uppers

Bugs fixed
----------
Expand Down
10 changes: 9 additions & 1 deletion doc/usage/extensions/autodoc.rst
Expand Up @@ -157,7 +157,7 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,

* For classes and exceptions, members inherited from base classes will be
left out when documenting all members, unless you give the
``inherited-members`` flag option, in addition to ``members``::
``inherited-members`` option, in addition to ``members``::

.. autoclass:: Noodle
:members:
Expand All @@ -166,11 +166,19 @@ inserting them into the page source under a suitable :rst:dir:`py:module`,
This can be combined with ``undoc-members`` to document *all* available
members of the class or module.

It can take an anchestor class not to document inherited members from it.
By default, members of ``object`` class are not documented. To show them
all, give ``None`` to the option.

Note: this will lead to markup errors if the inherited members come from a
module whose docstrings are not reST formatted.

.. versionadded:: 0.3

.. versionchanged:: 3.0

It takes an anchestor class name as an argument.

* It's possible to override the signature for explicitly documented callable
objects (functions, methods, classes) with the regular syntax that will
override the signature gained from introspection::
Expand Down
47 changes: 36 additions & 11 deletions sphinx/ext/autodoc/__init__.py
Expand Up @@ -82,6 +82,14 @@ def members_set_option(arg: Any) -> Union[object, Set[str]]:
return {x.strip() for x in arg.split(',')}


def inherited_members_option(arg: Any) -> Union[object, Set[str]]:
"""Used to convert the :members: option to auto directives."""
if arg is None:
return 'object'
else:
return arg


SUPPRESS = object()


Expand Down Expand Up @@ -515,6 +523,17 @@ def filter_members(self, members: List[Tuple[str, Any]], want_all: bool
The user can override the skipping decision by connecting to the
``autodoc-skip-member`` event.
"""
def is_filtered_inherited_member(name: str) -> bool:
if inspect.isclass(self.object):
for cls in self.object.__mro__:
if cls.__name__ == self.options.inherited_members and cls != self.object:
# given member is a member of specified *super class*
return True
elif name in cls.__dict__:
return False

return False

ret = []

# search for members in source code too
Expand Down Expand Up @@ -545,13 +564,16 @@ def filter_members(self, members: List[Tuple[str, Any]], want_all: bool
if want_all and membername.startswith('__') and \
membername.endswith('__') and len(membername) > 4:
# special __methods__
if self.options.special_members is ALL and \
membername != '__doc__':
keep = has_doc or self.options.undoc_members
elif self.options.special_members and \
self.options.special_members is not ALL and \
membername in self.options.special_members:
keep = has_doc or self.options.undoc_members
if self.options.special_members is ALL:
if membername == '__doc__':
keep = False
elif is_filtered_inherited_member(membername):
keep = False
else:
keep = has_doc or self.options.undoc_members
elif self.options.special_members:
if membername in self.options.special_members:
keep = has_doc or self.options.undoc_members
elif (namespace, membername) in attr_docs:
if want_all and membername.startswith('_'):
# ignore members whose name starts with _ by default
Expand All @@ -565,8 +587,11 @@ def filter_members(self, members: List[Tuple[str, Any]], want_all: bool
keep = self.options.private_members and \
(has_doc or self.options.undoc_members)
else:
# ignore undocumented members if :undoc-members: is not given
keep = has_doc or self.options.undoc_members
if self.options.members is ALL and is_filtered_inherited_member(membername):
keep = False
else:
# ignore undocumented members if :undoc-members: is not given
keep = has_doc or self.options.undoc_members

# give the user a chance to decide whether this member
# should be skipped
Expand Down Expand Up @@ -740,7 +765,7 @@ class ModuleDocumenter(Documenter):

option_spec = {
'members': members_option, 'undoc-members': bool_option,
'noindex': bool_option, 'inherited-members': bool_option,
'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'synopsis': identity,
'platform': identity, 'deprecated': bool_option,
'member-order': identity, 'exclude-members': members_set_option,
Expand Down Expand Up @@ -1039,7 +1064,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
member_order = 20
option_spec = {
'members': members_option, 'undoc-members': bool_option,
'noindex': bool_option, 'inherited-members': bool_option,
'noindex': bool_option, 'inherited-members': inherited_members_option,
'show-inheritance': bool_option, 'member-order': identity,
'exclude-members': members_set_option,
'private-members': bool_option, 'special-members': members_option,
Expand Down
24 changes: 24 additions & 0 deletions tests/test_autodoc.py
Expand Up @@ -581,6 +581,30 @@ def test_autodoc_inherited_members(app):
]


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_inherited_members_Base(app):
options = {"members": None,
"inherited-members": "Base",
"special-members": None}

# check methods for object class are shown
actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
assert ' .. py:method:: Derived.inheritedmeth()' in actual
assert ' .. py:method:: Derived.inheritedclassmeth' not in actual


@pytest.mark.sphinx('html', testroot='ext-autodoc')
def test_autodoc_inherited_members_None(app):
options = {"members": None,
"inherited-members": "None",
"special-members": None}

# check methods for object class are shown
actual = do_autodoc(app, 'class', 'target.inheritance.Derived', options)
assert ' .. py:method:: Derived.__init__' in actual
assert ' .. py:method:: Derived.__str__' in actual


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

0 comments on commit 5f6c571

Please sign in to comment.