[format] Add ruff formating for apidoc/autosummary (#12447)

This commit is contained in:
Chris Sewell 2024-06-20 09:37:49 +02:00 committed by GitHub
parent 13b16c9b04
commit ee92847a0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 501 additions and 251 deletions

View File

@ -448,7 +448,32 @@ exclude = [
"sphinx/directives/*",
"sphinx/domains/*",
"sphinx/environment/*",
"sphinx/ext/*",
"sphinx/ext/autodoc/__init__.py",
"sphinx/ext/autodoc/directive.py",
"sphinx/ext/autodoc/importer.py",
"sphinx/ext/autodoc/mock.py",
"sphinx/ext/autodoc/preserve_defaults.py",
"sphinx/ext/autodoc/type_comment.py",
"sphinx/ext/autodoc/typehints.py",
"sphinx/ext/autosectionlabel.py",
"sphinx/ext/autosummary/__init__.py",
"sphinx/ext/coverage.py",
"sphinx/ext/doctest.py",
"sphinx/ext/duration.py",
"sphinx/ext/extlinks.py",
"sphinx/ext/githubpages.py",
"sphinx/ext/graphviz.py",
"sphinx/ext/ifconfig.py",
"sphinx/ext/imgconverter.py",
"sphinx/ext/imgmath.py",
"sphinx/ext/inheritance_diagram.py",
"sphinx/ext/intersphinx/*",
"sphinx/ext/linkcode.py",
"sphinx/ext/mathjax.py",
"sphinx/ext/napoleon/__init__.py",
"sphinx/ext/napoleon/docstring.py",
"sphinx/ext/todo.py",
"sphinx/ext/viewcode.py",
"sphinx/pycode/*",
"sphinx/pygments_styles.py",
"sphinx/registry.py",

View File

@ -95,8 +95,9 @@ def write_file(name: str, text: str, opts: Any) -> None:
f.write(text)
def create_module_file(package: str | None, basename: str, opts: Any,
user_template_dir: str | None = None) -> None:
def create_module_file(
package: str | None, basename: str, opts: Any, user_template_dir: str | None = None
) -> None:
"""Build the text of the file and write the file."""
options = copy(OPTIONS)
if opts.includeprivate and 'private-members' not in options:
@ -117,24 +118,32 @@ def create_module_file(package: str | None, basename: str, opts: Any,
write_file(qualname, text, opts)
def create_package_file(root: str, master_package: str | None, subroot: str,
py_files: list[str],
opts: Any, subs: list[str], is_namespace: bool,
excludes: Sequence[re.Pattern[str]] = (),
user_template_dir: str | None = None,
) -> None:
def create_package_file(
root: str,
master_package: str | None,
subroot: str,
py_files: list[str],
opts: Any,
subs: list[str],
is_namespace: bool,
excludes: Sequence[re.Pattern[str]] = (),
user_template_dir: str | None = None,
) -> None:
"""Build the text of the file and write the file."""
# build a list of sub packages (directories containing an __init__ file)
subpackages = [module_join(master_package, subroot, pkgname)
for pkgname in subs
if not is_skipped_package(path.join(root, pkgname), opts, excludes)]
subpackages = [
module_join(master_package, subroot, pkgname)
for pkgname in subs
if not is_skipped_package(path.join(root, pkgname), opts, excludes)
]
# build a list of sub modules
submodules = [sub.split('.')[0] for sub in py_files
if not is_skipped_module(path.join(root, sub), opts, excludes) and
not is_initpy(sub)]
submodules = [
sub.split('.')[0]
for sub in py_files
if not is_skipped_module(path.join(root, sub), opts, excludes) and not is_initpy(sub)
]
submodules = sorted(set(submodules))
submodules = [module_join(master_package, subroot, modname)
for modname in submodules]
submodules = [module_join(master_package, subroot, modname) for modname in submodules]
options = copy(OPTIONS)
if opts.includeprivate and 'private-members' not in options:
options.append('private-members')
@ -163,8 +172,9 @@ def create_package_file(root: str, master_package: str | None, subroot: str,
create_module_file(None, submodule, opts, user_template_dir)
def create_modules_toc_file(modules: list[str], opts: Any, name: str = 'modules',
user_template_dir: str | None = None) -> None:
def create_modules_toc_file(
modules: list[str], opts: Any, name: str = 'modules', user_template_dir: str | None = None
) -> None:
"""Create the module's index."""
modules.sort()
prev_module = ''
@ -188,8 +198,9 @@ def create_modules_toc_file(modules: list[str], opts: Any, name: str = 'modules'
write_file(name, text, opts)
def is_skipped_package(dirname: str, opts: Any,
excludes: Sequence[re.Pattern[str]] = ()) -> bool:
def is_skipped_package(
dirname: str, opts: Any, excludes: Sequence[re.Pattern[str]] = ()
) -> bool:
"""Check if we want to skip this module."""
if not path.isdir(dirname):
return False
@ -213,17 +224,22 @@ def is_skipped_module(filename: str, opts: Any, _excludes: Sequence[re.Pattern[s
return path.basename(filename).startswith('_') and not opts.includeprivate
def walk(rootpath: str, excludes: Sequence[re.Pattern[str]], opts: Any,
) -> Iterator[tuple[str, list[str], list[str]]]:
def walk(
rootpath: str,
excludes: Sequence[re.Pattern[str]],
opts: Any,
) -> Iterator[tuple[str, list[str], list[str]]]:
"""Walk through the directory and list files and subdirectories up."""
followlinks = getattr(opts, 'followlinks', False)
includeprivate = getattr(opts, 'includeprivate', False)
for root, subs, files in os.walk(rootpath, followlinks=followlinks):
# document only Python module files (that aren't excluded)
files = sorted(f for f in files
if f.endswith(PY_SUFFIXES) and
not is_excluded(path.join(root, f), excludes))
files = sorted(
f
for f in files
if f.endswith(PY_SUFFIXES) and not is_excluded(path.join(root, f), excludes)
)
# remove hidden ('.') and private ('_') directories, as well as
# excluded dirs
@ -232,22 +248,27 @@ def walk(rootpath: str, excludes: Sequence[re.Pattern[str]], opts: Any,
else:
exclude_prefixes = ('.', '_')
subs[:] = sorted(sub for sub in subs if not sub.startswith(exclude_prefixes) and
not is_excluded(path.join(root, sub), excludes))
subs[:] = sorted(
sub
for sub in subs
if not sub.startswith(exclude_prefixes)
and not is_excluded(path.join(root, sub), excludes)
)
yield root, subs, files
def has_child_module(rootpath: str, excludes: Sequence[re.Pattern[str]], opts: Any) -> bool:
"""Check the given directory contains child module/s (at least one)."""
return any(
files
for _root, _subs, files in walk(rootpath, excludes, opts)
)
return any(files for _root, _subs, files in walk(rootpath, excludes, opts))
def recurse_tree(rootpath: str, excludes: Sequence[re.Pattern[str]], opts: Any,
user_template_dir: str | None = None) -> list[str]:
def recurse_tree(
rootpath: str,
excludes: Sequence[re.Pattern[str]],
opts: Any,
user_template_dir: str | None = None,
) -> list[str]:
"""
Look for every file in the directory tree and create the corresponding
ReST files.
@ -279,14 +300,21 @@ def recurse_tree(rootpath: str, excludes: Sequence[re.Pattern[str]], opts: Any,
if is_pkg or is_namespace:
# we are in a package with something to document
if subs or len(files) > 1 or not is_skipped_package(root, opts):
subpackage = root[len(rootpath):].lstrip(path.sep).\
replace(path.sep, '.')
subpackage = root[len(rootpath) :].lstrip(path.sep).replace(path.sep, '.')
# if this is not a namespace or
# a namespace and there is something there to document
if not is_namespace or has_child_module(root, excludes, opts):
create_package_file(root, root_package, subpackage,
files, opts, subs, is_namespace, excludes,
user_template_dir)
create_package_file(
root,
root_package,
subpackage,
files,
opts,
subs,
is_namespace,
excludes,
user_template_dir,
)
toplevels.append(module_join(root_package, subpackage))
else:
# if we are at the root level, we don't require it to be a package
@ -312,8 +340,7 @@ def is_excluded(root: str, excludes: Sequence[re.Pattern[str]]) -> bool:
def get_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
usage='%(prog)s [OPTIONS] -o <OUTPUT_PATH> <MODULE_PATH> '
'[EXCLUDE_PATTERN, ...]',
usage='%(prog)s [OPTIONS] -o <OUTPUT_PATH> <MODULE_PATH> ' '[EXCLUDE_PATTERN, ...]',
epilog=__('For more information, visit <https://www.sphinx-doc.org/>.'),
description=__("""
Look recursively in <MODULE_PATH> for Python modules and packages and create
@ -322,87 +349,196 @@ one reST file with automodule directives per package in the <OUTPUT_PATH>.
The <EXCLUDE_PATTERN>s can be file and/or directory patterns that will be
excluded from generation.
Note: By default this script will not overwrite already created files."""))
Note: By default this script will not overwrite already created files."""),
)
parser.add_argument('--version', action='version', dest='show_version',
version='%%(prog)s %s' % __display_version__)
parser.add_argument(
'--version',
action='version',
dest='show_version',
version='%%(prog)s %s' % __display_version__,
)
parser.add_argument('module_path',
help=__('path to module to document'))
parser.add_argument('exclude_pattern', nargs='*',
help=__('fnmatch-style file and/or directory patterns '
'to exclude from generation'))
parser.add_argument('module_path', help=__('path to module to document'))
parser.add_argument(
'exclude_pattern',
nargs='*',
help=__('fnmatch-style file and/or directory patterns ' 'to exclude from generation'),
)
parser.add_argument('-o', '--output-dir', action='store', dest='destdir',
required=True,
help=__('directory to place all output'))
parser.add_argument('-q', action='store_true', dest='quiet',
help=__('no output on stdout, just warnings on stderr'))
parser.add_argument('-d', '--maxdepth', action='store', dest='maxdepth',
type=int, default=4,
help=__('maximum depth of submodules to show in the TOC '
'(default: 4)'))
parser.add_argument('-f', '--force', action='store_true', dest='force',
help=__('overwrite existing files'))
parser.add_argument('-l', '--follow-links', action='store_true',
dest='followlinks', default=False,
help=__('follow symbolic links. Powerful when combined '
'with collective.recipe.omelette.'))
parser.add_argument('-n', '--dry-run', action='store_true', dest='dryrun',
help=__('run the script without creating files'))
parser.add_argument('-e', '--separate', action='store_true',
dest='separatemodules',
help=__('put documentation for each module on its own page'))
parser.add_argument('-P', '--private', action='store_true',
dest='includeprivate',
help=__('include "_private" modules'))
parser.add_argument('--tocfile', action='store', dest='tocfile', default='modules',
help=__("filename of table of contents (default: modules)"))
parser.add_argument('-T', '--no-toc', action='store_false', dest='tocfile',
help=__("don't create a table of contents file"))
parser.add_argument('-E', '--no-headings', action='store_true',
dest='noheadings',
help=__("don't create headings for the module/package "
"packages (e.g. when the docstrings already "
"contain them)"))
parser.add_argument('-M', '--module-first', action='store_true',
dest='modulefirst',
help=__('put module documentation before submodule '
'documentation'))
parser.add_argument('--implicit-namespaces', action='store_true',
dest='implicit_namespaces',
help=__('interpret module paths according to PEP-0420 '
'implicit namespaces specification'))
parser.add_argument('-s', '--suffix', action='store', dest='suffix',
default='rst',
help=__('file suffix (default: rst)'))
parser.add_argument('-F', '--full', action='store_true', dest='full',
help=__('generate a full project with sphinx-quickstart'))
parser.add_argument('-a', '--append-syspath', action='store_true',
dest='append_syspath',
help=__('append module_path to sys.path, used when --full is given'))
parser.add_argument('-H', '--doc-project', action='store', dest='header',
help=__('project name (default: root module name)'))
parser.add_argument('-A', '--doc-author', action='store', dest='author',
help=__('project author(s), used when --full is given'))
parser.add_argument('-V', '--doc-version', action='store', dest='version',
help=__('project version, used when --full is given'))
parser.add_argument('-R', '--doc-release', action='store', dest='release',
help=__('project release, used when --full is given, '
'defaults to --doc-version'))
parser.add_argument(
'-o',
'--output-dir',
action='store',
dest='destdir',
required=True,
help=__('directory to place all output'),
)
parser.add_argument(
'-q',
action='store_true',
dest='quiet',
help=__('no output on stdout, just warnings on stderr'),
)
parser.add_argument(
'-d',
'--maxdepth',
action='store',
dest='maxdepth',
type=int,
default=4,
help=__('maximum depth of submodules to show in the TOC ' '(default: 4)'),
)
parser.add_argument(
'-f', '--force', action='store_true', dest='force', help=__('overwrite existing files')
)
parser.add_argument(
'-l',
'--follow-links',
action='store_true',
dest='followlinks',
default=False,
help=__(
'follow symbolic links. Powerful when combined ' 'with collective.recipe.omelette.'
),
)
parser.add_argument(
'-n',
'--dry-run',
action='store_true',
dest='dryrun',
help=__('run the script without creating files'),
)
parser.add_argument(
'-e',
'--separate',
action='store_true',
dest='separatemodules',
help=__('put documentation for each module on its own page'),
)
parser.add_argument(
'-P',
'--private',
action='store_true',
dest='includeprivate',
help=__('include "_private" modules'),
)
parser.add_argument(
'--tocfile',
action='store',
dest='tocfile',
default='modules',
help=__('filename of table of contents (default: modules)'),
)
parser.add_argument(
'-T',
'--no-toc',
action='store_false',
dest='tocfile',
help=__("don't create a table of contents file"),
)
parser.add_argument(
'-E',
'--no-headings',
action='store_true',
dest='noheadings',
help=__(
"don't create headings for the module/package "
'packages (e.g. when the docstrings already '
'contain them)'
),
)
parser.add_argument(
'-M',
'--module-first',
action='store_true',
dest='modulefirst',
help=__('put module documentation before submodule ' 'documentation'),
)
parser.add_argument(
'--implicit-namespaces',
action='store_true',
dest='implicit_namespaces',
help=__(
'interpret module paths according to PEP-0420 ' 'implicit namespaces specification'
),
)
parser.add_argument(
'-s',
'--suffix',
action='store',
dest='suffix',
default='rst',
help=__('file suffix (default: rst)'),
)
parser.add_argument(
'-F',
'--full',
action='store_true',
dest='full',
help=__('generate a full project with sphinx-quickstart'),
)
parser.add_argument(
'-a',
'--append-syspath',
action='store_true',
dest='append_syspath',
help=__('append module_path to sys.path, used when --full is given'),
)
parser.add_argument(
'-H',
'--doc-project',
action='store',
dest='header',
help=__('project name (default: root module name)'),
)
parser.add_argument(
'-A',
'--doc-author',
action='store',
dest='author',
help=__('project author(s), used when --full is given'),
)
parser.add_argument(
'-V',
'--doc-version',
action='store',
dest='version',
help=__('project version, used when --full is given'),
)
parser.add_argument(
'-R',
'--doc-release',
action='store',
dest='release',
help=__('project release, used when --full is given, ' 'defaults to --doc-version'),
)
group = parser.add_argument_group(__('extension options'))
group.add_argument('--extensions', metavar='EXTENSIONS', dest='extensions',
action='append', help=__('enable arbitrary extensions'))
group.add_argument(
'--extensions',
metavar='EXTENSIONS',
dest='extensions',
action='append',
help=__('enable arbitrary extensions'),
)
for ext in EXTENSIONS:
group.add_argument('--ext-%s' % ext, action='append_const',
const='sphinx.ext.%s' % ext, dest='extensions',
help=__('enable %s extension') % ext)
group.add_argument(
'--ext-%s' % ext,
action='append_const',
const='sphinx.ext.%s' % ext,
dest='extensions',
help=__('enable %s extension') % ext,
)
group = parser.add_argument_group(__('Project templating'))
group.add_argument('-t', '--templatedir', metavar='TEMPLATEDIR',
dest='templatedir',
help=__('template directory for template files'))
group.add_argument(
'-t',
'--templatedir',
metavar='TEMPLATEDIR',
dest='templatedir',
help=__('template directory for template files'),
)
return parser
@ -436,6 +572,7 @@ def main(argv: Sequence[str] = (), /) -> int:
if args.full:
from sphinx.cmd import quickstart as qs
modules.sort()
prev_module = ''
text = ''
@ -455,8 +592,7 @@ def main(argv: Sequence[str] = (), /) -> int:
'suffix': '.' + args.suffix,
'master': 'index',
'epub': True,
'extensions': ['sphinx.ext.autodoc', 'sphinx.ext.viewcode',
'sphinx.ext.todo'],
'extensions': ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.todo'],
'makefile': True,
'batchfile': True,
'make_mode': True,
@ -477,8 +613,7 @@ def main(argv: Sequence[str] = (), /) -> int:
d['extensions'].extend(ext.split(','))
if not args.dryrun:
qs.generate(d, silent=True, overwrite=args.force,
templatedir=args.templatedir)
qs.generate(d, silent=True, overwrite=args.force, templatedir=args.templatedir)
elif args.tocfile:
create_modules_toc_file(modules, args, args.tocfile, args.templatedir)
@ -486,5 +621,5 @@ def main(argv: Sequence[str] = (), /) -> int:
# So program can be started with "python -m sphinx.apidoc ..."
if __name__ == "__main__":
if __name__ == '__main__':
raise SystemExit(main(sys.argv[1:]))

View File

@ -65,7 +65,7 @@ class DummyApplication:
self.config = Config()
self.registry = SphinxComponentRegistry()
self.messagelog: list[str] = []
self.srcdir = "/"
self.srcdir = '/'
self.translator = translator
self.verbosity = 0
self._warncount = 0
@ -98,10 +98,17 @@ def setup_documenters(app: Any) -> None:
ModuleDocumenter,
PropertyDocumenter,
)
documenters: list[type[Documenter]] = [
ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter,
FunctionDocumenter, MethodDocumenter,
AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter,
ModuleDocumenter,
ClassDocumenter,
ExceptionDocumenter,
DataDocumenter,
FunctionDocumenter,
MethodDocumenter,
AttributeDocumenter,
DecoratorDocumenter,
PropertyDocumenter,
]
for documenter in documenters:
app.registry.add_documenter(documenter.objtype, documenter)
@ -123,8 +130,9 @@ class AutosummaryRenderer:
raise ValueError(msg)
system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')]
loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path,
system_templates_path)
loader = SphinxTemplateLoader(
app.srcdir, app.config.templates_path, system_templates_path
)
self.env = SandboxedEnvironment(loader=loader)
self.env.filters['escape'] = rst.escape
@ -132,7 +140,7 @@ class AutosummaryRenderer:
self.env.filters['underline'] = _underline
if app.translator:
self.env.add_extension("jinja2.ext.i18n")
self.env.add_extension('jinja2.ext.i18n')
# ``install_gettext_translations`` is injected by the ``jinja2.ext.i18n`` extension
self.env.install_gettext_translations(app.translator) # type: ignore[attr-defined]
@ -168,17 +176,17 @@ def _split_full_qualified_name(name: str) -> tuple[str | None, str]:
parts = name.split('.')
for i, _part in enumerate(parts, 1):
try:
modname = ".".join(parts[:i])
modname = '.'.join(parts[:i])
importlib.import_module(modname)
except ImportError:
if parts[:i - 1]:
return ".".join(parts[:i - 1]), ".".join(parts[i - 1:])
if parts[: i - 1]:
return '.'.join(parts[: i - 1]), '.'.join(parts[i - 1 :])
else:
return None, ".".join(parts)
return None, '.'.join(parts)
except IndexError:
pass
return name, ""
return name, ''
# -- Generating output ---------------------------------------------------------
@ -194,12 +202,19 @@ class ModuleScanner:
def is_skipped(self, name: str, value: Any, objtype: str) -> bool:
try:
return self.app.emit_firstresult('autodoc-skip-member', objtype,
name, value, False, {})
return self.app.emit_firstresult(
'autodoc-skip-member', objtype, name, value, False, {}
)
except Exception as exc:
logger.warning(__('autosummary: failed to determine %r to be documented, '
'the following exception was raised:\n%s'),
name, exc, type='autosummary')
logger.warning(
__(
'autosummary: failed to determine %r to be documented, '
'the following exception was raised:\n%s'
),
name,
exc,
type='autosummary',
)
return False
def scan(self, imported_members: bool) -> list[str]:
@ -257,12 +272,19 @@ def members_of(obj: Any, conf: Config) -> Sequence[str]:
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,
recursive: bool, context: dict,
modname: str | None = None,
qualname: str | None = None) -> str:
def generate_autosummary_content(
name: str,
obj: Any,
parent: Any,
template: AutosummaryRenderer,
template_name: str,
imported_members: bool,
app: Any,
recursive: bool,
context: dict,
modname: str | None = None,
qualname: str | None = None,
) -> str:
doc = get_documenter(app, obj, parent)
ns: dict[str, Any] = {}
@ -275,23 +297,25 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
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(doc, app, obj, {'function'}, imported=imported_members)
ns['classes'], ns['all_classes'] = \
_get_members(doc, app, obj, {'class'}, imported=imported_members)
ns['exceptions'], ns['all_exceptions'] = \
_get_members(doc, app, obj, {'exception'}, imported=imported_members)
ns['attributes'], ns['all_attributes'] = \
_get_module_attrs(name, ns['members'])
ns['functions'], ns['all_functions'] = _get_members(
doc, app, obj, {'function'}, imported=imported_members
)
ns['classes'], ns['all_classes'] = _get_members(
doc, app, obj, {'class'}, imported=imported_members
)
ns['exceptions'], ns['all_exceptions'] = _get_members(
doc, app, obj, {'exception'}, imported=imported_members
)
ns['attributes'], ns['all_attributes'] = _get_module_attrs(name, ns['members'])
ispackage = hasattr(obj, '__path__')
if ispackage and recursive:
# 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"]
ns['all_functions']
+ ns['all_classes']
+ ns['all_exceptions']
+ ns['all_attributes']
)
# If respect_module_all and module has a __all__ attribute, first get
@ -301,40 +325,44 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
#
# Otherwise, use get_modules method normally
if respect_module_all and '__all__' in dir(obj):
imported_modules, all_imported_modules = \
_get_members(doc, app, obj, {'module'}, imported=True)
imported_modules, all_imported_modules = _get_members(
doc, app, 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]
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, name=name,
public_members=public_members)
modules, all_modules = _get_modules(
obj, skip=skip, name=name, public_members=public_members
)
ns['modules'] = imported_modules + modules
ns["all_modules"] = all_imported_modules + all_modules
ns['all_modules'] = all_imported_modules + all_modules
elif doc.objtype == 'class':
ns['members'] = dir(obj)
ns['inherited_members'] = \
set(dir(obj)) - set(obj.__dict__.keys())
ns['methods'], ns['all_methods'] = \
_get_members(doc, app, obj, {'method'}, include_public={'__init__'})
ns['attributes'], ns['all_attributes'] = \
_get_members(doc, app, obj, {'attribute', 'property'})
ns['inherited_members'] = set(dir(obj)) - set(obj.__dict__.keys())
ns['methods'], ns['all_methods'] = _get_members(
doc, app, obj, {'method'}, include_public={'__init__'}
)
ns['attributes'], ns['all_attributes'] = _get_members(
doc, app, obj, {'attribute', 'property'}
)
if modname is None or qualname is None:
modname, qualname = _split_full_qualified_name(name)
if doc.objtype in ('method', 'attribute', 'property'):
ns['class'] = qualname.rsplit(".", 1)[0]
ns['class'] = qualname.rsplit('.', 1)[0]
if doc.objtype == 'class':
shortname = qualname
else:
shortname = qualname.rsplit(".", 1)[-1]
shortname = qualname.rsplit('.', 1)[-1]
ns['fullname'] = name
ns['module'] = modname
@ -352,12 +380,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
def _skip_member(app: Sphinx, obj: Any, name: str, objtype: str) -> bool:
try:
return app.emit_firstresult('autodoc-skip-member', objtype, name,
obj, False, {})
return app.emit_firstresult('autodoc-skip-member', objtype, name, obj, False, {})
except Exception as exc:
logger.warning(__('autosummary: failed to determine %r to be documented, '
'the following exception was raised:\n%s'),
name, exc, type='autosummary')
logger.warning(
__(
'autosummary: failed to determine %r to be documented, '
'the following exception was raised:\n%s'
),
name,
exc,
type='autosummary',
)
return False
@ -384,9 +417,15 @@ def _get_all_members(doc: type[Documenter], app: Sphinx, obj: Any) -> dict[str,
return {}
def _get_members(doc: type[Documenter], app: Sphinx, obj: Any, types: set[str], *,
include_public: Set[str] = frozenset(),
imported: bool = True) -> tuple[list[str], list[str]]:
def _get_members(
doc: type[Documenter],
app: Sphinx,
obj: Any,
types: set[str],
*,
include_public: Set[str] = frozenset(),
imported: bool = True,
) -> tuple[list[str], list[str]]:
items: list[str] = []
public: list[str] = []
@ -423,20 +462,16 @@ def _get_module_attrs(name: str, members: Any) -> tuple[list[str], list[str]]:
if not attr_name.startswith('_'):
public.append(attr_name)
except PycodeError:
pass # give up if ModuleAnalyzer fails to parse code
pass # give up if ModuleAnalyzer fails to parse code
return public, attrs
def _get_modules(
obj: Any,
*,
skip: Sequence[str],
name: str,
public_members: Sequence[str] | None = None) -> tuple[list[str], list[str]]:
obj: Any, *, skip: Sequence[str], name: 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
@ -458,17 +493,20 @@ def _get_modules(
return public, items
def generate_autosummary_docs(sources: list[str],
output_dir: str | os.PathLike[str] | None = None,
suffix: str = '.rst',
base_path: str | os.PathLike[str] | None = None,
imported_members: bool = False, app: Any = None,
overwrite: bool = True, encoding: str = 'utf-8') -> None:
def generate_autosummary_docs(
sources: list[str],
output_dir: str | os.PathLike[str] | None = None,
suffix: str = '.rst',
base_path: str | os.PathLike[str] | None = None,
imported_members: bool = False,
app: Any = None,
overwrite: bool = True,
encoding: str = 'utf-8',
) -> None:
showed_sources = sorted(sources)
if len(showed_sources) > 20:
showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:]
logger.info(__('[autosummary] generating autosummary for: %s'),
', '.join(showed_sources))
logger.info(__('[autosummary] generating autosummary for: %s'), ', '.join(showed_sources))
if output_dir:
logger.info(__('[autosummary] writing to %s'), output_dir)
@ -501,30 +539,43 @@ def generate_autosummary_docs(sources: list[str],
try:
name, obj, parent, modname = import_by_name(entry.name)
qualname = name.replace(modname + ".", "")
qualname = name.replace(modname + '.', '')
except ImportExceptionGroup as exc:
try:
# try to import as an instance attribute
name, obj, parent, modname = import_ivar_by_name(entry.name)
qualname = name.replace(modname + ".", "")
qualname = name.replace(modname + '.', '')
except ImportError as exc2:
if exc2.__cause__:
exceptions: list[BaseException] = [*exc.exceptions, exc2.__cause__]
else:
exceptions = [*exc.exceptions, exc2]
errors = list({f"* {type(e).__name__}: {e}" for e in exceptions})
logger.warning(__('[autosummary] failed to import %s.\nPossible hints:\n%s'),
entry.name, '\n'.join(errors))
errors = list({f'* {type(e).__name__}: {e}' for e in exceptions})
logger.warning(
__('[autosummary] failed to import %s.\nPossible hints:\n%s'),
entry.name,
'\n'.join(errors),
)
continue
context: dict[str, Any] = {}
if app:
context.update(app.config.autosummary_context)
content = generate_autosummary_content(name, obj, parent, template, entry.template,
imported_members, app, entry.recursive, context,
modname, qualname)
content = generate_autosummary_content(
name,
obj,
parent,
template,
entry.template,
imported_members,
app,
entry.recursive,
context,
modname,
qualname,
)
filename = os.path.join(path, filename_map.get(name, name) + suffix)
if os.path.isfile(filename):
@ -544,14 +595,20 @@ def generate_autosummary_docs(sources: list[str],
# descend recursively to new files
if new_files:
generate_autosummary_docs(new_files, output_dir=output_dir,
suffix=suffix, base_path=base_path,
imported_members=imported_members, app=app,
overwrite=overwrite)
generate_autosummary_docs(
new_files,
output_dir=output_dir,
suffix=suffix,
base_path=base_path,
imported_members=imported_members,
app=app,
overwrite=overwrite,
)
# -- Finding documented entries in files ---------------------------------------
def find_autosummary_in_files(filenames: list[str]) -> list[AutosummaryEntry]:
"""Find out what items are documented in source/*.rst.
@ -566,7 +623,8 @@ def find_autosummary_in_files(filenames: list[str]) -> list[AutosummaryEntry]:
def find_autosummary_in_docstring(
name: str, filename: str | None = None,
name: str,
filename: str | None = None,
) -> list[AutosummaryEntry]:
"""Find out what items are documented in the given object's docstring.
@ -579,16 +637,21 @@ def find_autosummary_in_docstring(
except AttributeError:
pass
except ImportExceptionGroup as exc:
errors = '\n'.join({f"* {type(e).__name__}: {e}" for e in exc.exceptions})
errors = '\n'.join({f'* {type(e).__name__}: {e}' for e in exc.exceptions})
logger.warning(f'Failed to import {name}.\nPossible hints:\n{errors}') # NoQA: G004
except SystemExit:
logger.warning("Failed to import '%s'; the module executes module level "
'statement and it might call sys.exit().', name)
logger.warning(
"Failed to import '%s'; the module executes module level "
'statement and it might call sys.exit().',
name,
)
return []
def find_autosummary_in_lines(
lines: list[str], module: str | None = None, filename: str | None = None,
lines: list[str],
module: str | None = None,
filename: str | None = None,
) -> list[AutosummaryEntry]:
"""Find out what items appear in autosummary:: directives in the
given lines.
@ -601,10 +664,8 @@ def find_autosummary_in_lines(
corresponding options set.
"""
autosummary_re = re.compile(r'^(\s*)\.\.\s+autosummary::\s*')
automodule_re = re.compile(
r'^\s*\.\.\s+automodule::\s*([A-Za-z0-9_.]+)\s*$')
module_re = re.compile(
r'^\s*\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$')
automodule_re = re.compile(r'^\s*\.\.\s+automodule::\s*([A-Za-z0-9_.]+)\s*$')
module_re = re.compile(r'^\s*\.\.\s+(current)?module::\s*([a-zA-Z0-9_.]+)\s*$')
autosummary_item_re = re.compile(r'^\s+(~?[_a-zA-Z][a-zA-Z0-9_.]*)\s*.*?')
recursive_arg_re = re.compile(r'^\s+:recursive:\s*$')
toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$')
@ -617,7 +678,7 @@ def find_autosummary_in_lines(
template = ''
current_module = module
in_autosummary = False
base_indent = ""
base_indent = ''
for line in lines:
if in_autosummary:
@ -630,8 +691,7 @@ def find_autosummary_in_lines(
if m:
toctree = m.group(1)
if filename:
toctree = os.path.join(os.path.dirname(filename),
toctree)
toctree = os.path.join(os.path.dirname(filename), toctree)
continue
m = template_arg_re.match(line)
@ -647,13 +707,12 @@ def find_autosummary_in_lines(
name = m.group(1).strip()
if name.startswith('~'):
name = name[1:]
if current_module and \
not name.startswith(current_module + '.'):
name = f"{current_module}.{name}"
if current_module and not name.startswith(current_module + '.'):
name = f'{current_module}.{name}'
documented.append(AutosummaryEntry(name, toctree, template, recursive))
continue
if not line.strip() or line.startswith(base_indent + " "):
if not line.strip() or line.startswith(base_indent + ' '):
continue
in_autosummary = False
@ -671,8 +730,7 @@ def find_autosummary_in_lines(
if m:
current_module = m.group(1).strip()
# recurse into the automodule docstring
documented.extend(find_autosummary_in_docstring(
current_module, filename=filename))
documented.extend(find_autosummary_in_docstring(current_module, filename=filename))
continue
m = module_re.match(line)
@ -698,33 +756,62 @@ The format of the autosummary directive is documented in the
``sphinx.ext.autosummary`` Python module and can be read using::
pydoc sphinx.ext.autosummary
"""))
"""),
)
parser.add_argument('--version', action='version', dest='show_version',
version='%%(prog)s %s' % __display_version__)
parser.add_argument(
'--version',
action='version',
dest='show_version',
version='%%(prog)s %s' % __display_version__,
)
parser.add_argument('source_file', nargs='+',
help=__('source files to generate rST files for'))
parser.add_argument(
'source_file', nargs='+', help=__('source files to generate rST files for')
)
parser.add_argument('-o', '--output-dir', action='store',
dest='output_dir',
help=__('directory to place all output in'))
parser.add_argument('-s', '--suffix', action='store', dest='suffix',
default='rst',
help=__('default suffix for files (default: '
'%(default)s)'))
parser.add_argument('-t', '--templates', action='store', dest='templates',
default=None,
help=__('custom template directory (default: '
'%(default)s)'))
parser.add_argument('-i', '--imported-members', action='store_true',
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)'))
parser.add_argument(
'-o',
'--output-dir',
action='store',
dest='output_dir',
help=__('directory to place all output in'),
)
parser.add_argument(
'-s',
'--suffix',
action='store',
dest='suffix',
default='rst',
help=__('default suffix for files (default: ' '%(default)s)'),
)
parser.add_argument(
'-t',
'--templates',
action='store',
dest='templates',
default=None,
help=__('custom template directory (default: ' '%(default)s)'),
)
parser.add_argument(
'-i',
'--imported-members',
action='store_true',
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
@ -740,12 +827,15 @@ def main(argv: Sequence[str] = (), /) -> None:
if args.templates:
app.config.templates_path.append(path.abspath(args.templates))
app.config.autosummary_ignore_module_all = (not args.respect_module_all)
app.config.autosummary_ignore_module_all = not args.respect_module_all
generate_autosummary_docs(args.source_file, args.output_dir,
'.' + args.suffix,
imported_members=args.imported_members,
app=app)
generate_autosummary_docs(
args.source_file,
args.output_dir,
'.' + args.suffix,
imported_members=args.imported_members,
app=app,
)
if __name__ == '__main__':