Merge pull request #9831 from Yoshanuikabundi/autosummary___all__

Allow autosummary to respect __all__
This commit is contained in:
Takeshi KOMIYA 2021-11-26 02:15:43 +09:00 committed by GitHub
commit 15d834e4d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 14 deletions

View File

@ -15,6 +15,10 @@ Features added
* #9815: html theme: Wrap sidebar components in div to allow customizing their
layout via CSS
* #9831: Autosummary now documents only the members specified in a module's
``__all__`` attribute if :confval:`autosummary_ignore_module_all` is set to
``False``. The default behaviour is unchanged. Autogen also now supports
this behavior with the ``--respect-module-all`` switch.
Bugs fixed
----------

View File

@ -39,6 +39,10 @@ Options
Document imported members.
.. option:: -a, --respect-module-all
Document exactly the members in a module's ``__all__`` attribute.
Example
-------

View File

@ -201,6 +201,25 @@ also use these config values:
.. versionadded:: 2.1
.. versionchanged:: 4.4
If ``autosummary_ignore_module_all`` is ``False``, this configuration
value is ignored for members listed in ``__all__``.
.. confval:: autosummary_ignore_module_all
If ``False`` and a module has the ``__all__`` attribute set, autosummary
documents every member listed in ``__all__`` and no others. Default is
``True``
Note that if an imported member is listed in ``__all__``, it will be
documented regardless of the value of ``autosummary_imported_members``. To
match the behaviour of ``from module import *``, set
``autosummary_ignore_module_all`` to False and
``autosummary_imported_members`` to True.
.. versionadded:: 4.4
.. confval:: autosummary_filename_map
A dict mapping object names to filenames. This is necessary to avoid

View File

@ -826,5 +826,6 @@ def setup(app: Sphinx) -> Dict[str, Any]:
app.add_config_value('autosummary_mock_imports',
lambda config: config.autodoc_mock_imports, 'env')
app.add_config_value('autosummary_imported_members', [], False, [bool])
app.add_config_value('autosummary_ignore_module_all', True, 'env', bool)
return {'version': sphinx.__display_version__, 'parallel_read_safe': True}

View File

