Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow autosummary to respect __all__ #9831

Merged
merged 5 commits into from Nov 25, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES
Expand Up @@ -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
----------
Expand Down
4 changes: 4 additions & 0 deletions doc/man/sphinx-autogen.rst
Expand Up @@ -39,6 +39,10 @@ Options

Document imported members.

.. option:: -a, --respect-module-all

Document exactly the members in a module's ``__all__`` attribute.

Example
-------

Expand Down
19 changes: 19 additions & 0 deletions doc/usage/extensions/autosummary.rst
Expand Up @@ -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``
tk0miya marked this conversation as resolved.
Show resolved Hide resolved

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
Expand Down
1 change: 1 addition & 0 deletions sphinx/ext/autosummary/__init__.py
Expand Up @@ -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}
30 changes: 25 additions & 5 deletions sphinx/ext/autosummary/generate.py
Expand Up @@ -28,7 +28,7 @@
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
Expand All @@ -46,7 +46,7 @@
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

Expand All @@ -68,6 +68,7 @@ def __init__(self, translator: NullTranslations) -> None:

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:
Expand Down Expand Up @@ -192,7 +193,7 @@ def is_skipped(self, name: str, value: Any, objtype: str) -> bool:

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:
Expand All @@ -216,11 +217,25 @@ def scan(self, imported_members: bool) -> List[str]:
# 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_module_all:
# list members that have __all__ set
members.append(name)

return members

def members_of(conf: Config, obj: Any) -> Sequence[str]:
tk0miya marked this conversation as resolved.
Show resolved Hide resolved
"""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,
Expand All @@ -245,7 +260,7 @@ def get_class_members(obj: Any) -> Dict[str, 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:
Expand Down Expand Up @@ -630,6 +645,10 @@ def get_parser() -> argparse.ArgumentParser:
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

Expand All @@ -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_module_all = not args.respect_module_all

generate_autosummary_docs(args.source_file, args.output_dir,
'.' + args.suffix,
Expand Down
12 changes: 12 additions & 0 deletions tests/roots/test-ext-autosummary/autosummary_dummy_module.py
@@ -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
Expand Down Expand Up @@ -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
49 changes: 39 additions & 10 deletions tests/test_ext_autosummary.py
Expand Up @@ -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'
Expand All @@ -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
Expand All @@ -246,9 +273,9 @@ def skip_member(app, what, name, obj, skip, options):
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'] == []
Expand All @@ -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'] == ''
Expand Down Expand Up @@ -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()

assert (' .. autosummary::\n'
' \n'
' Foo\n'
Expand All @@ -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()
Expand Down