diff --git a/.ruff.toml b/.ruff.toml index a64a00012..725091200 100644 --- a/.ruff.toml +++ b/.ruff.toml @@ -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", diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index b2e2291be..9bfa08d05 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -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 ' - '[EXCLUDE_PATTERN, ...]', + usage='%(prog)s [OPTIONS] -o ' '[EXCLUDE_PATTERN, ...]', epilog=__('For more information, visit .'), description=__(""" Look recursively in for Python modules and packages and create @@ -322,87 +349,196 @@ one reST file with automodule directives per package in the . The 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:])) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index c400d720e..8cfceb9a6 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -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__':