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

Autosummary: Extend __all__ members to template rendering #10811

Merged
merged 7 commits into from Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions CHANGES
Expand Up @@ -27,6 +27,9 @@ Deprecated
Features added
--------------

* #10811: Autosummary: extend ``__all__`` to imported members for template rendering
when option ``autosummary_ignore_module_all`` is set to ``False``. Patch by
Clement Pinard
* #11147: Add a ``content_offset`` parameter to ``nested_parse_with_titles()``,
allowing for correct line numbers during nested parsing.
Patch by Jeremy Maitin-Shepard
Expand Down
52 changes: 49 additions & 3 deletions sphinx/ext/autosummary/generate.py
Expand Up @@ -300,9 +300,17 @@ def get_module_attrs(members: Any) -> tuple[list[str], list[str]]:
pass # give up if ModuleAnalyzer fails to parse code
return public, attrs

def get_modules(obj: Any) -> tuple[list[str], list[str]]:
def get_modules(
obj: Any,
skip: Sequence[str],
public_members: Sequence[str] | None = None) -> tuple[list[str], list[str]]:
items: list[str] = []
public: list[str] = []
for _, modname, _ispkg in pkgutil.iter_modules(obj.__path__):

if modname in skip:
# module was overwritten in __init__.py, so not accessible
continue
fullname = name + '.' + modname
try:
module = import_module(fullname)
Expand All @@ -312,7 +320,12 @@ def get_modules(obj: Any) -> tuple[list[str], list[str]]:
pass

items.append(fullname)
public = [x for x in items if not x.split('.')[-1].startswith('_')]
if public_members is not None:
if modname in public_members:
public.append(fullname)
else:
if not modname.startswith('_'):
public.append(fullname)
return public, items

ns: dict[str, Any] = {}
Expand All @@ -321,6 +334,10 @@ def get_modules(obj: Any) -> tuple[list[str], list[str]]:
if doc.objtype == 'module':
scanner = ModuleScanner(app, obj)
ns['members'] = scanner.scan(imported_members)

respect_module_all = not app.config.autosummary_ignore_module_all
imported_members = imported_members or ('__all__' in dir(obj) and respect_module_all)

ns['functions'], ns['all_functions'] = \
get_members(obj, {'function'}, imported=imported_members)
ns['classes'], ns['all_classes'] = \
Expand All @@ -331,7 +348,36 @@ def get_modules(obj: Any) -> tuple[list[str], list[str]]:
get_module_attrs(ns['members'])
ispackage = hasattr(obj, '__path__')
if ispackage and recursive:
ns['modules'], ns['all_modules'] = get_modules(obj)
# Use members that are not modules as skip list, because it would then mean
# that module was overwritten in the package namespace
skip = (
ns["all_functions"]
+ ns["all_classes"]
+ ns["all_exceptions"]
+ ns["all_attributes"]
)

# If respect_module_all and module has a __all__ attribute, first get
# modules that were explicitly imported. Next, find the rest with the
# get_modules method, but only put in "public" modules that are in the
# __all__ list
#
# Otherwise, use get_modules method normally
if respect_module_all and '__all__' in dir(obj):
imported_modules, all_imported_modules = \
get_members(obj, {'module'}, imported=True)
skip += all_imported_modules
imported_modules = [name + '.' + modname for modname in imported_modules]
all_imported_modules = \
[name + '.' + modname for modname in all_imported_modules]
public_members = getall(obj)
else:
imported_modules, all_imported_modules = [], []
public_members = None

modules, all_modules = get_modules(obj, skip=skip, public_members=public_members)
ns['modules'] = imported_modules + modules
ns["all_modules"] = all_imported_modules + all_modules
elif doc.objtype == 'class':
ns['members'] = dir(obj)
ns['inherited_members'] = \
Expand Down
4 changes: 2 additions & 2 deletions tests/roots/test-ext-autosummary-imported_members/index.rst
@@ -1,5 +1,5 @@
test-ext-autosummary-mock_imports
=================================
test-ext-autosummary-imported_members
=====================================

.. autosummary::
:toctree: generated
Expand Down
@@ -0,0 +1,13 @@
from .autosummary_dummy_module import Bar, PublicBar, foo, public_foo


def baz():
"""Baz function"""
pass


def public_baz():
"""Public Baz function"""


__all__ = ["PublicBar", "public_foo", "public_baz", "extra_dummy_module"]
@@ -0,0 +1,20 @@
class Bar:
"""Bar class"""

pass


class PublicBar:
"""Public Bar class"""

pass


def foo():
"""Foo function"""
pass


def public_foo():
"""Public Foo function"""
pass
@@ -0,0 +1,20 @@
class Bar:
"""Bar class"""

pass


class PublicBar:
"""Public Bar class"""

pass


def foo():
"""Foo function"""
pass


def public_foo():
"""Public Foo function"""
pass
8 changes: 8 additions & 0 deletions tests/roots/test-ext-autosummary-module_all/conf.py
@@ -0,0 +1,8 @@
import os
import sys

sys.path.insert(0, os.path.abspath('.'))

extensions = ['sphinx.ext.autosummary']
autosummary_generate = True
autosummary_ignore_module_all = False
8 changes: 8 additions & 0 deletions tests/roots/test-ext-autosummary-module_all/index.rst
@@ -0,0 +1,8 @@
test-ext-autosummary-module_all
===============================

.. autosummary::
:toctree: generated
:recursive:

autosummary_dummy_package_all
24 changes: 24 additions & 0 deletions tests/test_ext_autosummary.py
Expand Up @@ -545,6 +545,30 @@ def test_autosummary_imported_members(app, status, warning):
sys.modules.pop('autosummary_dummy_package', None)


@pytest.mark.sphinx('dummy', testroot='ext-autosummary-module_all')
def test_autosummary_module_all(app, status, warning):
try:
app.build()
# generated/foo is generated successfully
assert app.env.get_doctree('generated/autosummary_dummy_package_all')
module = (app.srcdir / 'generated' / 'autosummary_dummy_package_all.rst').read_text(encoding='utf8')
assert (' .. autosummary::\n'
' \n'
' PublicBar\n'
' \n' in module)
assert (' .. autosummary::\n'
' \n'
' public_foo\n'
' public_baz\n'
' \n' in module)
assert ('.. autosummary::\n'
' :toctree:\n'
' :recursive:\n\n'
' autosummary_dummy_package_all.extra_dummy_module\n\n' in module)
finally:
sys.modules.pop('autosummary_dummy_package_all', None)


@pytest.mark.sphinx(testroot='ext-autodoc',
confoverrides={'extensions': ['sphinx.ext.autosummary']})
def test_generate_autosummary_docs_property(app):
Expand Down