Skip to content

Commit

Permalink
Merge pull request #9831 from Yoshanuikabundi/autosummary___all__
Browse files Browse the repository at this point in the history
Allow autosummary to respect __all__
  • Loading branch information
tk0miya committed Nov 25, 2021
2 parents 259de30 + 73b7cd5 commit 15d834e
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 14 deletions.
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``

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}
31 changes: 26 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.object, self.app.config):
try:
value = safe_getattr(self.object, name)
except AttributeError:
Expand All @@ -212,16 +213,31 @@ def scan(self, imported_members: bool) -> List[str]:
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,
Expand All @@ -245,7 +261,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(obj, app.config):
try:
members[name] = safe_getattr(obj, name)
except AttributeError:
Expand Down Expand Up @@ -630,6 +646,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 +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,
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
48 changes: 39 additions & 9 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 @@ -246,9 +274,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 +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'] == ''
Expand Down Expand Up @@ -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'
Expand All @@ -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()
Expand Down

0 comments on commit 15d834e

Please sign in to comment.