diff --git a/CHANGES b/CHANGES index 59cb99f64..76d03ac11 100644 --- a/CHANGES +++ b/CHANGES @@ -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 ---------- @@ -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 ---------- diff --git a/doc/usage/extensions/autodoc.rst b/doc/usage/extensions/autodoc.rst index 3bb9630bd..f6aa5947c 100644 --- a/doc/usage/extensions/autodoc.rst +++ b/doc/usage/extensions/autodoc.rst @@ -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: @@ -166,11 +166,29 @@ 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. + + For example; If your class ``Foo`` is derived from ``list`` class and + you don't want to document ``list.__len__()``, you should specify a + option ``:inherited-members: list`` to avoid special members of list + class. + + Another example; If your class Foo has ``__str__`` special method and + autodoc directive has both ``inherited-members`` and ``special-members``, + ``__str__`` will be documented as in the past, but other special method + that are not implemented in your class ``Foo``. + 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:: diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 812f270f5..a89f01d36 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -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() @@ -515,6 +523,17 @@ class Documenter: 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 @@ -545,13 +564,16 @@ class Documenter: 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 @@ -565,8 +587,11 @@ class Documenter: 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 @@ -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, @@ -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, diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index 5cad89cdf..13ccf80c6 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -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,