@ -28,7 +28,7 @@ import sys
import warnings
from gettext import NullTranslations
from os import path
from typing import Any, Dict, List, NamedTuple, Set, Tuple, Type, Union
from typing import Any, Dict, List, NamedTuple, Sequence, Set, Tuple, Type, Union
from jinja2 import TemplateNotFound
from jinja2.sandbox import SandboxedEnvironment
@ -46,7 +46,7 @@ from sphinx.locale import __
from sphinx.pycode import ModuleAnalyzer, PycodeError
from sphinx.registry import SphinxComponentRegistry
from sphinx.util import logging, rst, split_full_qualified_name
from sphinx.util.inspect import safe_getattr
from sphinx.util.inspect import getall, safe_getattr
from sphinx.util.osutil import ensuredir
from sphinx.util.template import SphinxTemplateLoader
@ -68,6 +68,7 @@ class DummyApplication:
self.config.add('autosummary_context', {}, True, None)
self.config.add('autosummary_filename_map', {}, True, None)
self.config.add('autosummary_ignore_module_all', True, 'env', bool)
self.config.init_values()
def emit_firstresult(self, *args: Any) -> None:
@ -192,7 +193,7 @@ class ModuleScanner:
def scan(self, imported_members: bool) -> List[str]:
members = []
for name in dir(self.object):
for name in members_of(self.object, self.app.config):
try:
value = safe_getattr(self.object, name)
except AttributeError:
@ -212,16 +213,31 @@ class ModuleScanner:
except AttributeError:
imported = False
respect_module_all = not self.app.config.autosummary_ignore_module_all
if imported_members:
# list all members up
members.append(name)
elif imported is False:
# list not-imported members up
# list not-imported members
members.append(name)
elif '__all__' in dir(self.object) and respect_module_all:
# list members that have __all__ set
members.append(name)
return members
def members_of(obj: Any, conf: Config) -> Sequence[str]:
"""Get the members of ``obj``, possibly ignoring the ``__all__`` module attribute
Follows the ``conf.autosummary_ignore_module_all`` setting."""
if conf.autosummary_ignore_module_all:
return dir(obj)
else:
return getall(obj) or dir(obj)
def generate_autosummary_content(name: str, obj: Any, parent: Any,
template: AutosummaryRenderer, template_name: str,
imported_members: bool, app: Any,
@ -245,7 +261,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
def get_module_members(obj: Any) -> Dict[str, Any]:
members = {}
for name in dir(obj):
for name in members_of(obj, app.config):
try:
members[name] = safe_getattr(obj, name)
except AttributeError:
@ -630,6 +646,10 @@ The format of the autosummary directive is documented in the
dest='imported_members', default=False,
help=__('document imported members (default: '
'%(default)s)'))
parser.add_argument('-a', '--respect-module-all', action='store_true',
dest='respect_module_all', default=False,
help=__('document exactly the members in module __all__ attribute. '
'(default: %(default)s)'))
return parser
@ -646,6 +666,7 @@ def main(argv: List[str] = sys.argv[1:]) -> None:
if args.templates:
app.config.templates_path.append(path.abspath(args.templates))
app.config.autosummary_ignore_module_all = not args.respect_module_all # type: ignore
generate_autosummary_docs(args.source_file, args.output_dir,
'.' + args.suffix,

View File

@ -1,6 +1,16 @@
from os import path # NOQA
from typing import Union
__all__ = [
"CONSTANT1",
"Exc",
"Foo",
"_Baz",
"bar",
"qux",
"path",
]
#: module variable
CONSTANT1 = None
CONSTANT2 = None
@ -48,3 +58,5 @@ class _Exc(Exception):
#: a module-level attribute
qux = 2
#: a module-level attribute that has been excluded from __all__
quuz = 2

View File

@ -216,14 +216,42 @@ def test_autosummary_generate_content_for_module(app):
context = template.render.call_args[0][1]
assert context['members'] == ['CONSTANT1', 'CONSTANT2', 'Exc', 'Foo', '_Baz', '_Exc',
'__builtins__', '__cached__', '__doc__', '__file__',
'__name__', '__package__', '_quux', 'bar', 'qux']
'__all__', '__builtins__', '__cached__', '__doc__',
'__file__', '__name__', '__package__', '_quux', 'bar',
'quuz', 'qux']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['_quux', 'bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc', '_Exc']
assert context['attributes'] == ['CONSTANT1', 'qux', 'quuz']
assert context['all_attributes'] == ['CONSTANT1', 'qux', 'quuz']
assert context['fullname'] == 'autosummary_dummy_module'
assert context['module'] == 'autosummary_dummy_module'
assert context['objname'] == ''
assert context['name'] == ''
assert context['objtype'] == 'module'
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module___all__(app):
import autosummary_dummy_module
template = Mock()
app.config.autosummary_ignore_module_all = False
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, False, app, False, {})
assert template.render.call_args[0][0] == 'module'
context = template.render.call_args[0][1]
assert context['members'] == ['CONSTANT1', 'Exc', 'Foo', '_Baz', 'bar', 'qux', 'path']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc']
assert context['attributes'] == ['CONSTANT1', 'qux']
assert context['all_attributes'] == ['CONSTANT1', 'qux']
assert context['fullname'] == 'autosummary_dummy_module'
@ -246,9 +274,9 @@ def test_autosummary_generate_content_for_module_skipped(app):
generate_autosummary_content('autosummary_dummy_module', autosummary_dummy_module, None,
template, None, False, app, False, {})
context = template.render.call_args[0][1]
assert context['members'] == ['CONSTANT1', 'CONSTANT2', '_Baz', '_Exc', '__builtins__',
'__cached__', '__doc__', '__file__', '__name__',
'__package__', '_quux', 'qux']
assert context['members'] == ['CONSTANT1', 'CONSTANT2', '_Baz', '_Exc', '__all__',
'__builtins__', '__cached__', '__doc__', '__file__',
'__name__', '__package__', '_quux', 'quuz', 'qux']
assert context['functions'] == []
assert context['classes'] == []
assert context['exceptions'] == []
@ -265,17 +293,17 @@ def test_autosummary_generate_content_for_module_imported_members(app):
context = template.render.call_args[0][1]
assert context['members'] == ['CONSTANT1', 'CONSTANT2', 'Exc', 'Foo', 'Union', '_Baz',
'_Exc', '__builtins__', '__cached__', '__doc__',
'_Exc', '__all__', '__builtins__', '__cached__', '__doc__',
'__file__', '__loader__', '__name__', '__package__',
'__spec__', '_quux', 'bar', 'path', 'qux']
'__spec__', '_quux', 'bar', 'path', 'quuz', 'qux']
assert context['functions'] == ['bar']
assert context['all_functions'] == ['_quux', 'bar']
assert context['classes'] == ['Foo']
assert context['all_classes'] == ['Foo', '_Baz']
assert context['exceptions'] == ['Exc']
assert context['all_exceptions'] == ['Exc', '_Exc']
assert context['attributes'] == ['CONSTANT1', 'qux']
assert context['all_attributes'] == ['CONSTANT1', 'qux']
assert context['attributes'] == ['CONSTANT1', 'qux', 'quuz']
assert context['all_attributes'] == ['CONSTANT1', 'qux', 'quuz']
assert context['fullname'] == 'autosummary_dummy_module'
assert context['module'] == 'autosummary_dummy_module'
assert context['objname'] == ''
@ -313,6 +341,7 @@ def test_autosummary_generate(app, status, warning):
assert doctree[3][0][0][2][5].astext() == 'autosummary_dummy_module.qux\n\na module-level attribute'
module = (app.srcdir / 'generated' / 'autosummary_dummy_module.rst').read_text()
assert (' .. autosummary::\n'
' \n'
' Foo\n'
@ -321,6 +350,7 @@ def test_autosummary_generate(app, status, warning):
' \n'
' CONSTANT1\n'
' qux\n'
' quuz\n'
' \n' in module)
Foo = (app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.rst').read_text()