#2021: Allow autosummary to respect __all__

This commit is contained in:
Josh Mitchell 2021-11-09 13:14:39 +11:00
parent cee86909b9
commit ad0071ddb7
5 changed files with 96 additions and 15 deletions

View File

@ -201,6 +201,25 @@ also use these config values:
.. versionadded:: 2.1
.. versionchanged:: 4.3
If ``autosummary_ignore___all__`` is ``False``, this configuration value
is ignored for members listed in ``__all__``.
.. confval:: autosummary_ignore___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___all__`` to False and ``autosummary_imported_members``
to True.
.. versionadded:: 4.3
.. 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___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___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.app.config, self.object):
try:
value = safe_getattr(self.object, name)
except AttributeError:
@ -216,11 +217,25 @@ class ModuleScanner:
# 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 not self.app.config.autosummary_ignore___all__:
# list members that have __all__ set
members.append(name)
return members
def members_of(conf: Config, obj: Any) -> Sequence[str]:
"""Get the members of ``obj``, possibly ignoring the ``__all__`` module attribute
Follows the ``conf.autosummary_ignore___all__`` setting."""
if conf.autosummary_ignore___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,
@ -245,7 +260,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(app.config, obj):
try:
members[name] = safe_getattr(obj, name)
except AttributeError:
@ -630,6 +645,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-__all__', action='store_false',
dest='ignore___all__', default=True,
help=__('document exactly the members in module __all__ attribute. '
'(default: %(default)s)'))
return parser
@ -646,6 +665,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___all__ = args.ignore___all__
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___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'
@ -232,7 +260,6 @@ def test_autosummary_generate_content_for_module(app):
assert context['name'] == ''
assert context['objtype'] == 'module'
@pytest.mark.sphinx(testroot='ext-autosummary')
def test_autosummary_generate_content_for_module_skipped(app):
import autosummary_dummy_module
@ -246,9 +273,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 +292,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 +340,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()
print(module)
assert (' .. autosummary::\n'
' \n'
' Foo\n'
@ -321,6 +349,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()