From 06aa1a78647bd8794eb37d7c62934feaec33db3c Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Sun, 2 Sep 2018 22:30:51 +0900 Subject: [PATCH] Fix #2401: autodoc: ``:members:`` causes ``:special-members:`` not to be shown --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 24 +++++++++++++ sphinx/ext/autosummary/generate.py | 5 +-- tests/py35/test_autodoc_py35.py | 4 +-- tests/test_autodoc.py | 56 +++++++++++++----------------- 5 files changed, 55 insertions(+), 35 deletions(-) diff --git a/CHANGES b/CHANGES index 80d657998..c0f972ffe 100644 --- a/CHANGES +++ b/CHANGES @@ -30,6 +30,7 @@ Bugs fixed * #5348: download reference to remote file is not displayed * #5282: html theme: ``pygments_style`` of theme was overrided by ``conf.py`` by default +* #2401: autodoc: ``:members:`` causes ``:special-members:`` not to be shown Testing -------- diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 41a6731d2..ead3a25fd 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -110,6 +110,20 @@ def bool_option(arg): return True +def merge_special_members_option(options): + # type: (Dict) -> None + """Merge :special-members: option to :members: option.""" + if 'special-members' in options and options['special-members'] is not ALL: + if options.get('members') is ALL: + pass + elif options.get('members'): + for member in options['special-members']: + if member not in options['members']: + options['members'].append(member) + else: + options['members'] = options['special-members'] + + class AutodocReporter(object): """ A reporter replacement that assigns the correct source name @@ -823,6 +837,11 @@ class ModuleDocumenter(Documenter): 'imported-members': bool_option, 'ignore-module-all': bool_option } # type: Dict[unicode, Callable] + def __init__(self, *args): + # type: (Any) -> None + super(ModuleDocumenter, self).__init__(*args) + merge_special_members_option(self.options) + @classmethod def can_document_member(cls, member, membername, isattr, parent): # type: (Any, unicode, bool, Any) -> bool @@ -1075,6 +1094,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: 'private-members': bool_option, 'special-members': members_option, } # type: Dict[unicode, Callable] + def __init__(self, *args): + # type: (Any) -> None + super(ClassDocumenter, self).__init__(*args) + merge_special_members_option(self.options) + @classmethod def can_document_member(cls, member, membername, isattr, parent): # type: (Any, unicode, bool, Any) -> bool diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index c45576c3a..dbadfe8d5 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -43,11 +43,12 @@ from sphinx.util.rst import escape as rst_escape if False: # For type annotation - from typing import Any, Callable, Dict, Tuple, List # NOQA + from typing import Any, Callable, Dict, List, Tuple, Type # NOQA from jinja2 import BaseLoader # NOQA from sphinx import addnodes # NOQA from sphinx.builders import Builder # NOQA from sphinx.environment import BuildEnvironment # NOQA + from sphinx.ext.autodoc import Documenter # NOQA class DummyApplication(object): @@ -69,7 +70,7 @@ def setup_documenters(app): ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, FunctionDocumenter, MethodDocumenter, AttributeDocumenter, InstanceAttributeDocumenter - ] + ] # type: List[Type[Documenter]] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) diff --git a/tests/py35/test_autodoc_py35.py b/tests/py35/test_autodoc_py35.py index 51a9ef1ff..23b3eb83e 100644 --- a/tests/py35/test_autodoc_py35.py +++ b/tests/py35/test_autodoc_py35.py @@ -18,7 +18,7 @@ import six from docutils.statemachine import ViewList from six import StringIO -from sphinx.ext.autodoc import add_documenter, FunctionDocumenter, ALL # NOQA +from sphinx.ext.autodoc import add_documenter, FunctionDocumenter, ALL, Options # NOQA from sphinx.testing.util import SphinxTestApp, Struct from sphinx.util import logging @@ -49,7 +49,7 @@ def setup_test(): global options, directive global processed_docstrings, processed_signatures - options = Struct( + options = Options( inherited_members = False, undoc_members = False, private_members = False, diff --git a/tests/test_autodoc.py b/tests/test_autodoc.py index c9a342f94..cefceb833 100644 --- a/tests/test_autodoc.py +++ b/tests/test_autodoc.py @@ -21,7 +21,7 @@ from six import PY3 from sphinx.ext.autodoc import ( AutoDirective, ModuleLevelDocumenter, cut_lines, between, ALL, - merge_autodoc_default_flags + merge_autodoc_default_flags, Options ) from sphinx.ext.autodoc.directive import DocumenterBridge, process_documenter_options from sphinx.testing.util import SphinxTestApp, Struct # NOQA @@ -79,7 +79,7 @@ def setup_test(): global options, directive global processed_docstrings, processed_signatures - options = Struct( + options = Options( inherited_members = False, undoc_members = False, private_members = False, @@ -757,6 +757,29 @@ def test_autodoc_imported_members(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_special_members(app): + # specific special methods + options = {"undoc-members": None, + "special-members": "__init__,__special1__"} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ] + + # combination with specific members + options = {"members": "attr,docattr", + "undoc-members": None, + "special-members": "__init__,__special1__"} + actual = do_autodoc(app, 'class', 'target.Class', options) + assert list(filter(lambda l: '::' in l, actual)) == [ + '.. py:class:: Class(arg)', + ' .. py:method:: Class.__init__(arg)', + ' .. py:method:: Class.__special1__()', + ' .. py:attribute:: Class.attr', + ' .. py:attribute:: Class.docattr', + ] + # all special methods options = {"members": None, "undoc-members": None, @@ -786,33 +809,6 @@ def test_autodoc_special_members(app): ' .. py:method:: Class.undocmeth()' ] - # specific special methods - options = {"members": None, - "undoc-members": None, - "special-members": "__init__,__special1__"} - actual = do_autodoc(app, 'class', 'target.Class', options) - assert list(filter(lambda l: '::' in l, actual)) == [ - '.. py:class:: Class(arg)', - ' .. py:method:: Class.__init__(arg)', - ' .. py:method:: Class.__special1__()', - ' .. py:attribute:: Class.attr', - ' .. py:attribute:: Class.descr', - ' .. py:attribute:: Class.docattr', - ' .. py:method:: Class.excludemeth()', - ' .. py:attribute:: Class.inst_attr_comment', - ' .. py:attribute:: Class.inst_attr_inline', - ' .. py:attribute:: Class.inst_attr_string', - ' .. py:attribute:: Class.mdocattr', - ' .. py:method:: Class.meth()', - ' .. py:classmethod:: Class.moore(a, e, f) -> happiness', - ' .. py:attribute:: Class.prop', - ROGER_METHOD, - ' .. py:attribute:: Class.skipattr', - ' .. py:method:: Class.skipmeth()', - ' .. py:attribute:: Class.udocattr', - ' .. py:method:: Class.undocmeth()' - ] - @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_ignore_module_all(app): @@ -1551,9 +1547,7 @@ def test_autodoc_default_options_with_values(app): assert ' .. py:attribute:: EnumCls.val4' not in actual # with :special-members: - # Note that :members: must be *on* for :special-members: to work. app.config.autodoc_default_options = { - 'members': None, 'special-members': '__init__,__iter__', } actual = do_autodoc(app, 'class', 'target.CustomIter')