diff --git a/docs/lite/api/_custom/sphinx_builder_html b/docs/lite/api/_custom/sphinx_builder_html index 453a52fea86bbe95cd49a2090bc25b2db53c3901..4bf16bba38d3acc7f7ceb17adf911f052d8d4de1 100644 --- a/docs/lite/api/_custom/sphinx_builder_html +++ b/docs/lite/api/_custom/sphinx_builder_html @@ -1,12 +1,6 @@ -""" - sphinx.builders.html - ~~~~~~~~~~~~~~~~~~~~ +"""Several HTML builders.""" - Several HTML builders. - - :copyright: Copyright 2007-2020 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import html import os @@ -19,11 +13,12 @@ from lxml import etree from hashlib import md5 from datetime import datetime from os import path -from typing import IO, Any, Dict, Iterable, Iterator, List, Set, Tuple, Type +from typing import IO, Any, Iterable, Iterator, List, Tuple, Type from urllib.parse import quote +import docutils.readers.doctree from docutils import nodes -from docutils.core import publish_parts +from docutils.core import Publisher from docutils.frontend import OptionParser from docutils.io import DocTreeInput, StringOutput from docutils.nodes import Node @@ -35,6 +30,7 @@ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import ENUM, Config from sphinx.domains import Domain, Index, IndexEntry +from sphinx.environment import BuildEnvironment from sphinx.environment.adapters.asset import ImageAdapter from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.environment.adapters.toctree import TocTree @@ -43,22 +39,17 @@ from sphinx.highlighting import PygmentsBridge from sphinx.locale import _, __ from sphinx.search import js_index from sphinx.theming import HTMLThemeFactory -from sphinx.util import isurl, logging, md5, progress_message, status_iterator -from sphinx.util.docutils import is_html5_writer_available, new_document +from sphinx.util import isurl, logging, md5 +from sphinx.util.display import progress_message, status_iterator +from sphinx.util.docutils import new_document from sphinx.util.fileutil import copy_asset from sphinx.util.i18n import format_date from sphinx.util.inventory import InventoryFile from sphinx.util.matching import DOTFILES, Matcher, patmatch from sphinx.util.osutil import copyfile, ensuredir, os_path, relative_uri from sphinx.util.tags import Tags -from sphinx.writers.html import HTMLTranslator, HTMLWriter - -# HTML5 Writer is available or not -if is_html5_writer_available(): - from sphinx.writers.html5 import HTML5Translator - html5_ready = True -else: - html5_ready = False +from sphinx.writers.html import HTMLWriter +from sphinx.writers.html5 import HTML5Translator #: the filename for the inventory of objects INVENTORY_FILENAME = 'objects.inv' @@ -66,6 +57,17 @@ INVENTORY_FILENAME = 'objects.inv' logger = logging.getLogger(__name__) return_codes_re = re.compile('[\r\n]+') +DOMAIN_INDEX_TYPE = Tuple[ + # Index name (e.g. py-modindex) + str, + # Index class + Type[Index], + # list of (heading string, list of index entries) pairs. + List[Tuple[str, List[IndexEntry]]], + # whether sub-entries should start collapsed + bool, +] + def get_stable_hash(obj: Any) -> str: """ @@ -80,6 +82,17 @@ def get_stable_hash(obj: Any) -> str: return md5(str(obj).encode()).hexdigest() +def convert_locale_to_language_tag(locale: str | None) -> str | None: + """Convert a locale string to a language tag (ex. en_US -> en-US). + + refs: BCP 47 (:rfc:`5646`) + """ + if locale: + return locale.replace('_', '-') + else: + return None + + class Stylesheet(str): """A metadata of stylesheet. @@ -87,12 +100,12 @@ class Stylesheet(str): its filename (str). """ - attributes: Dict[str, str] = None + attributes: dict[str, str] = None filename: str = None priority: int = None - def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: Any - ) -> "Stylesheet": + def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: Any, + ) -> Stylesheet: self = str.__new__(cls, filename) self.filename = filename self.priority = priority @@ -113,11 +126,11 @@ class JavaScript(str): its filename (str). """ - attributes: Dict[str, str] = None + attributes: dict[str, str] = None filename: str = None priority: int = None - def __new__(cls, filename: str, priority: int = 500, **attributes: str) -> "JavaScript": + def __new__(cls, filename: str, priority: int = 500, **attributes: str) -> JavaScript: self = str.__new__(cls, filename) self.filename = filename self.priority = priority @@ -134,7 +147,7 @@ class BuildInfo: """ @classmethod - def load(cls, f: IO) -> "BuildInfo": + def load(cls, f: IO) -> BuildInfo: try: lines = f.readlines() assert lines[0].rstrip() == '# Sphinx build info version 1' @@ -148,7 +161,9 @@ class BuildInfo: except Exception as exc: raise ValueError(__('build info file is broken: %r') % exc) from exc - def __init__(self, config: Config = None, tags: Tags = None, config_categories: List[str] = []) -> None: # NOQA + def __init__( + self, config: Config = None, tags: Tags = None, config_categories: list[str] = [], + ) -> None: self.config_hash = '' self.tags_hash = '' @@ -159,7 +174,7 @@ class BuildInfo: if tags: self.tags_hash = get_stable_hash(sorted(tags)) - def __eq__(self, other: "BuildInfo") -> bool: # type: ignore + def __eq__(self, other: BuildInfo) -> bool: # type: ignore return (self.config_hash == other.config_hash and self.tags_hash == other.tags_hash) @@ -201,23 +216,39 @@ class StandaloneHTMLBuilder(Builder): download_support = True # enable download role imgpath: str = None - domain_indices: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] = [] # NOQA + domain_indices: list[DOMAIN_INDEX_TYPE] = [] - def __init__(self, app: Sphinx) -> None: - super().__init__(app) + def __init__(self, app: Sphinx, env: BuildEnvironment = None) -> None: + super().__init__(app, env) # CSS files - self.css_files: List[Stylesheet] = [] + self.css_files: list[Stylesheet] = [] # JS files - self.script_files: List[JavaScript] = [] + self.script_files: list[JavaScript] = [] + + # Cached Publisher for writing doctrees to HTML + reader = docutils.readers.doctree.Reader(parser_name='restructuredtext') + pub = Publisher( + reader=reader, + parser=reader.parser, + writer=HTMLWriter(self), + source_class=DocTreeInput, + destination=StringOutput(encoding='unicode'), + ) + if docutils.__version_info__[:2] >= (0, 19): + pub.get_settings(output_encoding='unicode', traceback=True) + else: + op = pub.setup_option_parser(output_encoding='unicode', traceback=True) + pub.settings = op.get_default_values() + self._publisher = pub def init(self) -> None: self.build_info = self.create_build_info() # basename of images directory self.imagedir = '_images' # section numbers for headings in the currently visited document - self.secnumbers: Dict[str, Tuple[int, ...]] = {} + self.secnumbers: dict[str, tuple[int, ...]] = {} # currently written docname self.current_docname: str = None @@ -255,15 +286,18 @@ class StandaloneHTMLBuilder(Builder): return jsfile return None - def _get_style_filename(self) -> str: - if self.config.html_style is not None: - return self.config.html_style + def _get_style_filenames(self) -> Iterator[str]: + if isinstance(self.config.html_style, str): + yield self.config.html_style + elif self.config.html_style is not None: + yield from self.config.html_style elif self.theme: - return self.theme.get_config('theme', 'stylesheet') + stylesheet = self.theme.get_config('theme', 'stylesheet') + yield from map(str.strip, stylesheet.split(',')) else: - return 'default.css' + yield 'default.css' - def get_theme_config(self) -> Tuple[str, Dict]: + def get_theme_config(self) -> tuple[str, dict]: return self.config.html_theme, self.config.html_theme_options def init_templates(self) -> None: @@ -300,7 +334,9 @@ class StandaloneHTMLBuilder(Builder): def init_css_files(self) -> None: self.css_files = [] self.add_css_file('pygments.css', priority=200) - self.add_css_file(self._get_style_filename(), priority=200) + + for filename in self._get_style_filenames(): + self.add_css_file(filename, priority=200) for filename, attrs in self.app.registry.css_files: self.add_css_file(filename, **attrs) @@ -319,9 +355,8 @@ class StandaloneHTMLBuilder(Builder): self.script_files = [] self.add_js_file('documentation_options.js', id="documentation_options", data_url_root='', priority=200) - self.add_js_file('jquery.js', priority=200) - self.add_js_file('underscore.js', priority=200) self.add_js_file('doctools.js', priority=200) + self.add_js_file('sphinx_highlight.js', priority=200) for filename, attrs in self.app.registry.js_files: self.add_js_file(filename, **attrs) @@ -330,7 +365,7 @@ class StandaloneHTMLBuilder(Builder): attrs.setdefault('priority', 800) # User's JSs are loaded after extensions' self.add_js_file(filename, **attrs) - if self.config.language and self._get_translations_js(): + if self._get_translations_js(): self.add_js_file('translations.js') def add_js_file(self, filename: str, **kwargs: Any) -> None: @@ -340,11 +375,8 @@ class StandaloneHTMLBuilder(Builder): self.script_files.append(JavaScript(filename, **kwargs)) @property - def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore - if not html5_ready or self.config.html4_writer: - return HTMLTranslator - else: - return HTML5Translator + def default_translator_class(self) -> type[nodes.NodeVisitor]: # type: ignore + return HTML5Translator @property def math_renderer_name(self) -> str: @@ -368,7 +400,7 @@ class StandaloneHTMLBuilder(Builder): def get_outdated_docs(self) -> Iterator[str]: try: - with open(path.join(self.outdir, '.buildinfo')) as fp: + with open(path.join(self.outdir, '.buildinfo'), encoding="utf-8") as fp: buildinfo = BuildInfo.load(fp) if self.build_info != buildinfo: @@ -412,41 +444,40 @@ class StandaloneHTMLBuilder(Builder): # source doesn't exist anymore pass - def get_asset_paths(self) -> List[str]: + def get_asset_paths(self) -> list[str]: return self.config.html_extra_path + self.config.html_static_path - def render_partial(self, node: Node) -> Dict[str, str]: + def render_partial(self, node: Node | None) -> dict[str, str]: """Utility: Render a lone doctree node.""" if node is None: return {'fragment': ''} + doc = new_document('') doc.append(node) + self._publisher.set_source(doc) + self._publisher.publish() + return self._publisher.writer.parts - writer = HTMLWriter(self) - return publish_parts(reader_name='doctree', - writer=writer, - source_class=DocTreeInput, - settings_overrides={'output_encoding': 'unicode'}, - source=doc) - - def prepare_writing(self, docnames: Set[str]) -> None: + def prepare_writing(self, docnames: set[str]) -> None: # create the search indexer self.indexer = None if self.search: from sphinx.search import IndexBuilder lang = self.config.html_search_language or self.config.language - if not lang: - lang = 'en' self.indexer = IndexBuilder(self.env, lang, self.config.html_search_options, self.config.html_search_scorer) self.load_indexer(docnames) self.docwriter = HTMLWriter(self) - self.docsettings: Any = OptionParser( - defaults=self.env.settings, - components=(self.docwriter,), - read_config_files=True).get_default_values() + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', category=DeprecationWarning) + # DeprecationWarning: The frontend.OptionParser class will be replaced + # by a subclass of argparse.ArgumentParser in Docutils 0.21 or later. + self.docsettings: Any = OptionParser( + defaults=self.env.settings, + components=(self.docwriter,), + read_config_files=True).get_default_values() self.docsettings.compact_lists = bool(self.config.html_compact_lists) # determine the additional indices to include @@ -457,7 +488,7 @@ class StandaloneHTMLBuilder(Builder): for domain_name in sorted(self.env.domains): domain: Domain = self.env.domains[domain_name] for indexcls in domain.indices: - indexname = '%s-%s' % (domain.name, indexcls.name) + indexname = f'{domain.name}-{indexcls.name}' if isinstance(indices_config, list): if indexname not in indices_config: continue @@ -487,7 +518,7 @@ class StandaloneHTMLBuilder(Builder): self.relations = self.env.collect_relations() - rellinks: List[Tuple[str, str, str, str]] = [] + rellinks: list[tuple[str, str, str, str]] = [] if self.use_index: rellinks.append(('genindex', _('General Index'), 'I', _('index'))) for indexname, indexcls, _content, _collapse in self.domain_indices: @@ -499,6 +530,7 @@ class StandaloneHTMLBuilder(Builder): # back up script_files and css_files to allow adding JS/CSS files to a specific page. self._script_files = list(self.script_files) self._css_files = list(self.css_files) + styles = list(self._get_style_filenames()) self.globalcontext = { 'embedded': self.embedded, @@ -513,6 +545,7 @@ class StandaloneHTMLBuilder(Builder): 'docstitle': self.config.html_title, 'shorttitle': self.config.html_short_title, 'show_copyright': self.config.html_show_copyright, + 'show_search_summary': self.config.html_show_search_summary, 'show_sphinx': self.config.html_show_sphinx, 'has_source': self.config.html_copy_source, 'show_source': self.config.html_show_sourcelink, @@ -520,17 +553,18 @@ class StandaloneHTMLBuilder(Builder): 'file_suffix': self.out_suffix, 'link_suffix': self.link_suffix, 'script_files': self.script_files, - 'language': self.config.language, + 'language': convert_locale_to_language_tag(self.config.language), 'css_files': self.css_files, 'sphinx_version': __display_version__, 'sphinx_version_tuple': sphinx_version, - 'style': self._get_style_filename(), + 'docutils_version_info': docutils.__version_info__[:5], + 'styles': styles, 'rellinks': rellinks, 'builder': self.name, 'parents': [], - 'logo': logo, - 'favicon': favicon, - 'html5_doctype': html5_ready and not self.config.html4_writer, + 'logo_url': logo, + 'favicon_url': favicon, + 'html5_doctype': True, } if self.theme: self.globalcontext.update( @@ -538,7 +572,7 @@ class StandaloneHTMLBuilder(Builder): self.theme.get_options(self.theme_options).items()) self.globalcontext.update(self.config.html_context) - def get_doc_context(self, docname: str, body: str, metatags: str) -> Dict[str, Any]: + def get_doc_context(self, docname: str, body: str, metatags: str) -> dict[str, Any]: """Collect items for the template context of a page.""" # find out relations prev = next = None @@ -550,7 +584,7 @@ class StandaloneHTMLBuilder(Builder): try: next = { 'link': self.get_relative_uri(docname, related[2]), - 'title': self.render_partial(titles[related[2]])['title'] + 'title': self.render_partial(titles[related[2]])['title'], } rellinks.append((related[2], next['title'], 'N', _('next'))) except KeyError: @@ -559,7 +593,7 @@ class StandaloneHTMLBuilder(Builder): try: prev = { 'link': self.get_relative_uri(docname, related[1]), - 'title': self.render_partial(titles[related[1]])['title'] + 'title': self.render_partial(titles[related[1]])['title'], } rellinks.append((related[1], prev['title'], 'P', _('previous'))) except KeyError: @@ -762,19 +796,20 @@ class StandaloneHTMLBuilder(Builder): def create_pygments_style_file(self) -> None: """create a style file for pygments.""" - with open(path.join(self.outdir, '_static', 'pygments.css'), 'w') as f: + with open(path.join(self.outdir, '_static', 'pygments.css'), 'w', + encoding="utf-8") as f: f.write(self.highlighter.get_stylesheet()) if self.dark_highlighter: - with open(path.join(self.outdir, '_static', 'pygments_dark.css'), 'w') as f: + with open(path.join(self.outdir, '_static', 'pygments_dark.css'), 'w', + encoding="utf-8") as f: f.write(self.dark_highlighter.get_stylesheet()) def copy_translation_js(self) -> None: """Copy a JavaScript file for translations.""" - if self.config.language is not None: - jsfile = self._get_translations_js() - if jsfile: - copyfile(jsfile, path.join(self.outdir, '_static', 'translations.js')) + jsfile = self._get_translations_js() + if jsfile: + copyfile(jsfile, path.join(self.outdir, '_static', 'translations.js')) def copy_stemmer_js(self) -> None: """Copy a JavaScript file for stemmer.""" @@ -787,7 +822,7 @@ class StandaloneHTMLBuilder(Builder): if jsfile: copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js')) - def copy_theme_static_files(self, context: Dict) -> None: + def copy_theme_static_files(self, context: dict[str, Any]) -> None: def onerror(filename: str, error: Exception) -> None: logger.warning(__('Failed to copy a file in html_static_file: %s: %r'), filename, error) @@ -799,7 +834,7 @@ class StandaloneHTMLBuilder(Builder): excluded=DOTFILES, context=context, renderer=self.templates, onerror=onerror) - def copy_html_static_files(self, context: Dict) -> None: + def copy_html_static_files(self, context: dict) -> None: def onerror(filename: str, error: Exception) -> None: logger.warning(__('Failed to copy a file in html_static_file: %s: %r'), filename, error) @@ -853,7 +888,7 @@ class StandaloneHTMLBuilder(Builder): def write_buildinfo(self) -> None: try: - with open(path.join(self.outdir, '.buildinfo'), 'w') as fp: + with open(path.join(self.outdir, '.buildinfo'), 'w', encoding="utf-8") as fp: self.build_info.dump(fp) except OSError as exc: logger.warning(__('Failed to write build info file: %r'), exc) @@ -867,7 +902,7 @@ class StandaloneHTMLBuilder(Builder): """Pick the best candidate for an image and link down-scaled images to their high res version. """ - Builder.post_process_images(self, doctree) + super().post_process_images(doctree) if self.config.html_scaled_image_link and self.html_scaled_image_link: for node in doctree.findall(nodes.image): @@ -875,12 +910,13 @@ class StandaloneHTMLBuilder(Builder): # resizing options are not given. scaled image link is available # only for resized images. continue - elif isinstance(node.parent, nodes.reference): + if isinstance(node.parent, nodes.reference): # A image having hyperlink target continue - elif 'no-scaled-link' in node['classes']: + if 'no-scaled-link' in node['classes']: # scaled image link is disabled for this node continue + uri = node['uri'] reference = nodes.reference('', '', internal=True) if uri in self.images: @@ -903,7 +939,7 @@ class StandaloneHTMLBuilder(Builder): self.indexer.load(fb, self.indexer_format) except (OSError, ValueError): if keep: - logger.warning(__('search index couldn\'t be loaded, but not all ' + logger.warning(__("search index couldn't be loaded, but not all " 'documents will be built: the index will be ' 'incomplete.')) # delete all entries for files that will be rebuilt @@ -930,7 +966,7 @@ class StandaloneHTMLBuilder(Builder): def get_outfilename(self, pagename: str) -> str: return path.join(self.outdir, os_path(pagename) + self.out_suffix) - def add_sidebars(self, pagename: str, ctx: Dict) -> None: + def add_sidebars(self, pagename: str, ctx: dict) -> None: def has_wildcard(pattern: str) -> bool: return any(char in pattern for char in '*?[') @@ -985,8 +1021,8 @@ class StandaloneHTMLBuilder(Builder): def get_target_uri(self, docname: str, typ: str = None) -> str: return quote(docname) + self.link_suffix - def handle_page(self, pagename: str, addctx: Dict, templatename: str = 'page.html', - outfilename: str = None, event_arg: Any = None) -> None: + def handle_page(self, pagename: str, addctx: dict, templatename: str = 'page.html', + outfilename: str | None = None, event_arg: Any = None) -> None: ctx = self.globalcontext.copy() # current_page_name is backwards compatibility ctx['pagename'] = ctx['current_page_name'] = pagename @@ -1002,7 +1038,9 @@ class StandaloneHTMLBuilder(Builder): else: ctx['pageurl'] = None - def pathto(otheruri: str, resource: bool = False, baseuri: str = default_baseuri) -> str: # NOQA + def pathto( + otheruri: str, resource: bool = False, baseuri: str = default_baseuri, + ) -> str: if resource and '://' in otheruri: # allow non-local resources given by scheme return otheruri @@ -1017,9 +1055,9 @@ class StandaloneHTMLBuilder(Builder): def hasdoc(name: str) -> bool: if name in self.env.all_docs: return True - elif name == 'search' and self.search: + if name == 'search' and self.search: return True - elif name == 'genindex' and self.get_builder_config('use_index', 'html'): + if name == 'genindex' and self.get_builder_config('use_index', 'html'): return True return False ctx['hasdoc'] = hasdoc @@ -1041,7 +1079,7 @@ class StandaloneHTMLBuilder(Builder): # sort JS/CSS before rendering HTML try: # Convert script_files to list to support non-list script_files (refs: #8889) - ctx['script_files'] = sorted(list(ctx['script_files']), key=lambda js: js.priority) + ctx['script_files'] = sorted(ctx['script_files'], key=lambda js: js.priority) except AttributeError: # Skip sorting if users modifies script_files directly (maybe via `html_context`). # refs: #8885 @@ -1050,7 +1088,7 @@ class StandaloneHTMLBuilder(Builder): pass try: - ctx['css_files'] = sorted(list(ctx['css_files']), key=lambda css: css.priority) + ctx['css_files'] = sorted(ctx['css_files'], key=lambda css: css.priority) except AttributeError: pass @@ -1145,7 +1183,7 @@ class StandaloneHTMLBuilder(Builder): copyfile(self.env.doc2path(pagename), source_name) def update_page_context(self, pagename: str, templatename: str, - ctx: Dict, event_arg: Any) -> None: + ctx: dict, event_arg: Any) -> None: pass def handle_finish(self) -> None: @@ -1174,7 +1212,7 @@ class StandaloneHTMLBuilder(Builder): def convert_html_css_files(app: Sphinx, config: Config) -> None: """This converts string styled html_css_files to tuple styled one.""" - html_css_files: List[Tuple[str, Dict]] = [] + html_css_files: list[tuple[str, dict]] = [] for entry in config.html_css_files: if isinstance(entry, str): html_css_files.append((entry, {})) @@ -1191,7 +1229,7 @@ def convert_html_css_files(app: Sphinx, config: Config) -> None: def convert_html_js_files(app: Sphinx, config: Config) -> None: """This converts string styled html_js_files to tuple styled one.""" - html_js_files: List[Tuple[str, Dict]] = [] + html_js_files: list[tuple[str, dict]] = [] for entry in config.html_js_files: if isinstance(entry, str): html_js_files.append((entry, {})) @@ -1207,7 +1245,7 @@ def convert_html_js_files(app: Sphinx, config: Config) -> None: def setup_css_tag_helper(app: Sphinx, pagename: str, templatename: str, - context: Dict, doctree: Node) -> None: + context: dict, doctree: Node) -> None: """Set up css_tag() template helper. .. note:: This set up function is added to keep compatibility with webhelper. @@ -1219,7 +1257,7 @@ def setup_css_tag_helper(app: Sphinx, pagename: str, templatename: str, for key in sorted(css.attributes): value = css.attributes[key] if value is not None: - attrs.append('%s="%s"' % (key, html.escape(value, True))) + attrs.append(f'{key}="{html.escape(value, True)}"') attrs.append('href="%s"' % pathto(css.filename, resource=True)) return '' % ' '.join(attrs) @@ -1227,7 +1265,7 @@ def setup_css_tag_helper(app: Sphinx, pagename: str, templatename: str, def setup_js_tag_helper(app: Sphinx, pagename: str, templatename: str, - context: Dict, doctree: Node) -> None: + context: dict, doctree: Node) -> None: """Set up js_tag() template helper. .. note:: This set up function is added to keep compatibility with webhelper. @@ -1246,7 +1284,7 @@ def setup_js_tag_helper(app: Sphinx, pagename: str, templatename: str, elif key == 'data_url_root': attrs.append('data-url_root="%s"' % pathto('', resource=True)) else: - attrs.append('%s="%s"' % (key, html.escape(value, True))) + attrs.append(f'{key}="{html.escape(value, True)}"') if js.filename: attrs.append('src="%s"' % pathto(js.filename, resource=True)) else: @@ -1254,31 +1292,27 @@ def setup_js_tag_helper(app: Sphinx, pagename: str, templatename: str, attrs.append('src="%s"' % pathto(js, resource=True)) if attrs: - return '' % (' '.join(attrs), body) + return f'' else: - return '' % body + return f'' context['js_tag'] = js_tag def setup_resource_paths(app: Sphinx, pagename: str, templatename: str, - context: Dict, doctree: Node) -> None: + context: dict, doctree: Node) -> None: """Set up relative resource paths.""" pathto = context.get('pathto') # favicon_url - favicon = context.get('favicon') - if favicon and not isurl(favicon): - context['favicon_url'] = pathto('_static/' + favicon, resource=True) - else: - context['favicon_url'] = favicon + favicon_url = context.get('favicon_url') + if favicon_url and not isurl(favicon_url): + context['favicon_url'] = pathto('_static/' + favicon_url, resource=True) # logo_url - logo = context.get('logo') - if logo and not isurl(logo): - context['logo_url'] = pathto('_static/' + logo, resource=True) - else: - context['logo_url'] = logo + logo_url = context.get('logo_url') + if logo_url and not isurl(logo_url): + context['logo_url'] = pathto('_static/' + logo_url, resource=True) def validate_math_renderer(app: Sphinx) -> None: @@ -1289,7 +1323,7 @@ def validate_math_renderer(app: Sphinx) -> None: if name is None: raise ConfigError(__('Many math_renderers are registered. ' 'But no math_renderer is selected.')) - elif name not in app.registry.html_inline_math_renderers: + if name not in app.registry.html_inline_math_renderers: raise ConfigError(__('Unknown math_renderer %r is given.') % name) @@ -1337,40 +1371,16 @@ def validate_html_favicon(app: Sphinx, config: Config) -> None: config.html_favicon = None # type: ignore -class _stable_repr_object(): - - def __repr__(self): - return '' - - -UNSET = _stable_repr_object() - - -def migrate_html_add_permalinks(app: Sphinx, config: Config) -> None: - """Migrate html_add_permalinks to html_permalinks*.""" - html_add_permalinks = config.html_add_permalinks - if html_add_permalinks is UNSET: - return - - # RemovedInSphinx60Warning - logger.warning(__('html_add_permalinks has been deprecated since v3.5.0. ' - 'Please use html_permalinks and html_permalinks_icon instead.')) - if not html_add_permalinks: - config.html_permalinks = False # type: ignore[attr-defined] - return - - config.html_permalinks_icon = html.escape( # type: ignore[attr-defined] - html_add_permalinks - ) - -# for compatibility -import sphinxcontrib.serializinghtml # NOQA - -import sphinx.builders.dirhtml # NOQA -import sphinx.builders.singlehtml # NOQA +def error_on_html_4(_app: Sphinx, config: Config) -> None: + """Error on HTML 4.""" + if config.html4_writer: + raise ConfigError(_( + 'HTML 4 is no longer supported by Sphinx. ' + '("html4_writer=True" detected in configuration options)', + )) -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: # builders app.add_builder(StandaloneHTMLBuilder) @@ -1382,7 +1392,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: lambda self: _('%s %s documentation') % (self.project, self.release), 'html', [str]) app.add_config_value('html_short_title', lambda self: self.html_title, 'html') - app.add_config_value('html_style', None, 'html', [str]) + app.add_config_value('html_style', None, 'html', [list, str]) app.add_config_value('html_logo', None, 'html', [str]) app.add_config_value('html_favicon', None, 'html', [str]) app.add_config_value('html_css_files', [], 'html') @@ -1393,7 +1403,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('html_sidebars', {}, 'html') app.add_config_value('html_additional_pages', {}, 'html') app.add_config_value('html_domain_indices', True, 'html', [list]) - app.add_config_value('html_add_permalinks', UNSET, 'html') app.add_config_value('html_permalinks', True, 'html') app.add_config_value('html_permalinks_icon', '¶', 'html') app.add_config_value('html_use_index', True, 'html') @@ -1405,6 +1414,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('html_file_suffix', None, 'html', [str]) app.add_config_value('html_link_suffix', None, 'html', [str]) app.add_config_value('html_show_copyright', True, 'html') + app.add_config_value('html_show_search_summary', True, 'html') app.add_config_value('html_show_sphinx', True, 'html') app.add_config_value('html_context', {}, 'html') app.add_config_value('html_output_encoding', 'utf-8', 'html') @@ -1415,7 +1425,7 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('html_search_scorer', '', None) app.add_config_value('html_scaled_image_link', True, 'html') app.add_config_value('html_baseurl', '', 'html') - app.add_config_value('html_codeblock_linenos_style', 'inline', 'html', # RemovedInSphinx60Warning # NOQA + app.add_config_value('html_codeblock_linenos_style', 'inline', 'html', # RemovedInSphinx70Warning # noqa: E501 ENUM('table', 'inline')) app.add_config_value('html_math_renderer', None, 'env') app.add_config_value('html4_writer', False, 'html') @@ -1427,11 +1437,11 @@ def setup(app: Sphinx) -> Dict[str, Any]: # event handlers app.connect('config-inited', convert_html_css_files, priority=800) app.connect('config-inited', convert_html_js_files, priority=800) - app.connect('config-inited', migrate_html_add_permalinks, priority=800) app.connect('config-inited', validate_html_extra_path, priority=800) app.connect('config-inited', validate_html_static_path, priority=800) app.connect('config-inited', validate_html_logo, priority=800) app.connect('config-inited', validate_html_favicon, priority=800) + app.connect('config-inited', error_on_html_4, priority=800) app.connect('builder-inited', validate_math_renderer) app.connect('html-page-context', setup_css_tag_helper) app.connect('html-page-context', setup_js_tag_helper) diff --git a/docs/lite/api/_custom/sphinx_writer_html5 b/docs/lite/api/_custom/sphinx_writer_html5 index 6ae120d5b61c7bcbdb5043c45787fb231d4d0722..bc944cfbc95c2c9bb565074f49e123aa9ea1fea3 100644 --- a/docs/lite/api/_custom/sphinx_writer_html5 +++ b/docs/lite/api/_custom/sphinx_writer_html5 @@ -1,20 +1,13 @@ -""" - sphinx.writers.html5 - ~~~~~~~~~~~~~~~~~~~~ +"""Experimental docutils writers for HTML5 handling Sphinx's custom nodes.""" - Experimental docutils writers for HTML5 handling Sphinx's custom nodes. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import os import posixpath import re import urllib.parse import sys -import warnings -from typing import TYPE_CHECKING, Iterable, Set, Tuple, cast +from typing import TYPE_CHECKING, Iterable, cast from docutils import nodes from docutils.nodes import Element, Node, Text @@ -22,7 +15,6 @@ from docutils.writers.html5_polyglot import HTMLTranslator as BaseTranslator from sphinx import addnodes from sphinx.builders import Builder -from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning from sphinx.locale import _, __, admonitionlabels from sphinx.util import logging from sphinx.util.docutils import SphinxTranslator @@ -43,12 +35,11 @@ def multiply_length(length: str, scale: int) -> str: matched = re.match(r'^(\d*\.?\d*)\s*(\S*)$', length) if not matched: return length - elif scale == 100: + if scale == 100: return length - else: - amount, unit = matched.groups() - result = float(amount) * scale / 100 - return "%s%s" % (int(result), unit) + amount, unit = matched.groups() + result = float(amount) * scale / 100 + return f"{int(result)}{unit}" class HTML5Translator(SphinxTranslator, BaseTranslator): @@ -56,11 +47,11 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): Our custom HTML translator. """ - builder: "StandaloneHTMLBuilder" = None + builder: StandaloneHTMLBuilder # Override docutils.writers.html5_polyglot:HTMLTranslator # otherwise, nodes like ... will be # converted to ... by `visit_inline`. - supported_inline_tags: Set[str] = set() + supported_inline_tags: set[str] = set() def __init__(self, document: nodes.document, builder: Builder) -> None: super().__init__(document, builder) @@ -84,6 +75,13 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def depart_start_of_file(self, node: Element) -> None: self.docnames.pop() + ############################################################# + # Domain-specific object descriptions + ############################################################# + + # Top-level nodes for descriptions + ################################## + def visit_desc(self, node: Element) -> None: self.body.append(self.starttag(node, 'dl')) @@ -236,7 +234,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): 'References must have "refuri" or "refid" attribute.' atts['href'] = '#' + node['refid'] if not isinstance(node.parent, nodes.TextElement): - assert len(node) == 1 and isinstance(node[0], nodes.image) + assert len(node) == 1 and isinstance(node[0], nodes.image) # NoQA: PT018 atts['class'] += ' image-reference' if 'reftitle' in node: atts['title'] = node['reftitle'] @@ -265,20 +263,23 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): if name: node.insert(0, nodes.title(name, admonitionlabels[name])) + def depart_admonition(self, node: Element | None = None) -> None: + self.body.append('\n') + def visit_seealso(self, node: Element) -> None: self.visit_admonition(node, 'seealso') def depart_seealso(self, node: Element) -> None: self.depart_admonition(node) - def get_secnumber(self, node: Element) -> Tuple[int, ...]: + def get_secnumber(self, node: Element) -> tuple[int, ...] | None: if node.get('secnumber'): return node['secnumber'] if isinstance(node.parent, nodes.section): if self.builder.name == 'singlehtml': docname = self.docnames[-1] - anchorname = "%s/#%s" % (docname, node.parent['ids'][0]) + anchorname = "{}/#{}".format(docname, node.parent['ids'][0]) if anchorname not in self.builder.secnumbers: anchorname = "%s/" % docname # try first heading which has no anchor else: @@ -300,7 +301,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def add_fignumber(self, node: Element) -> None: def append_fignumber(figtype: str, figure_id: str) -> None: if self.builder.name == 'singlehtml': - key = "%s/%s" % (self.docnames[-1], figtype) + key = f"{self.docnames[-1]}/{figtype}" else: key = figtype @@ -395,12 +396,12 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): node.parent.hasattr('ids') and node.parent['ids']): # add permalink anchor if close_tag.startswith('%s' % ( - _('Permalink to this headline'), + 'title="{}">{}'.format( + _('Permalink to this heading'), self.config.html_permalinks_icon)) elif isinstance(node.parent, nodes.table): self.body.append('') @@ -427,7 +428,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): highlighted = self.highlighter.highlight_block( node.rawsource, lang, opts=opts, linenos=linenos, - location=node, **highlight_args + location=node, **highlight_args, ) starttag = self.starttag(node, 'div', suffix='', CLASS='highlight-%s notranslate' % lang) @@ -473,10 +474,25 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): if 'kbd' in node['classes']: self.body.append(self.starttag(node, 'kbd', '', CLASS='docutils literal notranslate')) - else: + return + lang = node.get("language", None) + if 'code' not in node['classes'] or not lang: self.body.append(self.starttag(node, 'code', '', CLASS='docutils literal notranslate')) self.protect_literal_text += 1 + return + + opts = self.config.highlight_options.get(lang, {}) + highlighted = self.highlighter.highlight_block( + node.astext(), lang, opts=opts, location=node, nowrap=True) + starttag = self.starttag( + node, + "code", + suffix="", + CLASS="docutils literal highlight highlight-%s" % lang, + ) + self.body.append(starttag + highlighted.strip() + "") + raise nodes.SkipNode def depart_literal(self, node: Element) -> None: if 'kbd' in node['classes']: @@ -563,7 +579,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): # rewrite the URI if the environment knows about it if olduri in self.builder.images: node['uri'] = posixpath.join(self.builder.imgpath, - self.builder.images[olduri]) + urllib.parse.quote(self.builder.images[olduri])) if 'scale' in node: # Try to figure out image height and width. Docutils does that too, @@ -572,8 +588,10 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): if not ('width' in node and 'height' in node): size = get_image_size(os.path.join(self.builder.srcdir, olduri)) if size is None: - logger.warning(__('Could not obtain image size. :scale: option is ignored.'), # NOQA - location=node) + logger.warning( + __('Could not obtain image size. :scale: option is ignored.'), + location=node, + ) else: if 'width' not in node: node['width'] = str(size[0]) @@ -803,7 +821,7 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def depart_math(self, node: Element, math_env: str = '') -> None: name = self.builder.math_renderer_name _, depart = self.builder.app.registry.html_inline_math_renderers[name] - if depart: + if depart: # type: ignore[truthy-function] depart(self, node) def visit_math_block(self, node: Element, math_env: str = '') -> None: @@ -814,38 +832,5 @@ class HTML5Translator(SphinxTranslator, BaseTranslator): def depart_math_block(self, node: Element, math_env: str = '') -> None: name = self.builder.math_renderer_name _, depart = self.builder.app.registry.html_block_math_renderers[name] - if depart: + if depart: # type: ignore[truthy-function] depart(self, node) - - @property - def permalink_text(self) -> str: - warnings.warn('HTMLTranslator.permalink_text is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - return self.config.html_permalinks_icon - - def generate_targets_for_table(self, node: Element) -> None: - """Generate hyperlink targets for tables. - - Original visit_table() generates hyperlink targets inside table tags - () if multiple IDs are assigned to listings. - That is invalid DOM structure. (This is a bug of docutils <= 0.13.1) - - This exports hyperlink targets before tables to make valid DOM structure. - """ - warnings.warn('generate_targets_for_table() is deprecated', - RemovedInSphinx60Warning, stacklevel=2) - for id in node['ids'][1:]: - self.body.append('' % id) - node['ids'].remove(id) - - @property - def _fieldlist_row_index(self): - warnings.warn('_fieldlist_row_index is deprecated', - RemovedInSphinx60Warning, stacklevel=2) - return self._fieldlist_row_indices[-1] - - @property - def _table_row_index(self): - warnings.warn('_table_row_index is deprecated', - RemovedInSphinx60Warning, stacklevel=2) - return self._table_row_indices[-1] diff --git a/docs/lite/api/_ext/customdocumenter.txt b/docs/lite/api/_ext/customdocumenter.txt index 2d37ae41f6772a21da2a7dc5c7bff75128e68330..505ed7c57e6d7a30d106ca7a33111df6a8278ad3 100644 --- a/docs/lite/api/_ext/customdocumenter.txt +++ b/docs/lite/api/_ext/customdocumenter.txt @@ -8,7 +8,7 @@ class CustomDocumenter(Documenter): def document_members(self, all_members: bool = False) -> None: """Generate reST for member documentation. - If *all_members* is True, do all members, else those given by + If *all_members* is True, document all members, else those given by *self.options.members*. """ # set current namespace for finding members @@ -16,8 +16,9 @@ class CustomDocumenter(Documenter): if self.objpath: self.env.temp_data['autodoc:class'] = self.objpath[0] - want_all = all_members or self.options.inherited_members or \ - self.options.members is ALL + want_all = (all_members or + self.options.inherited_members or + self.options.members is ALL) # find out which members are documentable members_check_module, members = self.get_object_members(want_all) @@ -59,7 +60,7 @@ class CustomDocumenter(Documenter): ] # document non-skipped members - memberdocumenters = [] # type: List[Tuple[Documenter, bool]] + memberdocumenters: list[tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -111,7 +112,7 @@ class CustomDocumenter(Documenter): if not self.parse_name(): # need a module to import logger.warning( - __('don\'t know which module to import for autodocumenting ' + __("don't know which module to import for autodocumenting " '%r (try placing a "module" or "currentmodule" directive ' 'in the document, or giving an explicit module name)') % self.name, type='autodoc') @@ -126,7 +127,8 @@ class CustomDocumenter(Documenter): # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. - self.real_modname = real_modname or self.get_real_modname() # type: str + guess_modname = self.get_real_modname() + self.real_modname: str = real_modname or guess_modname # try to also get a source code analyzer for attribute docs try: @@ -134,15 +136,28 @@ class CustomDocumenter(Documenter): # parse right now, to get PycodeErrors on parsing (results will # be cached anyway) self.analyzer.find_attr_docs() - except PycodeError as err: - logger.debug('[autodoc] module analyzer failed: %s', err) + except PycodeError as exc: + logger.debug('[autodoc] module analyzer failed: %s', exc) # no source file -- e.g. for builtin and C modules self.analyzer = None # at least add the module.__file__ as a dependency if hasattr(self.module, '__file__') and self.module.__file__: - self.directive.filename_set.add(self.module.__file__) + self.directive.record_dependencies.add(self.module.__file__) else: - self.directive.filename_set.add(self.analyzer.srcname) + self.directive.record_dependencies.add(self.analyzer.srcname) + + if self.real_modname != guess_modname: + # Add module to dependency list if target object is defined in other module. + try: + analyzer = ModuleAnalyzer.for_module(guess_modname) + self.directive.record_dependencies.add(analyzer.srcname) + except PycodeError: + pass + + docstrings: list[str] = sum(self.get_doc() or [], []) + if ismock(self.object) and not docstrings: + logger.warning(__('A mocked object is detected: %r'), + self.name, type='autodoc') # check __module__ of object (for members not given explicitly) if check_module: @@ -160,29 +175,41 @@ class ModuleDocumenter(CustomDocumenter): objtype = 'module' content_indent = '' titles_allowed = True + _extra_indent = ' ' - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, 'platform': identity, 'deprecated': bool_option, - 'member-order': identity, 'exclude-members': members_set_option, - 'private-members': bool_option, 'special-members': members_option, - 'imported-members': bool_option, 'ignore-module-all': bool_option - } # type: Dict[str, Callable] + 'member-order': member_order_option, 'exclude-members': exclude_members_option, + 'private-members': members_option, 'special-members': members_option, + 'imported-members': bool_option, 'ignore-module-all': bool_option, + 'no-value': bool_option, + } def __init__(self, *args: Any) -> None: super().__init__(*args) merge_members_option(self.options) + self.__all__: Sequence[str] | None = None + + def add_content(self, more_content: StringList | None) -> None: + old_indent = self.indent + self.indent += self._extra_indent + super().add_content(None) + self.indent = old_indent + if more_content: + for line, src in zip(more_content.data, more_content.items): + self.add_line(line, src[0], src[1]) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: # don't document submodules automatically return False - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: if modname is not None: logger.warning(__('"::" in automodule name doesn\'t make sense'), type='autodoc') @@ -196,6 +223,20 @@ class ModuleDocumenter(CustomDocumenter): type='autodoc') return ret + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + + try: + if not self.options.ignore_module_all: + self.__all__ = inspect.getall(self.object) + except ValueError as exc: + # invalid __all__ found. + logger.warning(__('__all__ should be a list of strings, not %r ' + '(in module %s) -- ignoring __all__') % + (exc.args[0], self.fullname), type='autodoc') + + return ret + def add_directive_header(self, sig: str) -> None: Documenter.add_directive_header(self, sig) @@ -209,37 +250,57 @@ class ModuleDocumenter(CustomDocumenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) - def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, object]]]: + def get_module_members(self) -> dict[str, ObjectMember]: + """Get members of target module.""" + if self.analyzer: + attr_docs = self.analyzer.attr_docs + else: + attr_docs = {} + + members: dict[str, ObjectMember] = {} + for name in dir(self.object): + try: + value = safe_getattr(self.object, name, None) + if ismock(value): + value = undecorate(value) + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) + except AttributeError: + continue + + # annotation only member (ex. attr: int) + for name in inspect.getannotations(self.object): + if name not in members: + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, INSTANCEATTR, + docstring="\n".join(docstring)) + + return members + + def get_object_members(self, want_all: bool) -> tuple[bool, ObjectMembers]: + members = self.get_module_members() if want_all: if (self.options.ignore_module_all or not hasattr(self.object, '__all__')): # for implicit module members, check __module__ to avoid # documenting imported objects - return True, get_module_members(self.object) + return True, list(members.values()) else: - memberlist = self.object.__all__ - # Sometimes __all__ is broken... - if not isinstance(memberlist, (list, tuple)) or not \ - all(isinstance(entry, str) for entry in memberlist): - logger.warning( - __('__all__ should be a list of strings, not %r ' - '(in module %s) -- ignoring __all__') % - (memberlist, self.fullname), - type='autodoc' - ) - # fall back to all members - return True, get_module_members(self.object) + for member in members.values(): + if member.__name__ not in self.__all__: + member.skipped = True + + # fall back to all members + return True, list(members.values()) else: memberlist = self.options.members or [] - ret = [] - for mname in memberlist: - try: - ret.append((mname, safe_getattr(self.object, mname))) - except AttributeError: - logger.warning( - __('missing attribute mentioned in :members: or __all__: ' - 'module %s, attribute %s') % - (safe_getattr(self.object, '__name__', '???'), mname), - type='autodoc' - ) - return False, ret + ret = [] + for name in memberlist: + if name in members: + ret.append(members[name]) + else: + logger.warning(__('missing attribute mentioned in :members: option: ' + 'module %s, attribute %s') % + (safe_getattr(self.object, '__name__', '???'), name), + type='autodoc') + return False, ret diff --git a/docs/lite/api/_ext/overwriteautosummary_generate.txt b/docs/lite/api/_ext/overwriteautosummary_generate.txt index 4b0a1b1dd2b410ecab971b13da9993c90d65ef0d..23a4c166e123413b21d42f4eeab1accbc09b4896 100644 --- a/docs/lite/api/_ext/overwriteautosummary_generate.txt +++ b/docs/lite/api/_ext/overwriteautosummary_generate.txt @@ -1,22 +1,19 @@ -""" - sphinx.ext.autosummary.generate - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Usable as a library or script to generate automatic RST source files for - items referred to in autosummary:: directives. +"""Generates reST source files for autosummary. - Each generated RST file contains a single auto*:: directive which - extracts the docstring of the referred item. +Usable as a library or script to generate automatic RST source files for +items referred to in autosummary:: directives. - Example Makefile rule:: +Each generated RST file contains a single auto*:: directive which +extracts the docstring of the referred item. - generate: - sphinx-autogen -o source/generated source/*.rst +Example Makefile rule:: - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. + generate: + sphinx-autogen -o source/generated source/*.rst """ +from __future__ import annotations + import argparse import importlib import inspect @@ -26,10 +23,8 @@ import pkgutil import pydoc import re import sys -import warnings -from gettext import NullTranslations from os import path -from typing import Any, Dict, List, NamedTuple, Sequence, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, NamedTuple, Sequence from jinja2 import TemplateNotFound from jinja2.sandbox import SandboxedEnvironment @@ -39,11 +34,14 @@ from sphinx import __display_version__, package_dir from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc.importer import import_module -from sphinx.ext.autosummary import (ImportExceptionGroup, get_documenter, import_by_name, - import_ivar_by_name) +from sphinx.ext.autosummary import ( + ImportExceptionGroup, + get_documenter, + import_by_name, + import_ivar_by_name, +) from sphinx.locale import __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.registry import SphinxComponentRegistry @@ -52,6 +50,9 @@ from sphinx.util.inspect import getall, safe_getattr from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxTemplateLoader +if TYPE_CHECKING: + from gettext import NullTranslations + logger = logging.getLogger(__name__) @@ -61,7 +62,7 @@ class DummyApplication: def __init__(self, translator: NullTranslations) -> None: self.config = Config() self.registry = SphinxComponentRegistry() - self.messagelog: List[str] = [] + self.messagelog: list[str] = [] self.srcdir = "/" self.translator = translator self.verbosity = 0 @@ -91,32 +92,26 @@ class AutosummaryEntry(NamedTuple): def setup_documenters(app: Any) -> None: - from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter, DataDocumenter, - DecoratorDocumenter, ExceptionDocumenter, - FunctionDocumenter, MethodDocumenter, ModuleDocumenter, - NewTypeAttributeDocumenter, NewTypeDataDocumenter, - PropertyDocumenter) - documenters: List[Type[Documenter]] = [ + from sphinx.ext.autodoc import ( + AttributeDocumenter, + ClassDocumenter, + DataDocumenter, + DecoratorDocumenter, + ExceptionDocumenter, + FunctionDocumenter, + MethodDocumenter, + ModuleDocumenter, + PropertyDocumenter, + ) + documenters: list[type[Documenter]] = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, - FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter, - NewTypeDataDocumenter, AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, + FunctionDocumenter, MethodDocumenter, + AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, ] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) -def _simple_info(msg: str) -> None: - warnings.warn('_simple_info() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print(msg) - - -def _simple_warn(msg: str) -> None: - warnings.warn('_simple_warn() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print('WARNING: ' + msg, file=sys.stderr) - - def _underline(title: str, line: str = '=') -> str: if '\n' in title: raise ValueError('Can only underline single lines') @@ -126,14 +121,9 @@ def _underline(title: str, line: str = '=') -> str: class AutosummaryRenderer: """A helper class for rendering.""" - def __init__(self, app: Union[Builder, Sphinx], template_dir: str = None) -> None: + def __init__(self, app: Sphinx) -> None: if isinstance(app, Builder): - warnings.warn('The first argument for AutosummaryRenderer has been ' - 'changed to Sphinx object', - RemovedInSphinx50Warning, stacklevel=2) - if template_dir: - warnings.warn('template_dir argument for AutosummaryRenderer is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) + raise ValueError('Expected a Sphinx application object!') system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path, @@ -144,26 +134,11 @@ class AutosummaryRenderer: self.env.filters['e'] = rst.escape self.env.filters['underline'] = _underline - if isinstance(app, (Sphinx, DummyApplication)): - if app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.translator) - elif isinstance(app, Builder): - if app.app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.app.translator) - - def exists(self, template_name: str) -> bool: - """Check if template file exists.""" - warnings.warn('AutosummaryRenderer.exists() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - self.env.get_template(template_name) - return True - except TemplateNotFound: - return False + if app.translator: + self.env.add_extension("jinja2.ext.i18n") + self.env.install_gettext_translations(app.translator) - def render(self, template_name: str, context: Dict) -> str: + def render(self, template_name: str, context: dict) -> str: """Render a template file.""" try: template = self.env.get_template(template_name) @@ -199,8 +174,14 @@ class ModuleScanner: name, exc, type='autosummary') return False - def scan(self, imported_members: bool) -> List[str]: + def scan(self, imported_members: bool) -> list[str]: members = [] + try: + analyzer = ModuleAnalyzer.for_module(self.object.__name__) + attr_docs = analyzer.find_attr_docs() + except PycodeError: + attr_docs = {} + for name in members_of(self.object, self.app.config): try: value = safe_getattr(self.object, name) @@ -212,7 +193,9 @@ class ModuleScanner: continue try: - if inspect.ismodule(value): + if ('', name) in attr_docs: + imported = False + elif inspect.ismodule(value): # NoQA: SIM114 imported = True elif safe_getattr(value, '__module__') != self.object.__name__: imported = True @@ -222,14 +205,14 @@ class ModuleScanner: imported = False respect_module_all = not self.app.config.autosummary_ignore_module_all - if imported_members: + if ( # list all members up - members.append(name) - elif imported is False: + imported_members # list not-imported members - members.append(name) - elif '__all__' in dir(self.object) and respect_module_all: + or imported is False # list members that have __all__ set + or (respect_module_all and '__all__' in dir(self.object)) + ): members.append(name) return members @@ -249,8 +232,9 @@ def members_of(obj: Any, conf: Config) -> Sequence[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, qualname: str = None) -> str: + recursive: bool, context: dict, + modname: str | None = None, + qualname: str | None = None) -> str: doc = get_documenter(app, obj, parent) def skip_member(obj: Any, name: str, objtype: str) -> bool: @@ -263,11 +247,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, name, exc, type='autosummary') return False - def get_class_members(obj: Any) -> Dict[str, Any]: + def get_class_members(obj: Any) -> dict[str, Any]: members = sphinx.ext.autodoc.get_class_members(obj, [qualname], safe_getattr) return {name: member.object for name, member in members.items()} - def get_module_members(obj: Any) -> Dict[str, Any]: + def get_module_members(obj: Any) -> dict[str, Any]: members = {} for name in members_of(obj, app.config): try: @@ -276,17 +260,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, continue return members - def get_all_members(obj: Any) -> Dict[str, Any]: + def get_all_members(obj: Any) -> dict[str, Any]: if doc.objtype == "module": return get_module_members(obj) elif doc.objtype == "class": return get_class_members(obj) return {} - def get_members(obj: Any, types: Set[str], include_public: List[str] = [], - imported: bool = True) -> Tuple[List[str], List[str]]: - items: List[str] = [] - public: List[str] = [] + def get_members(obj: Any, types: set[str], include_public: list[str] = [], + imported: bool = True) -> tuple[list[str], list[str]]: + items: list[str] = [] + public: list[str] = [] all_members = get_all_members(obj) for name, value in all_members.items(): @@ -308,7 +292,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, public.append(name) return public, items - def get_module_attrs(members: Any) -> Tuple[List[str], List[str]]: + def get_module_attrs(members: Any) -> tuple[list[str], list[str]]: """Find module attributes with docstrings.""" attrs, public = [], [] try: @@ -323,9 +307,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, pass # give up if ModuleAnalyzer fails to parse code return public, attrs - def get_modules(obj: Any) -> Tuple[List[str], List[str]]: - items: 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) @@ -335,15 +327,24 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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] = {} + ns: dict[str, Any] = {} ns.update(context) 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'] = \ @@ -354,7 +355,36 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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'] = \ @@ -389,21 +419,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, return template.render(doc.objtype, ns) -def generate_autosummary_docs(sources: List[str], output_dir: str = None, - suffix: str = '.rst', base_path: str = None, - builder: Builder = None, template_dir: str = None, +def generate_autosummary_docs(sources: list[str], output_dir: str | None = None, + suffix: str = '.rst', base_path: str | None = None, imported_members: bool = False, app: Any = None, overwrite: bool = True, encoding: str = 'utf-8') -> None: - - if builder: - warnings.warn('builder argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - if template_dir: - warnings.warn('template_dir argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - showed_sources = list(sorted(sources)) + showed_sources = sorted(sources) if len(showed_sources) > 20: showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:] logger.info(__('[autosummary] generating autosummary for: %s') % @@ -439,7 +459,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, ensuredir(path) try: - name, obj, parent, modname = import_by_name(entry.name, grouped_exception=True) + name, obj, parent, modname = import_by_name(entry.name) qualname = name.replace(modname + ".", "") except ImportExceptionGroup as exc: try: @@ -448,16 +468,16 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, qualname = name.replace(modname + ".", "") except ImportError as exc2: if exc2.__cause__: - exceptions: List[BaseException] = exc.exceptions + [exc2.__cause__] + exceptions: list[BaseException] = exc.exceptions + [exc2.__cause__] else: exceptions = exc.exceptions + [exc2] - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exceptions)) + 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] = {} + context: dict[str, Any] = {} if app: context.update(app.config.autosummary_context) @@ -484,7 +504,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if content == old_content: continue - elif overwrite: # content has changed + if overwrite: # content has changed with open(filename, 'w', encoding=encoding) as f: f.write(content) new_files.append(filename) @@ -497,19 +517,18 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if new_files: generate_autosummary_docs(new_files, output_dir=output_dir, suffix=suffix, base_path=base_path, - builder=builder, template_dir=template_dir, imported_members=imported_members, app=app, overwrite=overwrite) # -- Finding documented entries in files --------------------------------------- -def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: +def find_autosummary_in_files(filenames: list[str]) -> list[AutosummaryEntry]: """Find out what items are documented in source/*.rst. See `find_autosummary_in_lines`. """ - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] for filename in filenames: with open(filename, encoding='utf-8', errors='ignore') as f: lines = f.read().splitlines() @@ -517,33 +536,31 @@ def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: return documented -def find_autosummary_in_docstring(name: str, module: str = None, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_docstring( + name: str, filename: str | None = None, +) -> list[AutosummaryEntry]: """Find out what items are documented in the given object's docstring. See `find_autosummary_in_lines`. """ - if module: - warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - real_name, obj, parent, modname = import_by_name(name, grouped_exception=True) + real_name, obj, parent, modname = import_by_name(name) lines = pydoc.getdoc(obj).splitlines() return find_autosummary_in_lines(lines, module=name, filename=filename) except AttributeError: pass except ImportExceptionGroup as exc: - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exc.exceptions)) - print('Failed to import %s.\nPossible hints:\n%s' % (name, '\n'.join(errors))) + errors = '\n'.join({f"* {type(e).__name__}: {e}" for e in exc.exceptions}) + print(f'Failed to import {name}.\nPossible hints:\n{errors}') except SystemExit: print("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, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_lines( + 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. @@ -564,10 +581,10 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$') - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] recursive = False - toctree: str = None + toctree: str | None = None template = None current_module = module in_autosummary = False @@ -603,7 +620,7 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st name = name[1:] if current_module and \ not name.startswith(current_module + '.'): - name = "%s.%s" % (current_module, name) + name = f"{current_module}.{name}" documented.append(AutosummaryEntry(name, toctree, template, recursive)) continue @@ -683,12 +700,11 @@ The format of the autosummary directive is documented in the return parser -def main(argv: List[str] = sys.argv[1:]) -> None: - sphinx.locale.setlocale(locale.LC_ALL, '') - sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') - translator, _ = sphinx.locale.init([], None) +def main(argv: list[str] = sys.argv[1:]) -> None: + locale.setlocale(locale.LC_ALL, '') + sphinx.locale.init_console() - app = DummyApplication(translator) + app = DummyApplication(sphinx.locale.get_translator()) logging.setup(app, sys.stdout, sys.stderr) # type: ignore setup_documenters(app) args = get_parser().parse_args(argv) diff --git a/docs/lite/api/_ext/overwriteobjectiondirective.txt b/docs/lite/api/_ext/overwriteobjectiondirective.txt index f77828412afd6da7f8c14b04fa044e23eb6855a9..669be7eb3030776b9c632c09002d9016779e9c07 100644 --- a/docs/lite/api/_ext/overwriteobjectiondirective.txt +++ b/docs/lite/api/_ext/overwriteobjectiondirective.txt @@ -1,17 +1,11 @@ -""" - sphinx.directives - ~~~~~~~~~~~~~~~~~ +"""Handlers for additional ReST directives.""" - Handlers for additional ReST directives. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import re import inspect import importlib -from typing import TYPE_CHECKING, Any, Dict, Generic, List, Tuple, TypeVar, cast +from typing import TYPE_CHECKING, Any, Generic, List, TypeVar, cast from docutils import nodes from docutils.nodes import Node @@ -19,10 +13,10 @@ from docutils.parsers.rst import directives, roles from sphinx import addnodes from sphinx.addnodes import desc_signature -from sphinx.deprecation import RemovedInSphinx50Warning, deprecated_alias from sphinx.util import docutils, logging from sphinx.util.docfields import DocFieldTransformer, Field, TypedField from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.typing import OptionSpec if TYPE_CHECKING: @@ -33,10 +27,11 @@ if TYPE_CHECKING: nl_escape_re = re.compile(r'\\\n') strip_backslash_re = re.compile(r'\\(.)') -T = TypeVar('T') +ObjDescT = TypeVar('ObjDescT') + logger = logging.getLogger(__name__) -def optional_int(argument: str) -> int: +def optional_int(argument: str) -> int | None: """ Check for an integer argument or None value; raise ``ValueError`` if not. """ @@ -84,23 +79,23 @@ def get_example(name: str): def get_platforms(name: str): try: api_doc = inspect.getdoc(get_api(name)) - example_str = re.findall(r'Supported Platforms:\n\s+(.*?)\n\n', api_doc) - if not example_str: - example_str_leak = re.findall(r'Supported Platforms:\n\s+(.*)', api_doc) - if example_str_leak: - example_str = example_str_leak[0].strip() - example_list = example_str.split('\n') - example_list = [' ' + example_list[0]] - return ["", "支持平台:"] + example_list + [""] + platform_str = re.findall(r'Supported Platforms:\n\s+(.*?)\n\n', api_doc) + if not platform_str: + platform_str_leak = re.findall(r'Supported Platforms:\n\s+(.*)', api_doc) + if platform_str_leak: + platform_str = platform_str_leak[0].strip() + platform_list = platform_str.split('\n') + platform_list = [' ' + platform_list[0]] + return ["", "支持平台:"] + platform_list + [""] return [] - example_str = example_str[0].strip() - example_list = example_str.split('\n') - example_list = [' ' + example_list[0]] - return ["", "支持平台:"] + example_list + [""] + platform_str = platform_str[0].strip() + platform_list = platform_str.split('\n') + platform_list = [' ' + platform_list[0]] + return ["", "支持平台:"] + platform_list + [""] except: return [] -class ObjectDescription(SphinxDirective, Generic[T]): +class ObjectDescription(SphinxDirective, Generic[ObjDescT]): """ Directive to describe a class, function or similar object. Not used directly, but subclassed (in domain-specific directives) to add custom @@ -113,18 +108,20 @@ class ObjectDescription(SphinxDirective, Generic[T]): final_argument_whitespace = True option_spec: OptionSpec = { 'noindex': directives.flag, - } # type: Dict[str, DirectiveOption] + 'noindexentry': directives.flag, + 'nocontentsentry': directives.flag, + } # types of doc fields that this directive handles, see sphinx.util.docfields - doc_field_types: List[Field] = [] - domain: str = None - objtype: str = None - indexnode: addnodes.index = None + doc_field_types: list[Field] = [] + domain: str | None = None + objtype: str # set when `run` method is called + indexnode: addnodes.index # Warning: this might be removed in future version. Don't touch this from extensions. - _doc_field_type_map = {} # type: Dict[str, Tuple[Field, bool]] + _doc_field_type_map: dict[str, tuple[Field, bool]] = {} - def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]: + def get_field_type_map(self) -> dict[str, tuple[Field, bool]]: if self._doc_field_type_map == {}: self._doc_field_type_map = {} for field in self.doc_field_types: @@ -138,12 +135,10 @@ class ObjectDescription(SphinxDirective, Generic[T]): return self._doc_field_type_map - def get_signatures(self) -> List[str]: + def get_signatures(self) -> list[str]: """ Retrieve the signatures to document from the directive arguments. By default, signatures are given as arguments, one per line. - - Backslash-escaping of newlines is supported. """ lines = nl_escape_re.sub('', self.arguments[0]).split('\n') if self.config.strip_signature_backslash: @@ -152,7 +147,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): else: return [line.strip() for line in lines] - def handle_signature(self, sig: str, signode: desc_signature) -> Any: + def handle_signature(self, sig: str, signode: desc_signature) -> ObjDescT: """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -164,7 +159,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): """ raise ValueError - def add_target_and_index(self, name: Any, sig: str, signode: desc_signature) -> None: + def add_target_and_index(self, name: ObjDescT, sig: str, signode: desc_signature) -> None: """ Add cross-reference IDs and entries to self.indexnode, if applicable. @@ -195,6 +190,44 @@ class ObjectDescription(SphinxDirective, Generic[T]): """ pass + def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]: + """ + Returns a tuple of strings, one entry for each part of the object's + hierarchy (e.g. ``('module', 'submodule', 'Class', 'method')``). The + returned tuple is used to properly nest children within parents in the + table of contents, and can also be used within the + :py:meth:`_toc_entry_name` method. + + This method must not be used outwith table of contents generation. + """ + return () + + def _toc_entry_name(self, sig_node: desc_signature) -> str: + """ + Returns the text of the table of contents entry for the object. + + This function is called once, in :py:meth:`run`, to set the name for the + table of contents entry (a special attribute ``_toc_name`` is set on the + object node, later used in + ``environment.collectors.toctree.TocTreeCollector.process_doc().build_toc()`` + when the table of contents entries are collected). + + To support table of contents entries for their objects, domains must + override this method, also respecting the configuration setting + ``toc_object_entries_show_parents``. Domains must also override + :py:meth:`_object_hierarchy_parts`, with one (string) entry for each part of the + object's hierarchy. The result of this method is set on the signature + node, and can be accessed as ``sig_node['_toc_parts']`` for use within + this method. The resulting tuple is also used to properly nest children + within parents in the table of contents. + + An example implementations of this method is within the python domain + (:meth:`!PyObject._toc_entry_name`). The python domain sets the + ``_toc_parts`` attribute within the :py:meth:`handle_signature()` + method. + """ + return '' + def check_class_end(self, content): for i in content: if not i.startswith('.. include::') and i != "\n" and i != "": @@ -207,7 +240,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): ls.append((rst_file, start_num+i)) return ls - def run(self) -> List[Node]: + def run(self) -> list[Node]: """ Main directive entry function, called by docutils upon encountering the directive. @@ -233,15 +266,28 @@ class ObjectDescription(SphinxDirective, Generic[T]): node = addnodes.desc() node.document = self.state.document + source, line = self.get_source_info() + # If any options were specified to the directive, + # self.state.document.current_line will at this point be set to + # None. To ensure nodes created as part of the signature have a line + # number set, set the document's line number correctly. + # + # Note that we need to subtract one from the line number since + # note_source uses 0-based line numbers. + if line is not None: + line -= 1 + self.state.document.note_source(source, line) node['domain'] = self.domain # 'desctype' is a backwards compatible attribute node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) + node['noindexentry'] = ('noindexentry' in self.options) + node['nocontentsentry'] = ('nocontentsentry' in self.options) if self.domain: node['classes'].append(self.domain) node['classes'].append(node['objtype']) - self.names: List[T] = [] + self.names: list[ObjDescT] = [] signatures = self.get_signatures() for sig in signatures: # add a signature node for each signature in the current unit @@ -259,6 +305,15 @@ class ObjectDescription(SphinxDirective, Generic[T]): signode.clear() signode += addnodes.desc_name(sig, sig) continue # we don't want an index entry here + finally: + # Private attributes for ToC generation. Will be modified or removed + # without notice. + if self.env.app.config.toc_object_entries: + signode['_toc_parts'] = self._object_hierarchy_parts(signode) + signode['_toc_name'] = self._toc_entry_name(signode) + else: + signode['_toc_parts'] = () + signode['_toc_name'] = '' if name not in self.names: self.names.append(name) if not noindex: @@ -268,6 +323,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): contentnode = addnodes.desc_content() node.append(contentnode) + if self.names: # needed for association of version{added,changed} directives self.env.temp_data['object'] = self.names[0] @@ -300,7 +356,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): self.content.items.extend(self.extend_items(self.content.items[0][0], self.content.items[-1][1], len(extra))) except Exception as e: logger.warning(f'{e}') - self.state.nested_parse(self.content, self.content_offset, contentnode) + nested_parse_with_titles(self.state, self.content, contentnode, self.content_offset) self.transform_content(contentnode) self.env.app.emit('object-description-transform', self.domain, self.objtype, contentnode) @@ -318,14 +374,14 @@ class DefaultRole(SphinxDirective): optional_arguments = 1 final_argument_whitespace = False - def run(self) -> List[Node]: + def run(self) -> list[Node]: if not self.arguments: docutils.unregister_role('') return [] role_name = self.arguments[0] role, messages = roles.role(role_name, self.state_machine.language, self.lineno, self.state.reporter) - if role: + if role: # type: ignore[truthy-function] docutils.register_role('', role) self.env.temp_data['default_role'] = role_name else: @@ -347,9 +403,9 @@ class DefaultDomain(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} # type: Dict + option_spec: OptionSpec = {} - def run(self) -> List[Node]: + def run(self) -> list[Node]: domain_name = self.arguments[0].lower() # if domain_name not in env.domains: # # try searching by label @@ -360,7 +416,8 @@ class DefaultDomain(SphinxDirective): self.env.temp_data['default_domain'] = self.env.domains.get(domain_name) return [] -def setup(app: "Sphinx") -> Dict[str, Any]: + +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value("strip_signature_backslash", False, 'env') directives.register_directive('default-role', DefaultRole) directives.register_directive('default-domain', DefaultDomain) diff --git a/docs/lite/api/_ext/overwriteviewcode.txt b/docs/lite/api/_ext/overwriteviewcode.txt index 172780ec56b3ed90e7b0add617257a618cf38ee0..a2fdcfb787bb99e6d96742574675f79151a4848d 100644 --- a/docs/lite/api/_ext/overwriteviewcode.txt +++ b/docs/lite/api/_ext/overwriteviewcode.txt @@ -1,18 +1,12 @@ -""" - sphinx.ext.viewcode - ~~~~~~~~~~~~~~~~~~~ +"""Add links to module code in Python object descriptions.""" - Add links to module code in Python object descriptions. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import posixpath import traceback import warnings from os import path -from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast +from typing import Any, Generator, Iterable, cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,12 +16,12 @@ from sphinx import addnodes from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer from sphinx.transforms.post_transforms import SphinxPostTransform -from sphinx.util import get_full_modname, logging, status_iterator +from sphinx.util import get_full_modname, logging +from sphinx.util.display import status_iterator from sphinx.util.nodes import make_refnode @@ -46,13 +40,13 @@ class viewcode_anchor(Element): """ -def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]: +def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str | None: try: return get_full_modname(modname, attribute) except AttributeError: # sphinx.ext.viewcode can't follow class instance attribute # then AttributeError logging output only verbose mode. - logger.verbose('Didn\'t find %s in %s', attribute, modname) + logger.verbose("Didn't find %s in %s", attribute, modname) return None except Exception as e: # sphinx.ext.viewcode follow python domain directives. @@ -67,12 +61,11 @@ def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str def is_supported_builder(builder: Builder) -> bool: if builder.format != 'html': return False - elif builder.name == 'singlehtml': + if builder.name == 'singlehtml': return False - elif builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: + if builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: return False - else: - return True + return True def doctree_read(app: Sphinx, doctree: Node) -> None: @@ -112,7 +105,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: for objnode in list(doctree.findall(addnodes.desc)): if objnode.get('domain') != 'py': continue - names: Set[str] = set() + names: set[str] = set() for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue @@ -211,19 +204,7 @@ class ViewcodeAnchorTransform(SphinxPostTransform): node.parent.remove(node) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node - ) -> Optional[Node]: - # resolve our "viewcode" reference nodes -- they need special treatment - if node['reftype'] == 'viewcode': - warnings.warn('viewcode extension is no longer use pending_xref node. ' - 'Please update your extension.', RemovedInSphinx50Warning) - return make_refnode(app.builder, node['refdoc'], node['reftarget'], - node['refid'], contnode) - - return None - - -def get_module_filename(app: Sphinx, modname: str) -> Optional[str]: +def get_module_filename(app: Sphinx, modname: str) -> str | None: """Get module filename for *modname*.""" source_info = app.emit_firstresult('viewcode-find-source', modname) if source_info: @@ -251,13 +232,13 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool: if path.getmtime(module_filename) <= path.getmtime(page_filename): # generation is not needed if the HTML page is newer than module file. return False - except IOError: + except OSError: pass return True -def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], None, None]: +def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], None, None]: env = app.builder.env if not hasattr(env, '_viewcode_modules'): return @@ -282,7 +263,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non # construct a page name for the highlighted source pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/')) # highlight the source using the builder's highlighter - if env.config.highlight_language in ('python3', 'default', 'none'): + if env.config.highlight_language in {'default', 'none'}: lexer = env.config.highlight_language else: lexer = 'python' @@ -343,10 +324,9 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non stack.pop() html.append('') stack.append(modname + '.') - html.append('
  • %s
  • \n' % ( - urito(posixpath.join(OUTPUT_DIRNAME, 'index'), - posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))), - modname)) + relative_uri = urito(posixpath.join(OUTPUT_DIRNAME, 'index'), + posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))) + html.append(f'
  • {modname}
  • \n') html.append('' * (len(stack) - 1)) context = { 'title': _('Overview: module code'), @@ -357,7 +337,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non yield (posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html') -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) @@ -365,7 +345,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('env-merge-info', env_merge_info) app.connect('env-purge-doc', env_purge_doc) app.connect('html-collect-pages', collect_pages) - app.connect('missing-reference', missing_reference) # app.add_config_value('viewcode_include_modules', [], 'env') # app.add_config_value('viewcode_exclude_modules', [], 'env') app.add_event('viewcode-find-source') @@ -374,5 +353,5 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': sphinx.__display_version__, 'env_version': 1, - 'parallel_read_safe': True + 'parallel_read_safe': True, } diff --git a/docs/lite/api/requirements.txt b/docs/lite/api/requirements.txt index 7b4ba238b6cb2cb3748fcca3126f487094e06c57..6bb4d15e79dca58f5b69645cf92306cd3c7911d0 100644 --- a/docs/lite/api/requirements.txt +++ b/docs/lite/api/requirements.txt @@ -1,7 +1,7 @@ -sphinx == 4.4.0 -docutils == 0.17.1 -myst-parser == 0.18.1 -sphinx_rtd_theme == 1.0.0 +sphinx == 7.0.1 +docutils == 0.20.1 +myst-parser == 2.0.0 +sphinx_rtd_theme == 3.0.0 exhale == 0.2.3 breathe == 4.35.0 numpy diff --git a/docs/lite/api/source_en/conf.py b/docs/lite/api/source_en/conf.py index 408e41a12c0d7928efab0d519b43b62841af0b91..d09f992a5b0f39372136b6ac82e5acbbbabfc174 100644 --- a/docs/lite/api/source_en/conf.py +++ b/docs/lite/api/source_en/conf.py @@ -247,12 +247,10 @@ exhale_args = { # Fix some dl-label lack class='simple' from docutils.writers import _html_base -with open(_html_base.__file__, "r+", encoding="utf-8") as f: +with open(_html_base.__file__, "r", encoding="utf-8") as f: code_str = f.read() - old_str = ''' if self.is_compactable(node): - classes.append('simple')''' - new_str = ''' if classes == []: - classes.append('simple')''' + old_str = "classes = ['simple'] if self.is_compactable(node) else []" + new_str = "classes = ['simple']" code_str = code_str.replace(old_str, new_str) exec(code_str, _html_base.__dict__) @@ -482,5 +480,5 @@ for file_name in fileList: # with open(file_name2, 'w', encoding='utf-8') as p: # p.write(file_data2) -sys.path.append(os.path.abspath('../../../../resource/search')) -import search_code +# sys.path.append(os.path.abspath('../../../../resource/search')) +# import search_code diff --git a/docs/lite/api/source_zh_cn/conf.py b/docs/lite/api/source_zh_cn/conf.py index 57ffe8f2c28ac4766e2d1f4ee27001f3582e1ea5..9544f4e2568e477236897f98666b4d0c87185522 100644 --- a/docs/lite/api/source_zh_cn/conf.py +++ b/docs/lite/api/source_zh_cn/conf.py @@ -99,12 +99,10 @@ from custom_directives import IncludeCodeDirective # Fix some dl-label lack class='simple' from docutils.writers import _html_base -with open(_html_base.__file__, "r+", encoding="utf-8") as f: +with open(_html_base.__file__, "r", encoding="utf-8") as f: code_str = f.read() - old_str = ''' if self.is_compactable(node): - classes.append('simple')''' - new_str = ''' if classes == []: - classes.append('simple')''' + old_str = "classes = ['simple'] if self.is_compactable(node) else []" + new_str = "classes = ['simple']" code_str = code_str.replace(old_str, new_str) exec(code_str, _html_base.__dict__) @@ -276,8 +274,8 @@ import mindspore_lite sys.path.append(os.path.abspath('../../../../resource/sphinx_ext')) # import anchor_mod -sys.path.append(os.path.abspath('../../../../resource/search')) -import search_code +# sys.path.append(os.path.abspath('../../../../resource/search')) +# import search_code diff --git a/docs/mindearth/docs/_ext/customdocumenter.txt b/docs/mindearth/docs/_ext/customdocumenter.txt index 2d37ae41f6772a21da2a7dc5c7bff75128e68330..505ed7c57e6d7a30d106ca7a33111df6a8278ad3 100644 --- a/docs/mindearth/docs/_ext/customdocumenter.txt +++ b/docs/mindearth/docs/_ext/customdocumenter.txt @@ -8,7 +8,7 @@ class CustomDocumenter(Documenter): def document_members(self, all_members: bool = False) -> None: """Generate reST for member documentation. - If *all_members* is True, do all members, else those given by + If *all_members* is True, document all members, else those given by *self.options.members*. """ # set current namespace for finding members @@ -16,8 +16,9 @@ class CustomDocumenter(Documenter): if self.objpath: self.env.temp_data['autodoc:class'] = self.objpath[0] - want_all = all_members or self.options.inherited_members or \ - self.options.members is ALL + want_all = (all_members or + self.options.inherited_members or + self.options.members is ALL) # find out which members are documentable members_check_module, members = self.get_object_members(want_all) @@ -59,7 +60,7 @@ class CustomDocumenter(Documenter): ] # document non-skipped members - memberdocumenters = [] # type: List[Tuple[Documenter, bool]] + memberdocumenters: list[tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -111,7 +112,7 @@ class CustomDocumenter(Documenter): if not self.parse_name(): # need a module to import logger.warning( - __('don\'t know which module to import for autodocumenting ' + __("don't know which module to import for autodocumenting " '%r (try placing a "module" or "currentmodule" directive ' 'in the document, or giving an explicit module name)') % self.name, type='autodoc') @@ -126,7 +127,8 @@ class CustomDocumenter(Documenter): # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. - self.real_modname = real_modname or self.get_real_modname() # type: str + guess_modname = self.get_real_modname() + self.real_modname: str = real_modname or guess_modname # try to also get a source code analyzer for attribute docs try: @@ -134,15 +136,28 @@ class CustomDocumenter(Documenter): # parse right now, to get PycodeErrors on parsing (results will # be cached anyway) self.analyzer.find_attr_docs() - except PycodeError as err: - logger.debug('[autodoc] module analyzer failed: %s', err) + except PycodeError as exc: + logger.debug('[autodoc] module analyzer failed: %s', exc) # no source file -- e.g. for builtin and C modules self.analyzer = None # at least add the module.__file__ as a dependency if hasattr(self.module, '__file__') and self.module.__file__: - self.directive.filename_set.add(self.module.__file__) + self.directive.record_dependencies.add(self.module.__file__) else: - self.directive.filename_set.add(self.analyzer.srcname) + self.directive.record_dependencies.add(self.analyzer.srcname) + + if self.real_modname != guess_modname: + # Add module to dependency list if target object is defined in other module. + try: + analyzer = ModuleAnalyzer.for_module(guess_modname) + self.directive.record_dependencies.add(analyzer.srcname) + except PycodeError: + pass + + docstrings: list[str] = sum(self.get_doc() or [], []) + if ismock(self.object) and not docstrings: + logger.warning(__('A mocked object is detected: %r'), + self.name, type='autodoc') # check __module__ of object (for members not given explicitly) if check_module: @@ -160,29 +175,41 @@ class ModuleDocumenter(CustomDocumenter): objtype = 'module' content_indent = '' titles_allowed = True + _extra_indent = ' ' - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, 'platform': identity, 'deprecated': bool_option, - 'member-order': identity, 'exclude-members': members_set_option, - 'private-members': bool_option, 'special-members': members_option, - 'imported-members': bool_option, 'ignore-module-all': bool_option - } # type: Dict[str, Callable] + 'member-order': member_order_option, 'exclude-members': exclude_members_option, + 'private-members': members_option, 'special-members': members_option, + 'imported-members': bool_option, 'ignore-module-all': bool_option, + 'no-value': bool_option, + } def __init__(self, *args: Any) -> None: super().__init__(*args) merge_members_option(self.options) + self.__all__: Sequence[str] | None = None + + def add_content(self, more_content: StringList | None) -> None: + old_indent = self.indent + self.indent += self._extra_indent + super().add_content(None) + self.indent = old_indent + if more_content: + for line, src in zip(more_content.data, more_content.items): + self.add_line(line, src[0], src[1]) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: # don't document submodules automatically return False - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: if modname is not None: logger.warning(__('"::" in automodule name doesn\'t make sense'), type='autodoc') @@ -196,6 +223,20 @@ class ModuleDocumenter(CustomDocumenter): type='autodoc') return ret + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + + try: + if not self.options.ignore_module_all: + self.__all__ = inspect.getall(self.object) + except ValueError as exc: + # invalid __all__ found. + logger.warning(__('__all__ should be a list of strings, not %r ' + '(in module %s) -- ignoring __all__') % + (exc.args[0], self.fullname), type='autodoc') + + return ret + def add_directive_header(self, sig: str) -> None: Documenter.add_directive_header(self, sig) @@ -209,37 +250,57 @@ class ModuleDocumenter(CustomDocumenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) - def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, object]]]: + def get_module_members(self) -> dict[str, ObjectMember]: + """Get members of target module.""" + if self.analyzer: + attr_docs = self.analyzer.attr_docs + else: + attr_docs = {} + + members: dict[str, ObjectMember] = {} + for name in dir(self.object): + try: + value = safe_getattr(self.object, name, None) + if ismock(value): + value = undecorate(value) + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) + except AttributeError: + continue + + # annotation only member (ex. attr: int) + for name in inspect.getannotations(self.object): + if name not in members: + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, INSTANCEATTR, + docstring="\n".join(docstring)) + + return members + + def get_object_members(self, want_all: bool) -> tuple[bool, ObjectMembers]: + members = self.get_module_members() if want_all: if (self.options.ignore_module_all or not hasattr(self.object, '__all__')): # for implicit module members, check __module__ to avoid # documenting imported objects - return True, get_module_members(self.object) + return True, list(members.values()) else: - memberlist = self.object.__all__ - # Sometimes __all__ is broken... - if not isinstance(memberlist, (list, tuple)) or not \ - all(isinstance(entry, str) for entry in memberlist): - logger.warning( - __('__all__ should be a list of strings, not %r ' - '(in module %s) -- ignoring __all__') % - (memberlist, self.fullname), - type='autodoc' - ) - # fall back to all members - return True, get_module_members(self.object) + for member in members.values(): + if member.__name__ not in self.__all__: + member.skipped = True + + # fall back to all members + return True, list(members.values()) else: memberlist = self.options.members or [] - ret = [] - for mname in memberlist: - try: - ret.append((mname, safe_getattr(self.object, mname))) - except AttributeError: - logger.warning( - __('missing attribute mentioned in :members: or __all__: ' - 'module %s, attribute %s') % - (safe_getattr(self.object, '__name__', '???'), mname), - type='autodoc' - ) - return False, ret + ret = [] + for name in memberlist: + if name in members: + ret.append(members[name]) + else: + logger.warning(__('missing attribute mentioned in :members: option: ' + 'module %s, attribute %s') % + (safe_getattr(self.object, '__name__', '???'), name), + type='autodoc') + return False, ret diff --git a/docs/mindearth/docs/_ext/overwriteautosummary_generate.txt b/docs/mindearth/docs/_ext/overwriteautosummary_generate.txt index 4b0a1b1dd2b410ecab971b13da9993c90d65ef0d..23a4c166e123413b21d42f4eeab1accbc09b4896 100644 --- a/docs/mindearth/docs/_ext/overwriteautosummary_generate.txt +++ b/docs/mindearth/docs/_ext/overwriteautosummary_generate.txt @@ -1,22 +1,19 @@ -""" - sphinx.ext.autosummary.generate - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Usable as a library or script to generate automatic RST source files for - items referred to in autosummary:: directives. +"""Generates reST source files for autosummary. - Each generated RST file contains a single auto*:: directive which - extracts the docstring of the referred item. +Usable as a library or script to generate automatic RST source files for +items referred to in autosummary:: directives. - Example Makefile rule:: +Each generated RST file contains a single auto*:: directive which +extracts the docstring of the referred item. - generate: - sphinx-autogen -o source/generated source/*.rst +Example Makefile rule:: - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. + generate: + sphinx-autogen -o source/generated source/*.rst """ +from __future__ import annotations + import argparse import importlib import inspect @@ -26,10 +23,8 @@ import pkgutil import pydoc import re import sys -import warnings -from gettext import NullTranslations from os import path -from typing import Any, Dict, List, NamedTuple, Sequence, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, NamedTuple, Sequence from jinja2 import TemplateNotFound from jinja2.sandbox import SandboxedEnvironment @@ -39,11 +34,14 @@ from sphinx import __display_version__, package_dir from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc.importer import import_module -from sphinx.ext.autosummary import (ImportExceptionGroup, get_documenter, import_by_name, - import_ivar_by_name) +from sphinx.ext.autosummary import ( + ImportExceptionGroup, + get_documenter, + import_by_name, + import_ivar_by_name, +) from sphinx.locale import __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.registry import SphinxComponentRegistry @@ -52,6 +50,9 @@ from sphinx.util.inspect import getall, safe_getattr from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxTemplateLoader +if TYPE_CHECKING: + from gettext import NullTranslations + logger = logging.getLogger(__name__) @@ -61,7 +62,7 @@ class DummyApplication: def __init__(self, translator: NullTranslations) -> None: self.config = Config() self.registry = SphinxComponentRegistry() - self.messagelog: List[str] = [] + self.messagelog: list[str] = [] self.srcdir = "/" self.translator = translator self.verbosity = 0 @@ -91,32 +92,26 @@ class AutosummaryEntry(NamedTuple): def setup_documenters(app: Any) -> None: - from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter, DataDocumenter, - DecoratorDocumenter, ExceptionDocumenter, - FunctionDocumenter, MethodDocumenter, ModuleDocumenter, - NewTypeAttributeDocumenter, NewTypeDataDocumenter, - PropertyDocumenter) - documenters: List[Type[Documenter]] = [ + from sphinx.ext.autodoc import ( + AttributeDocumenter, + ClassDocumenter, + DataDocumenter, + DecoratorDocumenter, + ExceptionDocumenter, + FunctionDocumenter, + MethodDocumenter, + ModuleDocumenter, + PropertyDocumenter, + ) + documenters: list[type[Documenter]] = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, - FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter, - NewTypeDataDocumenter, AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, + FunctionDocumenter, MethodDocumenter, + AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, ] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) -def _simple_info(msg: str) -> None: - warnings.warn('_simple_info() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print(msg) - - -def _simple_warn(msg: str) -> None: - warnings.warn('_simple_warn() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print('WARNING: ' + msg, file=sys.stderr) - - def _underline(title: str, line: str = '=') -> str: if '\n' in title: raise ValueError('Can only underline single lines') @@ -126,14 +121,9 @@ def _underline(title: str, line: str = '=') -> str: class AutosummaryRenderer: """A helper class for rendering.""" - def __init__(self, app: Union[Builder, Sphinx], template_dir: str = None) -> None: + def __init__(self, app: Sphinx) -> None: if isinstance(app, Builder): - warnings.warn('The first argument for AutosummaryRenderer has been ' - 'changed to Sphinx object', - RemovedInSphinx50Warning, stacklevel=2) - if template_dir: - warnings.warn('template_dir argument for AutosummaryRenderer is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) + raise ValueError('Expected a Sphinx application object!') system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path, @@ -144,26 +134,11 @@ class AutosummaryRenderer: self.env.filters['e'] = rst.escape self.env.filters['underline'] = _underline - if isinstance(app, (Sphinx, DummyApplication)): - if app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.translator) - elif isinstance(app, Builder): - if app.app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.app.translator) - - def exists(self, template_name: str) -> bool: - """Check if template file exists.""" - warnings.warn('AutosummaryRenderer.exists() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - self.env.get_template(template_name) - return True - except TemplateNotFound: - return False + if app.translator: + self.env.add_extension("jinja2.ext.i18n") + self.env.install_gettext_translations(app.translator) - def render(self, template_name: str, context: Dict) -> str: + def render(self, template_name: str, context: dict) -> str: """Render a template file.""" try: template = self.env.get_template(template_name) @@ -199,8 +174,14 @@ class ModuleScanner: name, exc, type='autosummary') return False - def scan(self, imported_members: bool) -> List[str]: + def scan(self, imported_members: bool) -> list[str]: members = [] + try: + analyzer = ModuleAnalyzer.for_module(self.object.__name__) + attr_docs = analyzer.find_attr_docs() + except PycodeError: + attr_docs = {} + for name in members_of(self.object, self.app.config): try: value = safe_getattr(self.object, name) @@ -212,7 +193,9 @@ class ModuleScanner: continue try: - if inspect.ismodule(value): + if ('', name) in attr_docs: + imported = False + elif inspect.ismodule(value): # NoQA: SIM114 imported = True elif safe_getattr(value, '__module__') != self.object.__name__: imported = True @@ -222,14 +205,14 @@ class ModuleScanner: imported = False respect_module_all = not self.app.config.autosummary_ignore_module_all - if imported_members: + if ( # list all members up - members.append(name) - elif imported is False: + imported_members # list not-imported members - members.append(name) - elif '__all__' in dir(self.object) and respect_module_all: + or imported is False # list members that have __all__ set + or (respect_module_all and '__all__' in dir(self.object)) + ): members.append(name) return members @@ -249,8 +232,9 @@ def members_of(obj: Any, conf: Config) -> Sequence[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, qualname: str = None) -> str: + recursive: bool, context: dict, + modname: str | None = None, + qualname: str | None = None) -> str: doc = get_documenter(app, obj, parent) def skip_member(obj: Any, name: str, objtype: str) -> bool: @@ -263,11 +247,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, name, exc, type='autosummary') return False - def get_class_members(obj: Any) -> Dict[str, Any]: + def get_class_members(obj: Any) -> dict[str, Any]: members = sphinx.ext.autodoc.get_class_members(obj, [qualname], safe_getattr) return {name: member.object for name, member in members.items()} - def get_module_members(obj: Any) -> Dict[str, Any]: + def get_module_members(obj: Any) -> dict[str, Any]: members = {} for name in members_of(obj, app.config): try: @@ -276,17 +260,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, continue return members - def get_all_members(obj: Any) -> Dict[str, Any]: + def get_all_members(obj: Any) -> dict[str, Any]: if doc.objtype == "module": return get_module_members(obj) elif doc.objtype == "class": return get_class_members(obj) return {} - def get_members(obj: Any, types: Set[str], include_public: List[str] = [], - imported: bool = True) -> Tuple[List[str], List[str]]: - items: List[str] = [] - public: List[str] = [] + def get_members(obj: Any, types: set[str], include_public: list[str] = [], + imported: bool = True) -> tuple[list[str], list[str]]: + items: list[str] = [] + public: list[str] = [] all_members = get_all_members(obj) for name, value in all_members.items(): @@ -308,7 +292,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, public.append(name) return public, items - def get_module_attrs(members: Any) -> Tuple[List[str], List[str]]: + def get_module_attrs(members: Any) -> tuple[list[str], list[str]]: """Find module attributes with docstrings.""" attrs, public = [], [] try: @@ -323,9 +307,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, pass # give up if ModuleAnalyzer fails to parse code return public, attrs - def get_modules(obj: Any) -> Tuple[List[str], List[str]]: - items: 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) @@ -335,15 +327,24 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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] = {} + ns: dict[str, Any] = {} ns.update(context) 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'] = \ @@ -354,7 +355,36 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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'] = \ @@ -389,21 +419,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, return template.render(doc.objtype, ns) -def generate_autosummary_docs(sources: List[str], output_dir: str = None, - suffix: str = '.rst', base_path: str = None, - builder: Builder = None, template_dir: str = None, +def generate_autosummary_docs(sources: list[str], output_dir: str | None = None, + suffix: str = '.rst', base_path: str | None = None, imported_members: bool = False, app: Any = None, overwrite: bool = True, encoding: str = 'utf-8') -> None: - - if builder: - warnings.warn('builder argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - if template_dir: - warnings.warn('template_dir argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - showed_sources = list(sorted(sources)) + showed_sources = sorted(sources) if len(showed_sources) > 20: showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:] logger.info(__('[autosummary] generating autosummary for: %s') % @@ -439,7 +459,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, ensuredir(path) try: - name, obj, parent, modname = import_by_name(entry.name, grouped_exception=True) + name, obj, parent, modname = import_by_name(entry.name) qualname = name.replace(modname + ".", "") except ImportExceptionGroup as exc: try: @@ -448,16 +468,16 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, qualname = name.replace(modname + ".", "") except ImportError as exc2: if exc2.__cause__: - exceptions: List[BaseException] = exc.exceptions + [exc2.__cause__] + exceptions: list[BaseException] = exc.exceptions + [exc2.__cause__] else: exceptions = exc.exceptions + [exc2] - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exceptions)) + 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] = {} + context: dict[str, Any] = {} if app: context.update(app.config.autosummary_context) @@ -484,7 +504,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if content == old_content: continue - elif overwrite: # content has changed + if overwrite: # content has changed with open(filename, 'w', encoding=encoding) as f: f.write(content) new_files.append(filename) @@ -497,19 +517,18 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if new_files: generate_autosummary_docs(new_files, output_dir=output_dir, suffix=suffix, base_path=base_path, - builder=builder, template_dir=template_dir, imported_members=imported_members, app=app, overwrite=overwrite) # -- Finding documented entries in files --------------------------------------- -def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: +def find_autosummary_in_files(filenames: list[str]) -> list[AutosummaryEntry]: """Find out what items are documented in source/*.rst. See `find_autosummary_in_lines`. """ - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] for filename in filenames: with open(filename, encoding='utf-8', errors='ignore') as f: lines = f.read().splitlines() @@ -517,33 +536,31 @@ def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: return documented -def find_autosummary_in_docstring(name: str, module: str = None, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_docstring( + name: str, filename: str | None = None, +) -> list[AutosummaryEntry]: """Find out what items are documented in the given object's docstring. See `find_autosummary_in_lines`. """ - if module: - warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - real_name, obj, parent, modname = import_by_name(name, grouped_exception=True) + real_name, obj, parent, modname = import_by_name(name) lines = pydoc.getdoc(obj).splitlines() return find_autosummary_in_lines(lines, module=name, filename=filename) except AttributeError: pass except ImportExceptionGroup as exc: - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exc.exceptions)) - print('Failed to import %s.\nPossible hints:\n%s' % (name, '\n'.join(errors))) + errors = '\n'.join({f"* {type(e).__name__}: {e}" for e in exc.exceptions}) + print(f'Failed to import {name}.\nPossible hints:\n{errors}') except SystemExit: print("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, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_lines( + 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. @@ -564,10 +581,10 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$') - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] recursive = False - toctree: str = None + toctree: str | None = None template = None current_module = module in_autosummary = False @@ -603,7 +620,7 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st name = name[1:] if current_module and \ not name.startswith(current_module + '.'): - name = "%s.%s" % (current_module, name) + name = f"{current_module}.{name}" documented.append(AutosummaryEntry(name, toctree, template, recursive)) continue @@ -683,12 +700,11 @@ The format of the autosummary directive is documented in the return parser -def main(argv: List[str] = sys.argv[1:]) -> None: - sphinx.locale.setlocale(locale.LC_ALL, '') - sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') - translator, _ = sphinx.locale.init([], None) +def main(argv: list[str] = sys.argv[1:]) -> None: + locale.setlocale(locale.LC_ALL, '') + sphinx.locale.init_console() - app = DummyApplication(translator) + app = DummyApplication(sphinx.locale.get_translator()) logging.setup(app, sys.stdout, sys.stderr) # type: ignore setup_documenters(app) args = get_parser().parse_args(argv) diff --git a/docs/mindearth/docs/_ext/overwriteobjectiondirective.txt b/docs/mindearth/docs/_ext/overwriteobjectiondirective.txt index f551652f1a09c8a6178a2c04ffb8141eb98ef9df..2774703f812669d94823b280fec7e58a045fe465 100644 --- a/docs/mindearth/docs/_ext/overwriteobjectiondirective.txt +++ b/docs/mindearth/docs/_ext/overwriteobjectiondirective.txt @@ -1,17 +1,11 @@ -""" - sphinx.directives - ~~~~~~~~~~~~~~~~~ +"""Handlers for additional ReST directives.""" - Handlers for additional ReST directives. - - :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import re import inspect import importlib -from typing import TYPE_CHECKING, Any, Dict, Generic, List, Tuple, TypeVar, cast +from typing import TYPE_CHECKING, Any, Generic, List, TypeVar, cast from docutils import nodes from docutils.nodes import Node @@ -19,10 +13,10 @@ from docutils.parsers.rst import directives, roles from sphinx import addnodes from sphinx.addnodes import desc_signature -from sphinx.deprecation import RemovedInSphinx50Warning, deprecated_alias from sphinx.util import docutils, logging from sphinx.util.docfields import DocFieldTransformer, Field, TypedField from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.typing import OptionSpec if TYPE_CHECKING: @@ -33,7 +27,7 @@ if TYPE_CHECKING: nl_escape_re = re.compile(r'\\\n') strip_backslash_re = re.compile(r'\\(.)') -T = TypeVar('T') +ObjDescT = TypeVar('ObjDescT') logger = logging.getLogger(__name__) def optional_int(argument: str) -> int: @@ -90,7 +84,7 @@ def get_platforms(name: str): except: return [] -class ObjectDescription(SphinxDirective, Generic[T]): +class ObjectDescription(SphinxDirective, Generic[ObjDescT]): """ Directive to describe a class, function or similar object. Not used directly, but subclassed (in domain-specific directives) to add custom @@ -103,18 +97,20 @@ class ObjectDescription(SphinxDirective, Generic[T]): final_argument_whitespace = True option_spec: OptionSpec = { 'noindex': directives.flag, - } # type: Dict[str, DirectiveOption] + 'noindexentry': directives.flag, + 'nocontentsentry': directives.flag, + } # types of doc fields that this directive handles, see sphinx.util.docfields - doc_field_types: List[Field] = [] - domain: str = None - objtype: str = None - indexnode: addnodes.index = None + doc_field_types: list[Field] = [] + domain: str | None = None + objtype: str # set when `run` method is called + indexnode: addnodes.index # Warning: this might be removed in future version. Don't touch this from extensions. - _doc_field_type_map = {} # type: Dict[str, Tuple[Field, bool]] + _doc_field_type_map: dict[str, tuple[Field, bool]] = {} - def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]: + def get_field_type_map(self) -> dict[str, tuple[Field, bool]]: if self._doc_field_type_map == {}: self._doc_field_type_map = {} for field in self.doc_field_types: @@ -128,12 +124,10 @@ class ObjectDescription(SphinxDirective, Generic[T]): return self._doc_field_type_map - def get_signatures(self) -> List[str]: + def get_signatures(self) -> list[str]: """ Retrieve the signatures to document from the directive arguments. By default, signatures are given as arguments, one per line. - - Backslash-escaping of newlines is supported. """ lines = nl_escape_re.sub('', self.arguments[0]).split('\n') if self.config.strip_signature_backslash: @@ -142,7 +136,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): else: return [line.strip() for line in lines] - def handle_signature(self, sig: str, signode: desc_signature) -> Any: + def handle_signature(self, sig: str, signode: desc_signature) -> ObjDescT: """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -154,7 +148,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): """ raise ValueError - def add_target_and_index(self, name: Any, sig: str, signode: desc_signature) -> None: + def add_target_and_index(self, name: ObjDescT, sig: str, signode: desc_signature) -> None: """ Add cross-reference IDs and entries to self.indexnode, if applicable. @@ -197,7 +191,45 @@ class ObjectDescription(SphinxDirective, Generic[T]): ls.append((rst_file, start_num+i)) return ls - def run(self) -> List[Node]: + def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]: + """ + Returns a tuple of strings, one entry for each part of the object's + hierarchy (e.g. ``('module', 'submodule', 'Class', 'method')``). The + returned tuple is used to properly nest children within parents in the + table of contents, and can also be used within the + :py:meth:`_toc_entry_name` method. + + This method must not be used outwith table of contents generation. + """ + return () + + def _toc_entry_name(self, sig_node: desc_signature) -> str: + """ + Returns the text of the table of contents entry for the object. + + This function is called once, in :py:meth:`run`, to set the name for the + table of contents entry (a special attribute ``_toc_name`` is set on the + object node, later used in + ``environment.collectors.toctree.TocTreeCollector.process_doc().build_toc()`` + when the table of contents entries are collected). + + To support table of contents entries for their objects, domains must + override this method, also respecting the configuration setting + ``toc_object_entries_show_parents``. Domains must also override + :py:meth:`_object_hierarchy_parts`, with one (string) entry for each part of the + object's hierarchy. The result of this method is set on the signature + node, and can be accessed as ``sig_node['_toc_parts']`` for use within + this method. The resulting tuple is also used to properly nest children + within parents in the table of contents. + + An example implementations of this method is within the python domain + (:meth:`!PyObject._toc_entry_name`). The python domain sets the + ``_toc_parts`` attribute within the :py:meth:`handle_signature()` + method. + """ + return '' + + def run(self) -> list[Node]: """ Main directive entry function, called by docutils upon encountering the directive. @@ -223,15 +255,28 @@ class ObjectDescription(SphinxDirective, Generic[T]): node = addnodes.desc() node.document = self.state.document + source, line = self.get_source_info() + # If any options were specified to the directive, + # self.state.document.current_line will at this point be set to + # None. To ensure nodes created as part of the signature have a line + # number set, set the document's line number correctly. + # + # Note that we need to subtract one from the line number since + # note_source uses 0-based line numbers. + if line is not None: + line -= 1 + self.state.document.note_source(source, line) node['domain'] = self.domain # 'desctype' is a backwards compatible attribute node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) + node['noindexentry'] = ('noindexentry' in self.options) + node['nocontentsentry'] = ('nocontentsentry' in self.options) if self.domain: node['classes'].append(self.domain) node['classes'].append(node['objtype']) - self.names: List[T] = [] + self.names: list[ObjDescT] = [] signatures = self.get_signatures() for sig in signatures: # add a signature node for each signature in the current unit @@ -249,6 +294,15 @@ class ObjectDescription(SphinxDirective, Generic[T]): signode.clear() signode += addnodes.desc_name(sig, sig) continue # we don't want an index entry here + finally: + # Private attributes for ToC generation. Will be modified or removed + # without notice. + if self.env.app.config.toc_object_entries: + signode['_toc_parts'] = self._object_hierarchy_parts(signode) + signode['_toc_name'] = self._toc_entry_name(signode) + else: + signode['_toc_parts'] = () + signode['_toc_name'] = '' if name not in self.names: self.names.append(name) if not noindex: @@ -290,7 +344,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): self.content.items.extend(self.extend_items(self.content.items[0][0], self.content.items[-1][1], len(extra))) except Exception as e: logger.warning(f'{e}') - self.state.nested_parse(self.content, self.content_offset, contentnode) + nested_parse_with_titles(self.state, self.content, contentnode, self.content_offset) self.transform_content(contentnode) self.env.app.emit('object-description-transform', self.domain, self.objtype, contentnode) @@ -308,14 +362,14 @@ class DefaultRole(SphinxDirective): optional_arguments = 1 final_argument_whitespace = False - def run(self) -> List[Node]: + def run(self) -> list[Node]: if not self.arguments: docutils.unregister_role('') return [] role_name = self.arguments[0] role, messages = roles.role(role_name, self.state_machine.language, self.lineno, self.state.reporter) - if role: + if role: # type: ignore[truthy-function] docutils.register_role('', role) self.env.temp_data['default_role'] = role_name else: @@ -337,9 +391,9 @@ class DefaultDomain(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} # type: Dict + option_spec: OptionSpec = {} - def run(self) -> List[Node]: + def run(self) -> list[Node]: domain_name = self.arguments[0].lower() # if domain_name not in env.domains: # # try searching by label @@ -350,7 +404,8 @@ class DefaultDomain(SphinxDirective): self.env.temp_data['default_domain'] = self.env.domains.get(domain_name) return [] -def setup(app: "Sphinx") -> Dict[str, Any]: + +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value("strip_signature_backslash", False, 'env') directives.register_directive('default-role', DefaultRole) directives.register_directive('default-domain', DefaultDomain) diff --git a/docs/mindearth/docs/_ext/overwriteviewcode.txt b/docs/mindearth/docs/_ext/overwriteviewcode.txt index 84640d524131abde9a829bc02242894561c5e603..a2fdcfb787bb99e6d96742574675f79151a4848d 100644 --- a/docs/mindearth/docs/_ext/overwriteviewcode.txt +++ b/docs/mindearth/docs/_ext/overwriteviewcode.txt @@ -1,18 +1,12 @@ -""" - sphinx.ext.viewcode - ~~~~~~~~~~~~~~~~~~~ +"""Add links to module code in Python object descriptions.""" - Add links to module code in Python object descriptions. - - :copyright: Copyright 2007-2023 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import posixpath import traceback import warnings from os import path -from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast +from typing import Any, Generator, Iterable, cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,12 +16,12 @@ from sphinx import addnodes from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer from sphinx.transforms.post_transforms import SphinxPostTransform -from sphinx.util import get_full_modname, logging, status_iterator +from sphinx.util import get_full_modname, logging +from sphinx.util.display import status_iterator from sphinx.util.nodes import make_refnode @@ -46,13 +40,13 @@ class viewcode_anchor(Element): """ -def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]: +def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str | None: try: return get_full_modname(modname, attribute) except AttributeError: # sphinx.ext.viewcode can't follow class instance attribute # then AttributeError logging output only verbose mode. - logger.verbose('Didn\'t find %s in %s', attribute, modname) + logger.verbose("Didn't find %s in %s", attribute, modname) return None except Exception as e: # sphinx.ext.viewcode follow python domain directives. @@ -67,12 +61,11 @@ def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str def is_supported_builder(builder: Builder) -> bool: if builder.format != 'html': return False - elif builder.name == 'singlehtml': + if builder.name == 'singlehtml': return False - elif builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: + if builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: return False - else: - return True + return True def doctree_read(app: Sphinx, doctree: Node) -> None: @@ -112,7 +105,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: for objnode in list(doctree.findall(addnodes.desc)): if objnode.get('domain') != 'py': continue - names: Set[str] = set() + names: set[str] = set() for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue @@ -211,19 +204,7 @@ class ViewcodeAnchorTransform(SphinxPostTransform): node.parent.remove(node) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node - ) -> Optional[Node]: - # resolve our "viewcode" reference nodes -- they need special treatment - if node['reftype'] == 'viewcode': - warnings.warn('viewcode extension is no longer use pending_xref node. ' - 'Please update your extension.', RemovedInSphinx50Warning) - return make_refnode(app.builder, node['refdoc'], node['reftarget'], - node['refid'], contnode) - - return None - - -def get_module_filename(app: Sphinx, modname: str) -> Optional[str]: +def get_module_filename(app: Sphinx, modname: str) -> str | None: """Get module filename for *modname*.""" source_info = app.emit_firstresult('viewcode-find-source', modname) if source_info: @@ -251,13 +232,13 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool: if path.getmtime(module_filename) <= path.getmtime(page_filename): # generation is not needed if the HTML page is newer than module file. return False - except IOError: + except OSError: pass return True -def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], None, None]: +def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], None, None]: env = app.builder.env if not hasattr(env, '_viewcode_modules'): return @@ -282,7 +263,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non # construct a page name for the highlighted source pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/')) # highlight the source using the builder's highlighter - if env.config.highlight_language in ('python3', 'default', 'none'): + if env.config.highlight_language in {'default', 'none'}: lexer = env.config.highlight_language else: lexer = 'python' @@ -343,10 +324,9 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non stack.pop() html.append('') stack.append(modname + '.') - html.append('
  • %s
  • \n' % ( - urito(posixpath.join(OUTPUT_DIRNAME, 'index'), - posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))), - modname)) + relative_uri = urito(posixpath.join(OUTPUT_DIRNAME, 'index'), + posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))) + html.append(f'
  • {modname}
  • \n') html.append('' * (len(stack) - 1)) context = { 'title': _('Overview: module code'), @@ -357,7 +337,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non yield (posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html') -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) @@ -365,7 +345,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('env-merge-info', env_merge_info) app.connect('env-purge-doc', env_purge_doc) app.connect('html-collect-pages', collect_pages) - app.connect('missing-reference', missing_reference) # app.add_config_value('viewcode_include_modules', [], 'env') # app.add_config_value('viewcode_exclude_modules', [], 'env') app.add_event('viewcode-find-source') @@ -374,5 +353,5 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': sphinx.__display_version__, 'env_version': 1, - 'parallel_read_safe': True + 'parallel_read_safe': True, } diff --git a/docs/mindearth/docs/requirements.txt b/docs/mindearth/docs/requirements.txt index d0e9823ca3f3b89d6ef24e72e44ecd381eed6786..c45dfaaea15d727930a01217c3cde4c08013f03c 100644 --- a/docs/mindearth/docs/requirements.txt +++ b/docs/mindearth/docs/requirements.txt @@ -1,7 +1,7 @@ -sphinx == 4.4.0 -docutils == 0.17.1 -myst-parser == 0.18.1 -sphinx_rtd_theme == 1.0.0 +sphinx == 7.0.1 +docutils == 0.20.1 +myst-parser == 2.0.0 +sphinx_rtd_theme == 3.0.0 numpy nbsphinx == 0.8.11 IPython diff --git a/docs/mindflow/docs/_ext/customdocumenter.txt b/docs/mindflow/docs/_ext/customdocumenter.txt index 2d37ae41f6772a21da2a7dc5c7bff75128e68330..505ed7c57e6d7a30d106ca7a33111df6a8278ad3 100644 --- a/docs/mindflow/docs/_ext/customdocumenter.txt +++ b/docs/mindflow/docs/_ext/customdocumenter.txt @@ -8,7 +8,7 @@ class CustomDocumenter(Documenter): def document_members(self, all_members: bool = False) -> None: """Generate reST for member documentation. - If *all_members* is True, do all members, else those given by + If *all_members* is True, document all members, else those given by *self.options.members*. """ # set current namespace for finding members @@ -16,8 +16,9 @@ class CustomDocumenter(Documenter): if self.objpath: self.env.temp_data['autodoc:class'] = self.objpath[0] - want_all = all_members or self.options.inherited_members or \ - self.options.members is ALL + want_all = (all_members or + self.options.inherited_members or + self.options.members is ALL) # find out which members are documentable members_check_module, members = self.get_object_members(want_all) @@ -59,7 +60,7 @@ class CustomDocumenter(Documenter): ] # document non-skipped members - memberdocumenters = [] # type: List[Tuple[Documenter, bool]] + memberdocumenters: list[tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -111,7 +112,7 @@ class CustomDocumenter(Documenter): if not self.parse_name(): # need a module to import logger.warning( - __('don\'t know which module to import for autodocumenting ' + __("don't know which module to import for autodocumenting " '%r (try placing a "module" or "currentmodule" directive ' 'in the document, or giving an explicit module name)') % self.name, type='autodoc') @@ -126,7 +127,8 @@ class CustomDocumenter(Documenter): # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. - self.real_modname = real_modname or self.get_real_modname() # type: str + guess_modname = self.get_real_modname() + self.real_modname: str = real_modname or guess_modname # try to also get a source code analyzer for attribute docs try: @@ -134,15 +136,28 @@ class CustomDocumenter(Documenter): # parse right now, to get PycodeErrors on parsing (results will # be cached anyway) self.analyzer.find_attr_docs() - except PycodeError as err: - logger.debug('[autodoc] module analyzer failed: %s', err) + except PycodeError as exc: + logger.debug('[autodoc] module analyzer failed: %s', exc) # no source file -- e.g. for builtin and C modules self.analyzer = None # at least add the module.__file__ as a dependency if hasattr(self.module, '__file__') and self.module.__file__: - self.directive.filename_set.add(self.module.__file__) + self.directive.record_dependencies.add(self.module.__file__) else: - self.directive.filename_set.add(self.analyzer.srcname) + self.directive.record_dependencies.add(self.analyzer.srcname) + + if self.real_modname != guess_modname: + # Add module to dependency list if target object is defined in other module. + try: + analyzer = ModuleAnalyzer.for_module(guess_modname) + self.directive.record_dependencies.add(analyzer.srcname) + except PycodeError: + pass + + docstrings: list[str] = sum(self.get_doc() or [], []) + if ismock(self.object) and not docstrings: + logger.warning(__('A mocked object is detected: %r'), + self.name, type='autodoc') # check __module__ of object (for members not given explicitly) if check_module: @@ -160,29 +175,41 @@ class ModuleDocumenter(CustomDocumenter): objtype = 'module' content_indent = '' titles_allowed = True + _extra_indent = ' ' - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, 'platform': identity, 'deprecated': bool_option, - 'member-order': identity, 'exclude-members': members_set_option, - 'private-members': bool_option, 'special-members': members_option, - 'imported-members': bool_option, 'ignore-module-all': bool_option - } # type: Dict[str, Callable] + 'member-order': member_order_option, 'exclude-members': exclude_members_option, + 'private-members': members_option, 'special-members': members_option, + 'imported-members': bool_option, 'ignore-module-all': bool_option, + 'no-value': bool_option, + } def __init__(self, *args: Any) -> None: super().__init__(*args) merge_members_option(self.options) + self.__all__: Sequence[str] | None = None + + def add_content(self, more_content: StringList | None) -> None: + old_indent = self.indent + self.indent += self._extra_indent + super().add_content(None) + self.indent = old_indent + if more_content: + for line, src in zip(more_content.data, more_content.items): + self.add_line(line, src[0], src[1]) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: # don't document submodules automatically return False - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: if modname is not None: logger.warning(__('"::" in automodule name doesn\'t make sense'), type='autodoc') @@ -196,6 +223,20 @@ class ModuleDocumenter(CustomDocumenter): type='autodoc') return ret + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + + try: + if not self.options.ignore_module_all: + self.__all__ = inspect.getall(self.object) + except ValueError as exc: + # invalid __all__ found. + logger.warning(__('__all__ should be a list of strings, not %r ' + '(in module %s) -- ignoring __all__') % + (exc.args[0], self.fullname), type='autodoc') + + return ret + def add_directive_header(self, sig: str) -> None: Documenter.add_directive_header(self, sig) @@ -209,37 +250,57 @@ class ModuleDocumenter(CustomDocumenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) - def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, object]]]: + def get_module_members(self) -> dict[str, ObjectMember]: + """Get members of target module.""" + if self.analyzer: + attr_docs = self.analyzer.attr_docs + else: + attr_docs = {} + + members: dict[str, ObjectMember] = {} + for name in dir(self.object): + try: + value = safe_getattr(self.object, name, None) + if ismock(value): + value = undecorate(value) + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) + except AttributeError: + continue + + # annotation only member (ex. attr: int) + for name in inspect.getannotations(self.object): + if name not in members: + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, INSTANCEATTR, + docstring="\n".join(docstring)) + + return members + + def get_object_members(self, want_all: bool) -> tuple[bool, ObjectMembers]: + members = self.get_module_members() if want_all: if (self.options.ignore_module_all or not hasattr(self.object, '__all__')): # for implicit module members, check __module__ to avoid # documenting imported objects - return True, get_module_members(self.object) + return True, list(members.values()) else: - memberlist = self.object.__all__ - # Sometimes __all__ is broken... - if not isinstance(memberlist, (list, tuple)) or not \ - all(isinstance(entry, str) for entry in memberlist): - logger.warning( - __('__all__ should be a list of strings, not %r ' - '(in module %s) -- ignoring __all__') % - (memberlist, self.fullname), - type='autodoc' - ) - # fall back to all members - return True, get_module_members(self.object) + for member in members.values(): + if member.__name__ not in self.__all__: + member.skipped = True + + # fall back to all members + return True, list(members.values()) else: memberlist = self.options.members or [] - ret = [] - for mname in memberlist: - try: - ret.append((mname, safe_getattr(self.object, mname))) - except AttributeError: - logger.warning( - __('missing attribute mentioned in :members: or __all__: ' - 'module %s, attribute %s') % - (safe_getattr(self.object, '__name__', '???'), mname), - type='autodoc' - ) - return False, ret + ret = [] + for name in memberlist: + if name in members: + ret.append(members[name]) + else: + logger.warning(__('missing attribute mentioned in :members: option: ' + 'module %s, attribute %s') % + (safe_getattr(self.object, '__name__', '???'), name), + type='autodoc') + return False, ret diff --git a/docs/mindflow/docs/_ext/overwriteautosummary_generate.txt b/docs/mindflow/docs/_ext/overwriteautosummary_generate.txt index 4b0a1b1dd2b410ecab971b13da9993c90d65ef0d..23a4c166e123413b21d42f4eeab1accbc09b4896 100644 --- a/docs/mindflow/docs/_ext/overwriteautosummary_generate.txt +++ b/docs/mindflow/docs/_ext/overwriteautosummary_generate.txt @@ -1,22 +1,19 @@ -""" - sphinx.ext.autosummary.generate - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Usable as a library or script to generate automatic RST source files for - items referred to in autosummary:: directives. +"""Generates reST source files for autosummary. - Each generated RST file contains a single auto*:: directive which - extracts the docstring of the referred item. +Usable as a library or script to generate automatic RST source files for +items referred to in autosummary:: directives. - Example Makefile rule:: +Each generated RST file contains a single auto*:: directive which +extracts the docstring of the referred item. - generate: - sphinx-autogen -o source/generated source/*.rst +Example Makefile rule:: - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. + generate: + sphinx-autogen -o source/generated source/*.rst """ +from __future__ import annotations + import argparse import importlib import inspect @@ -26,10 +23,8 @@ import pkgutil import pydoc import re import sys -import warnings -from gettext import NullTranslations from os import path -from typing import Any, Dict, List, NamedTuple, Sequence, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, NamedTuple, Sequence from jinja2 import TemplateNotFound from jinja2.sandbox import SandboxedEnvironment @@ -39,11 +34,14 @@ from sphinx import __display_version__, package_dir from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc.importer import import_module -from sphinx.ext.autosummary import (ImportExceptionGroup, get_documenter, import_by_name, - import_ivar_by_name) +from sphinx.ext.autosummary import ( + ImportExceptionGroup, + get_documenter, + import_by_name, + import_ivar_by_name, +) from sphinx.locale import __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.registry import SphinxComponentRegistry @@ -52,6 +50,9 @@ from sphinx.util.inspect import getall, safe_getattr from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxTemplateLoader +if TYPE_CHECKING: + from gettext import NullTranslations + logger = logging.getLogger(__name__) @@ -61,7 +62,7 @@ class DummyApplication: def __init__(self, translator: NullTranslations) -> None: self.config = Config() self.registry = SphinxComponentRegistry() - self.messagelog: List[str] = [] + self.messagelog: list[str] = [] self.srcdir = "/" self.translator = translator self.verbosity = 0 @@ -91,32 +92,26 @@ class AutosummaryEntry(NamedTuple): def setup_documenters(app: Any) -> None: - from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter, DataDocumenter, - DecoratorDocumenter, ExceptionDocumenter, - FunctionDocumenter, MethodDocumenter, ModuleDocumenter, - NewTypeAttributeDocumenter, NewTypeDataDocumenter, - PropertyDocumenter) - documenters: List[Type[Documenter]] = [ + from sphinx.ext.autodoc import ( + AttributeDocumenter, + ClassDocumenter, + DataDocumenter, + DecoratorDocumenter, + ExceptionDocumenter, + FunctionDocumenter, + MethodDocumenter, + ModuleDocumenter, + PropertyDocumenter, + ) + documenters: list[type[Documenter]] = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, - FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter, - NewTypeDataDocumenter, AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, + FunctionDocumenter, MethodDocumenter, + AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, ] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) -def _simple_info(msg: str) -> None: - warnings.warn('_simple_info() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print(msg) - - -def _simple_warn(msg: str) -> None: - warnings.warn('_simple_warn() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print('WARNING: ' + msg, file=sys.stderr) - - def _underline(title: str, line: str = '=') -> str: if '\n' in title: raise ValueError('Can only underline single lines') @@ -126,14 +121,9 @@ def _underline(title: str, line: str = '=') -> str: class AutosummaryRenderer: """A helper class for rendering.""" - def __init__(self, app: Union[Builder, Sphinx], template_dir: str = None) -> None: + def __init__(self, app: Sphinx) -> None: if isinstance(app, Builder): - warnings.warn('The first argument for AutosummaryRenderer has been ' - 'changed to Sphinx object', - RemovedInSphinx50Warning, stacklevel=2) - if template_dir: - warnings.warn('template_dir argument for AutosummaryRenderer is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) + raise ValueError('Expected a Sphinx application object!') system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path, @@ -144,26 +134,11 @@ class AutosummaryRenderer: self.env.filters['e'] = rst.escape self.env.filters['underline'] = _underline - if isinstance(app, (Sphinx, DummyApplication)): - if app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.translator) - elif isinstance(app, Builder): - if app.app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.app.translator) - - def exists(self, template_name: str) -> bool: - """Check if template file exists.""" - warnings.warn('AutosummaryRenderer.exists() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - self.env.get_template(template_name) - return True - except TemplateNotFound: - return False + if app.translator: + self.env.add_extension("jinja2.ext.i18n") + self.env.install_gettext_translations(app.translator) - def render(self, template_name: str, context: Dict) -> str: + def render(self, template_name: str, context: dict) -> str: """Render a template file.""" try: template = self.env.get_template(template_name) @@ -199,8 +174,14 @@ class ModuleScanner: name, exc, type='autosummary') return False - def scan(self, imported_members: bool) -> List[str]: + def scan(self, imported_members: bool) -> list[str]: members = [] + try: + analyzer = ModuleAnalyzer.for_module(self.object.__name__) + attr_docs = analyzer.find_attr_docs() + except PycodeError: + attr_docs = {} + for name in members_of(self.object, self.app.config): try: value = safe_getattr(self.object, name) @@ -212,7 +193,9 @@ class ModuleScanner: continue try: - if inspect.ismodule(value): + if ('', name) in attr_docs: + imported = False + elif inspect.ismodule(value): # NoQA: SIM114 imported = True elif safe_getattr(value, '__module__') != self.object.__name__: imported = True @@ -222,14 +205,14 @@ class ModuleScanner: imported = False respect_module_all = not self.app.config.autosummary_ignore_module_all - if imported_members: + if ( # list all members up - members.append(name) - elif imported is False: + imported_members # list not-imported members - members.append(name) - elif '__all__' in dir(self.object) and respect_module_all: + or imported is False # list members that have __all__ set + or (respect_module_all and '__all__' in dir(self.object)) + ): members.append(name) return members @@ -249,8 +232,9 @@ def members_of(obj: Any, conf: Config) -> Sequence[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, qualname: str = None) -> str: + recursive: bool, context: dict, + modname: str | None = None, + qualname: str | None = None) -> str: doc = get_documenter(app, obj, parent) def skip_member(obj: Any, name: str, objtype: str) -> bool: @@ -263,11 +247,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, name, exc, type='autosummary') return False - def get_class_members(obj: Any) -> Dict[str, Any]: + def get_class_members(obj: Any) -> dict[str, Any]: members = sphinx.ext.autodoc.get_class_members(obj, [qualname], safe_getattr) return {name: member.object for name, member in members.items()} - def get_module_members(obj: Any) -> Dict[str, Any]: + def get_module_members(obj: Any) -> dict[str, Any]: members = {} for name in members_of(obj, app.config): try: @@ -276,17 +260,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, continue return members - def get_all_members(obj: Any) -> Dict[str, Any]: + def get_all_members(obj: Any) -> dict[str, Any]: if doc.objtype == "module": return get_module_members(obj) elif doc.objtype == "class": return get_class_members(obj) return {} - def get_members(obj: Any, types: Set[str], include_public: List[str] = [], - imported: bool = True) -> Tuple[List[str], List[str]]: - items: List[str] = [] - public: List[str] = [] + def get_members(obj: Any, types: set[str], include_public: list[str] = [], + imported: bool = True) -> tuple[list[str], list[str]]: + items: list[str] = [] + public: list[str] = [] all_members = get_all_members(obj) for name, value in all_members.items(): @@ -308,7 +292,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, public.append(name) return public, items - def get_module_attrs(members: Any) -> Tuple[List[str], List[str]]: + def get_module_attrs(members: Any) -> tuple[list[str], list[str]]: """Find module attributes with docstrings.""" attrs, public = [], [] try: @@ -323,9 +307,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, pass # give up if ModuleAnalyzer fails to parse code return public, attrs - def get_modules(obj: Any) -> Tuple[List[str], List[str]]: - items: 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) @@ -335,15 +327,24 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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] = {} + ns: dict[str, Any] = {} ns.update(context) 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'] = \ @@ -354,7 +355,36 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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'] = \ @@ -389,21 +419,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, return template.render(doc.objtype, ns) -def generate_autosummary_docs(sources: List[str], output_dir: str = None, - suffix: str = '.rst', base_path: str = None, - builder: Builder = None, template_dir: str = None, +def generate_autosummary_docs(sources: list[str], output_dir: str | None = None, + suffix: str = '.rst', base_path: str | None = None, imported_members: bool = False, app: Any = None, overwrite: bool = True, encoding: str = 'utf-8') -> None: - - if builder: - warnings.warn('builder argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - if template_dir: - warnings.warn('template_dir argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - showed_sources = list(sorted(sources)) + showed_sources = sorted(sources) if len(showed_sources) > 20: showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:] logger.info(__('[autosummary] generating autosummary for: %s') % @@ -439,7 +459,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, ensuredir(path) try: - name, obj, parent, modname = import_by_name(entry.name, grouped_exception=True) + name, obj, parent, modname = import_by_name(entry.name) qualname = name.replace(modname + ".", "") except ImportExceptionGroup as exc: try: @@ -448,16 +468,16 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, qualname = name.replace(modname + ".", "") except ImportError as exc2: if exc2.__cause__: - exceptions: List[BaseException] = exc.exceptions + [exc2.__cause__] + exceptions: list[BaseException] = exc.exceptions + [exc2.__cause__] else: exceptions = exc.exceptions + [exc2] - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exceptions)) + 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] = {} + context: dict[str, Any] = {} if app: context.update(app.config.autosummary_context) @@ -484,7 +504,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if content == old_content: continue - elif overwrite: # content has changed + if overwrite: # content has changed with open(filename, 'w', encoding=encoding) as f: f.write(content) new_files.append(filename) @@ -497,19 +517,18 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if new_files: generate_autosummary_docs(new_files, output_dir=output_dir, suffix=suffix, base_path=base_path, - builder=builder, template_dir=template_dir, imported_members=imported_members, app=app, overwrite=overwrite) # -- Finding documented entries in files --------------------------------------- -def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: +def find_autosummary_in_files(filenames: list[str]) -> list[AutosummaryEntry]: """Find out what items are documented in source/*.rst. See `find_autosummary_in_lines`. """ - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] for filename in filenames: with open(filename, encoding='utf-8', errors='ignore') as f: lines = f.read().splitlines() @@ -517,33 +536,31 @@ def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: return documented -def find_autosummary_in_docstring(name: str, module: str = None, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_docstring( + name: str, filename: str | None = None, +) -> list[AutosummaryEntry]: """Find out what items are documented in the given object's docstring. See `find_autosummary_in_lines`. """ - if module: - warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - real_name, obj, parent, modname = import_by_name(name, grouped_exception=True) + real_name, obj, parent, modname = import_by_name(name) lines = pydoc.getdoc(obj).splitlines() return find_autosummary_in_lines(lines, module=name, filename=filename) except AttributeError: pass except ImportExceptionGroup as exc: - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exc.exceptions)) - print('Failed to import %s.\nPossible hints:\n%s' % (name, '\n'.join(errors))) + errors = '\n'.join({f"* {type(e).__name__}: {e}" for e in exc.exceptions}) + print(f'Failed to import {name}.\nPossible hints:\n{errors}') except SystemExit: print("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, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_lines( + 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. @@ -564,10 +581,10 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$') - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] recursive = False - toctree: str = None + toctree: str | None = None template = None current_module = module in_autosummary = False @@ -603,7 +620,7 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st name = name[1:] if current_module and \ not name.startswith(current_module + '.'): - name = "%s.%s" % (current_module, name) + name = f"{current_module}.{name}" documented.append(AutosummaryEntry(name, toctree, template, recursive)) continue @@ -683,12 +700,11 @@ The format of the autosummary directive is documented in the return parser -def main(argv: List[str] = sys.argv[1:]) -> None: - sphinx.locale.setlocale(locale.LC_ALL, '') - sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') - translator, _ = sphinx.locale.init([], None) +def main(argv: list[str] = sys.argv[1:]) -> None: + locale.setlocale(locale.LC_ALL, '') + sphinx.locale.init_console() - app = DummyApplication(translator) + app = DummyApplication(sphinx.locale.get_translator()) logging.setup(app, sys.stdout, sys.stderr) # type: ignore setup_documenters(app) args = get_parser().parse_args(argv) diff --git a/docs/mindflow/docs/_ext/overwriteobjectiondirective.txt b/docs/mindflow/docs/_ext/overwriteobjectiondirective.txt index 8a58bf71191f77ca22097ea9de244c9df5c3d4fb..2774703f812669d94823b280fec7e58a045fe465 100644 --- a/docs/mindflow/docs/_ext/overwriteobjectiondirective.txt +++ b/docs/mindflow/docs/_ext/overwriteobjectiondirective.txt @@ -1,17 +1,11 @@ -""" - sphinx.directives - ~~~~~~~~~~~~~~~~~ +"""Handlers for additional ReST directives.""" - Handlers for additional ReST directives. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import re import inspect import importlib -from typing import TYPE_CHECKING, Any, Dict, Generic, List, Tuple, TypeVar, cast +from typing import TYPE_CHECKING, Any, Generic, List, TypeVar, cast from docutils import nodes from docutils.nodes import Node @@ -19,10 +13,10 @@ from docutils.parsers.rst import directives, roles from sphinx import addnodes from sphinx.addnodes import desc_signature -from sphinx.deprecation import RemovedInSphinx50Warning, deprecated_alias from sphinx.util import docutils, logging from sphinx.util.docfields import DocFieldTransformer, Field, TypedField from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.typing import OptionSpec if TYPE_CHECKING: @@ -33,7 +27,7 @@ if TYPE_CHECKING: nl_escape_re = re.compile(r'\\\n') strip_backslash_re = re.compile(r'\\(.)') -T = TypeVar('T') +ObjDescT = TypeVar('ObjDescT') logger = logging.getLogger(__name__) def optional_int(argument: str) -> int: @@ -90,7 +84,7 @@ def get_platforms(name: str): except: return [] -class ObjectDescription(SphinxDirective, Generic[T]): +class ObjectDescription(SphinxDirective, Generic[ObjDescT]): """ Directive to describe a class, function or similar object. Not used directly, but subclassed (in domain-specific directives) to add custom @@ -103,18 +97,20 @@ class ObjectDescription(SphinxDirective, Generic[T]): final_argument_whitespace = True option_spec: OptionSpec = { 'noindex': directives.flag, - } # type: Dict[str, DirectiveOption] + 'noindexentry': directives.flag, + 'nocontentsentry': directives.flag, + } # types of doc fields that this directive handles, see sphinx.util.docfields - doc_field_types: List[Field] = [] - domain: str = None - objtype: str = None - indexnode: addnodes.index = None + doc_field_types: list[Field] = [] + domain: str | None = None + objtype: str # set when `run` method is called + indexnode: addnodes.index # Warning: this might be removed in future version. Don't touch this from extensions. - _doc_field_type_map = {} # type: Dict[str, Tuple[Field, bool]] + _doc_field_type_map: dict[str, tuple[Field, bool]] = {} - def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]: + def get_field_type_map(self) -> dict[str, tuple[Field, bool]]: if self._doc_field_type_map == {}: self._doc_field_type_map = {} for field in self.doc_field_types: @@ -128,12 +124,10 @@ class ObjectDescription(SphinxDirective, Generic[T]): return self._doc_field_type_map - def get_signatures(self) -> List[str]: + def get_signatures(self) -> list[str]: """ Retrieve the signatures to document from the directive arguments. By default, signatures are given as arguments, one per line. - - Backslash-escaping of newlines is supported. """ lines = nl_escape_re.sub('', self.arguments[0]).split('\n') if self.config.strip_signature_backslash: @@ -142,7 +136,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): else: return [line.strip() for line in lines] - def handle_signature(self, sig: str, signode: desc_signature) -> Any: + def handle_signature(self, sig: str, signode: desc_signature) -> ObjDescT: """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -154,7 +148,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): """ raise ValueError - def add_target_and_index(self, name: Any, sig: str, signode: desc_signature) -> None: + def add_target_and_index(self, name: ObjDescT, sig: str, signode: desc_signature) -> None: """ Add cross-reference IDs and entries to self.indexnode, if applicable. @@ -197,7 +191,45 @@ class ObjectDescription(SphinxDirective, Generic[T]): ls.append((rst_file, start_num+i)) return ls - def run(self) -> List[Node]: + def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]: + """ + Returns a tuple of strings, one entry for each part of the object's + hierarchy (e.g. ``('module', 'submodule', 'Class', 'method')``). The + returned tuple is used to properly nest children within parents in the + table of contents, and can also be used within the + :py:meth:`_toc_entry_name` method. + + This method must not be used outwith table of contents generation. + """ + return () + + def _toc_entry_name(self, sig_node: desc_signature) -> str: + """ + Returns the text of the table of contents entry for the object. + + This function is called once, in :py:meth:`run`, to set the name for the + table of contents entry (a special attribute ``_toc_name`` is set on the + object node, later used in + ``environment.collectors.toctree.TocTreeCollector.process_doc().build_toc()`` + when the table of contents entries are collected). + + To support table of contents entries for their objects, domains must + override this method, also respecting the configuration setting + ``toc_object_entries_show_parents``. Domains must also override + :py:meth:`_object_hierarchy_parts`, with one (string) entry for each part of the + object's hierarchy. The result of this method is set on the signature + node, and can be accessed as ``sig_node['_toc_parts']`` for use within + this method. The resulting tuple is also used to properly nest children + within parents in the table of contents. + + An example implementations of this method is within the python domain + (:meth:`!PyObject._toc_entry_name`). The python domain sets the + ``_toc_parts`` attribute within the :py:meth:`handle_signature()` + method. + """ + return '' + + def run(self) -> list[Node]: """ Main directive entry function, called by docutils upon encountering the directive. @@ -223,15 +255,28 @@ class ObjectDescription(SphinxDirective, Generic[T]): node = addnodes.desc() node.document = self.state.document + source, line = self.get_source_info() + # If any options were specified to the directive, + # self.state.document.current_line will at this point be set to + # None. To ensure nodes created as part of the signature have a line + # number set, set the document's line number correctly. + # + # Note that we need to subtract one from the line number since + # note_source uses 0-based line numbers. + if line is not None: + line -= 1 + self.state.document.note_source(source, line) node['domain'] = self.domain # 'desctype' is a backwards compatible attribute node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) + node['noindexentry'] = ('noindexentry' in self.options) + node['nocontentsentry'] = ('nocontentsentry' in self.options) if self.domain: node['classes'].append(self.domain) node['classes'].append(node['objtype']) - self.names: List[T] = [] + self.names: list[ObjDescT] = [] signatures = self.get_signatures() for sig in signatures: # add a signature node for each signature in the current unit @@ -249,6 +294,15 @@ class ObjectDescription(SphinxDirective, Generic[T]): signode.clear() signode += addnodes.desc_name(sig, sig) continue # we don't want an index entry here + finally: + # Private attributes for ToC generation. Will be modified or removed + # without notice. + if self.env.app.config.toc_object_entries: + signode['_toc_parts'] = self._object_hierarchy_parts(signode) + signode['_toc_name'] = self._toc_entry_name(signode) + else: + signode['_toc_parts'] = () + signode['_toc_name'] = '' if name not in self.names: self.names.append(name) if not noindex: @@ -290,7 +344,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): self.content.items.extend(self.extend_items(self.content.items[0][0], self.content.items[-1][1], len(extra))) except Exception as e: logger.warning(f'{e}') - self.state.nested_parse(self.content, self.content_offset, contentnode) + nested_parse_with_titles(self.state, self.content, contentnode, self.content_offset) self.transform_content(contentnode) self.env.app.emit('object-description-transform', self.domain, self.objtype, contentnode) @@ -308,14 +362,14 @@ class DefaultRole(SphinxDirective): optional_arguments = 1 final_argument_whitespace = False - def run(self) -> List[Node]: + def run(self) -> list[Node]: if not self.arguments: docutils.unregister_role('') return [] role_name = self.arguments[0] role, messages = roles.role(role_name, self.state_machine.language, self.lineno, self.state.reporter) - if role: + if role: # type: ignore[truthy-function] docutils.register_role('', role) self.env.temp_data['default_role'] = role_name else: @@ -337,9 +391,9 @@ class DefaultDomain(SphinxDirective): required_arguments = 1 optional_arguments = 0 final_argument_whitespace = False - option_spec = {} # type: Dict + option_spec: OptionSpec = {} - def run(self) -> List[Node]: + def run(self) -> list[Node]: domain_name = self.arguments[0].lower() # if domain_name not in env.domains: # # try searching by label @@ -350,7 +404,8 @@ class DefaultDomain(SphinxDirective): self.env.temp_data['default_domain'] = self.env.domains.get(domain_name) return [] -def setup(app: "Sphinx") -> Dict[str, Any]: + +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value("strip_signature_backslash", False, 'env') directives.register_directive('default-role', DefaultRole) directives.register_directive('default-domain', DefaultDomain) diff --git a/docs/mindflow/docs/_ext/overwriteviewcode.txt b/docs/mindflow/docs/_ext/overwriteviewcode.txt index 172780ec56b3ed90e7b0add617257a618cf38ee0..a2fdcfb787bb99e6d96742574675f79151a4848d 100644 --- a/docs/mindflow/docs/_ext/overwriteviewcode.txt +++ b/docs/mindflow/docs/_ext/overwriteviewcode.txt @@ -1,18 +1,12 @@ -""" - sphinx.ext.viewcode - ~~~~~~~~~~~~~~~~~~~ +"""Add links to module code in Python object descriptions.""" - Add links to module code in Python object descriptions. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import posixpath import traceback import warnings from os import path -from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast +from typing import Any, Generator, Iterable, cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,12 +16,12 @@ from sphinx import addnodes from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer from sphinx.transforms.post_transforms import SphinxPostTransform -from sphinx.util import get_full_modname, logging, status_iterator +from sphinx.util import get_full_modname, logging +from sphinx.util.display import status_iterator from sphinx.util.nodes import make_refnode @@ -46,13 +40,13 @@ class viewcode_anchor(Element): """ -def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]: +def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str | None: try: return get_full_modname(modname, attribute) except AttributeError: # sphinx.ext.viewcode can't follow class instance attribute # then AttributeError logging output only verbose mode. - logger.verbose('Didn\'t find %s in %s', attribute, modname) + logger.verbose("Didn't find %s in %s", attribute, modname) return None except Exception as e: # sphinx.ext.viewcode follow python domain directives. @@ -67,12 +61,11 @@ def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str def is_supported_builder(builder: Builder) -> bool: if builder.format != 'html': return False - elif builder.name == 'singlehtml': + if builder.name == 'singlehtml': return False - elif builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: + if builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: return False - else: - return True + return True def doctree_read(app: Sphinx, doctree: Node) -> None: @@ -112,7 +105,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: for objnode in list(doctree.findall(addnodes.desc)): if objnode.get('domain') != 'py': continue - names: Set[str] = set() + names: set[str] = set() for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue @@ -211,19 +204,7 @@ class ViewcodeAnchorTransform(SphinxPostTransform): node.parent.remove(node) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node - ) -> Optional[Node]: - # resolve our "viewcode" reference nodes -- they need special treatment - if node['reftype'] == 'viewcode': - warnings.warn('viewcode extension is no longer use pending_xref node. ' - 'Please update your extension.', RemovedInSphinx50Warning) - return make_refnode(app.builder, node['refdoc'], node['reftarget'], - node['refid'], contnode) - - return None - - -def get_module_filename(app: Sphinx, modname: str) -> Optional[str]: +def get_module_filename(app: Sphinx, modname: str) -> str | None: """Get module filename for *modname*.""" source_info = app.emit_firstresult('viewcode-find-source', modname) if source_info: @@ -251,13 +232,13 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool: if path.getmtime(module_filename) <= path.getmtime(page_filename): # generation is not needed if the HTML page is newer than module file. return False - except IOError: + except OSError: pass return True -def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], None, None]: +def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], None, None]: env = app.builder.env if not hasattr(env, '_viewcode_modules'): return @@ -282,7 +263,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non # construct a page name for the highlighted source pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/')) # highlight the source using the builder's highlighter - if env.config.highlight_language in ('python3', 'default', 'none'): + if env.config.highlight_language in {'default', 'none'}: lexer = env.config.highlight_language else: lexer = 'python' @@ -343,10 +324,9 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non stack.pop() html.append('') stack.append(modname + '.') - html.append('
  • %s
  • \n' % ( - urito(posixpath.join(OUTPUT_DIRNAME, 'index'), - posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))), - modname)) + relative_uri = urito(posixpath.join(OUTPUT_DIRNAME, 'index'), + posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))) + html.append(f'
  • {modname}
  • \n') html.append('' * (len(stack) - 1)) context = { 'title': _('Overview: module code'), @@ -357,7 +337,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non yield (posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html') -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) @@ -365,7 +345,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('env-merge-info', env_merge_info) app.connect('env-purge-doc', env_purge_doc) app.connect('html-collect-pages', collect_pages) - app.connect('missing-reference', missing_reference) # app.add_config_value('viewcode_include_modules', [], 'env') # app.add_config_value('viewcode_exclude_modules', [], 'env') app.add_event('viewcode-find-source') @@ -374,5 +353,5 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': sphinx.__display_version__, 'env_version': 1, - 'parallel_read_safe': True + 'parallel_read_safe': True, } diff --git a/docs/mindflow/docs/requirements.txt b/docs/mindflow/docs/requirements.txt index f5ddc521080c312d8b9183f4ec8f7ab44186e930..57ac6e8540bdaec92b0db7b376e45efe0258c84a 100644 --- a/docs/mindflow/docs/requirements.txt +++ b/docs/mindflow/docs/requirements.txt @@ -1,7 +1,7 @@ -sphinx == 4.4.0 -docutils == 0.17.1 -myst-parser == 0.18.1 -sphinx_rtd_theme == 1.0.0 +sphinx == 7.0.1 +docutils == 0.20.1 +myst-parser == 2.0.0 +sphinx_rtd_theme == 3.0.0 numpy nbsphinx == 0.8.11 IPython diff --git a/docs/mindformers/docs/source_en/conf.py b/docs/mindformers/docs/source_en/conf.py index 7a6b9b6d149a4ff6f01b498b69af9881d09e5499..f4256a2a6e9c41e214da2f189d3f91f06142bae6 100644 --- a/docs/mindformers/docs/source_en/conf.py +++ b/docs/mindformers/docs/source_en/conf.py @@ -21,14 +21,14 @@ import sphinx.ext.autosummary.generate as g # Fix some dl-label lack class='simple' from docutils.writers import _html_base -with open(_html_base.__file__, "r", encoding="utf-8") as f: - code_str = f.read() - old_str = ''' if self.is_compactable(node): - classes.append('simple')''' - new_str = ''' if classes == []: - classes.append('simple')''' - code_str = code_str.replace(old_str, new_str) - exec(code_str, _html_base.__dict__) +# with open(_html_base.__file__, "r", encoding="utf-8") as f: +# code_str = f.read() +# old_str = ''' if self.is_compactable(node): +# classes.append('simple')''' +# new_str = ''' if classes == []: +# classes.append('simple')''' +# code_str = code_str.replace(old_str, new_str) +# exec(code_str, _html_base.__dict__) # -- Project information ----------------------------------------------------- diff --git a/docs/mindquantum/docs/_ext/customdocumenter.txt b/docs/mindquantum/docs/_ext/customdocumenter.txt index 2d37ae41f6772a21da2a7dc5c7bff75128e68330..505ed7c57e6d7a30d106ca7a33111df6a8278ad3 100644 --- a/docs/mindquantum/docs/_ext/customdocumenter.txt +++ b/docs/mindquantum/docs/_ext/customdocumenter.txt @@ -8,7 +8,7 @@ class CustomDocumenter(Documenter): def document_members(self, all_members: bool = False) -> None: """Generate reST for member documentation. - If *all_members* is True, do all members, else those given by + If *all_members* is True, document all members, else those given by *self.options.members*. """ # set current namespace for finding members @@ -16,8 +16,9 @@ class CustomDocumenter(Documenter): if self.objpath: self.env.temp_data['autodoc:class'] = self.objpath[0] - want_all = all_members or self.options.inherited_members or \ - self.options.members is ALL + want_all = (all_members or + self.options.inherited_members or + self.options.members is ALL) # find out which members are documentable members_check_module, members = self.get_object_members(want_all) @@ -59,7 +60,7 @@ class CustomDocumenter(Documenter): ] # document non-skipped members - memberdocumenters = [] # type: List[Tuple[Documenter, bool]] + memberdocumenters: list[tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -111,7 +112,7 @@ class CustomDocumenter(Documenter): if not self.parse_name(): # need a module to import logger.warning( - __('don\'t know which module to import for autodocumenting ' + __("don't know which module to import for autodocumenting " '%r (try placing a "module" or "currentmodule" directive ' 'in the document, or giving an explicit module name)') % self.name, type='autodoc') @@ -126,7 +127,8 @@ class CustomDocumenter(Documenter): # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. - self.real_modname = real_modname or self.get_real_modname() # type: str + guess_modname = self.get_real_modname() + self.real_modname: str = real_modname or guess_modname # try to also get a source code analyzer for attribute docs try: @@ -134,15 +136,28 @@ class CustomDocumenter(Documenter): # parse right now, to get PycodeErrors on parsing (results will # be cached anyway) self.analyzer.find_attr_docs() - except PycodeError as err: - logger.debug('[autodoc] module analyzer failed: %s', err) + except PycodeError as exc: + logger.debug('[autodoc] module analyzer failed: %s', exc) # no source file -- e.g. for builtin and C modules self.analyzer = None # at least add the module.__file__ as a dependency if hasattr(self.module, '__file__') and self.module.__file__: - self.directive.filename_set.add(self.module.__file__) + self.directive.record_dependencies.add(self.module.__file__) else: - self.directive.filename_set.add(self.analyzer.srcname) + self.directive.record_dependencies.add(self.analyzer.srcname) + + if self.real_modname != guess_modname: + # Add module to dependency list if target object is defined in other module. + try: + analyzer = ModuleAnalyzer.for_module(guess_modname) + self.directive.record_dependencies.add(analyzer.srcname) + except PycodeError: + pass + + docstrings: list[str] = sum(self.get_doc() or [], []) + if ismock(self.object) and not docstrings: + logger.warning(__('A mocked object is detected: %r'), + self.name, type='autodoc') # check __module__ of object (for members not given explicitly) if check_module: @@ -160,29 +175,41 @@ class ModuleDocumenter(CustomDocumenter): objtype = 'module' content_indent = '' titles_allowed = True + _extra_indent = ' ' - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, 'platform': identity, 'deprecated': bool_option, - 'member-order': identity, 'exclude-members': members_set_option, - 'private-members': bool_option, 'special-members': members_option, - 'imported-members': bool_option, 'ignore-module-all': bool_option - } # type: Dict[str, Callable] + 'member-order': member_order_option, 'exclude-members': exclude_members_option, + 'private-members': members_option, 'special-members': members_option, + 'imported-members': bool_option, 'ignore-module-all': bool_option, + 'no-value': bool_option, + } def __init__(self, *args: Any) -> None: super().__init__(*args) merge_members_option(self.options) + self.__all__: Sequence[str] | None = None + + def add_content(self, more_content: StringList | None) -> None: + old_indent = self.indent + self.indent += self._extra_indent + super().add_content(None) + self.indent = old_indent + if more_content: + for line, src in zip(more_content.data, more_content.items): + self.add_line(line, src[0], src[1]) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: # don't document submodules automatically return False - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: if modname is not None: logger.warning(__('"::" in automodule name doesn\'t make sense'), type='autodoc') @@ -196,6 +223,20 @@ class ModuleDocumenter(CustomDocumenter): type='autodoc') return ret + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + + try: + if not self.options.ignore_module_all: + self.__all__ = inspect.getall(self.object) + except ValueError as exc: + # invalid __all__ found. + logger.warning(__('__all__ should be a list of strings, not %r ' + '(in module %s) -- ignoring __all__') % + (exc.args[0], self.fullname), type='autodoc') + + return ret + def add_directive_header(self, sig: str) -> None: Documenter.add_directive_header(self, sig) @@ -209,37 +250,57 @@ class ModuleDocumenter(CustomDocumenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) - def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, object]]]: + def get_module_members(self) -> dict[str, ObjectMember]: + """Get members of target module.""" + if self.analyzer: + attr_docs = self.analyzer.attr_docs + else: + attr_docs = {} + + members: dict[str, ObjectMember] = {} + for name in dir(self.object): + try: + value = safe_getattr(self.object, name, None) + if ismock(value): + value = undecorate(value) + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) + except AttributeError: + continue + + # annotation only member (ex. attr: int) + for name in inspect.getannotations(self.object): + if name not in members: + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, INSTANCEATTR, + docstring="\n".join(docstring)) + + return members + + def get_object_members(self, want_all: bool) -> tuple[bool, ObjectMembers]: + members = self.get_module_members() if want_all: if (self.options.ignore_module_all or not hasattr(self.object, '__all__')): # for implicit module members, check __module__ to avoid # documenting imported objects - return True, get_module_members(self.object) + return True, list(members.values()) else: - memberlist = self.object.__all__ - # Sometimes __all__ is broken... - if not isinstance(memberlist, (list, tuple)) or not \ - all(isinstance(entry, str) for entry in memberlist): - logger.warning( - __('__all__ should be a list of strings, not %r ' - '(in module %s) -- ignoring __all__') % - (memberlist, self.fullname), - type='autodoc' - ) - # fall back to all members - return True, get_module_members(self.object) + for member in members.values(): + if member.__name__ not in self.__all__: + member.skipped = True + + # fall back to all members + return True, list(members.values()) else: memberlist = self.options.members or [] - ret = [] - for mname in memberlist: - try: - ret.append((mname, safe_getattr(self.object, mname))) - except AttributeError: - logger.warning( - __('missing attribute mentioned in :members: or __all__: ' - 'module %s, attribute %s') % - (safe_getattr(self.object, '__name__', '???'), mname), - type='autodoc' - ) - return False, ret + ret = [] + for name in memberlist: + if name in members: + ret.append(members[name]) + else: + logger.warning(__('missing attribute mentioned in :members: option: ' + 'module %s, attribute %s') % + (safe_getattr(self.object, '__name__', '???'), name), + type='autodoc') + return False, ret diff --git a/docs/mindquantum/docs/_ext/overwriteautosummary_generate.txt b/docs/mindquantum/docs/_ext/overwriteautosummary_generate.txt index 4b0a1b1dd2b410ecab971b13da9993c90d65ef0d..23a4c166e123413b21d42f4eeab1accbc09b4896 100644 --- a/docs/mindquantum/docs/_ext/overwriteautosummary_generate.txt +++ b/docs/mindquantum/docs/_ext/overwriteautosummary_generate.txt @@ -1,22 +1,19 @@ -""" - sphinx.ext.autosummary.generate - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Usable as a library or script to generate automatic RST source files for - items referred to in autosummary:: directives. +"""Generates reST source files for autosummary. - Each generated RST file contains a single auto*:: directive which - extracts the docstring of the referred item. +Usable as a library or script to generate automatic RST source files for +items referred to in autosummary:: directives. - Example Makefile rule:: +Each generated RST file contains a single auto*:: directive which +extracts the docstring of the referred item. - generate: - sphinx-autogen -o source/generated source/*.rst +Example Makefile rule:: - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. + generate: + sphinx-autogen -o source/generated source/*.rst """ +from __future__ import annotations + import argparse import importlib import inspect @@ -26,10 +23,8 @@ import pkgutil import pydoc import re import sys -import warnings -from gettext import NullTranslations from os import path -from typing import Any, Dict, List, NamedTuple, Sequence, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, NamedTuple, Sequence from jinja2 import TemplateNotFound from jinja2.sandbox import SandboxedEnvironment @@ -39,11 +34,14 @@ from sphinx import __display_version__, package_dir from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc.importer import import_module -from sphinx.ext.autosummary import (ImportExceptionGroup, get_documenter, import_by_name, - import_ivar_by_name) +from sphinx.ext.autosummary import ( + ImportExceptionGroup, + get_documenter, + import_by_name, + import_ivar_by_name, +) from sphinx.locale import __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.registry import SphinxComponentRegistry @@ -52,6 +50,9 @@ from sphinx.util.inspect import getall, safe_getattr from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxTemplateLoader +if TYPE_CHECKING: + from gettext import NullTranslations + logger = logging.getLogger(__name__) @@ -61,7 +62,7 @@ class DummyApplication: def __init__(self, translator: NullTranslations) -> None: self.config = Config() self.registry = SphinxComponentRegistry() - self.messagelog: List[str] = [] + self.messagelog: list[str] = [] self.srcdir = "/" self.translator = translator self.verbosity = 0 @@ -91,32 +92,26 @@ class AutosummaryEntry(NamedTuple): def setup_documenters(app: Any) -> None: - from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter, DataDocumenter, - DecoratorDocumenter, ExceptionDocumenter, - FunctionDocumenter, MethodDocumenter, ModuleDocumenter, - NewTypeAttributeDocumenter, NewTypeDataDocumenter, - PropertyDocumenter) - documenters: List[Type[Documenter]] = [ + from sphinx.ext.autodoc import ( + AttributeDocumenter, + ClassDocumenter, + DataDocumenter, + DecoratorDocumenter, + ExceptionDocumenter, + FunctionDocumenter, + MethodDocumenter, + ModuleDocumenter, + PropertyDocumenter, + ) + documenters: list[type[Documenter]] = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, - FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter, - NewTypeDataDocumenter, AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, + FunctionDocumenter, MethodDocumenter, + AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, ] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) -def _simple_info(msg: str) -> None: - warnings.warn('_simple_info() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print(msg) - - -def _simple_warn(msg: str) -> None: - warnings.warn('_simple_warn() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print('WARNING: ' + msg, file=sys.stderr) - - def _underline(title: str, line: str = '=') -> str: if '\n' in title: raise ValueError('Can only underline single lines') @@ -126,14 +121,9 @@ def _underline(title: str, line: str = '=') -> str: class AutosummaryRenderer: """A helper class for rendering.""" - def __init__(self, app: Union[Builder, Sphinx], template_dir: str = None) -> None: + def __init__(self, app: Sphinx) -> None: if isinstance(app, Builder): - warnings.warn('The first argument for AutosummaryRenderer has been ' - 'changed to Sphinx object', - RemovedInSphinx50Warning, stacklevel=2) - if template_dir: - warnings.warn('template_dir argument for AutosummaryRenderer is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) + raise ValueError('Expected a Sphinx application object!') system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path, @@ -144,26 +134,11 @@ class AutosummaryRenderer: self.env.filters['e'] = rst.escape self.env.filters['underline'] = _underline - if isinstance(app, (Sphinx, DummyApplication)): - if app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.translator) - elif isinstance(app, Builder): - if app.app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.app.translator) - - def exists(self, template_name: str) -> bool: - """Check if template file exists.""" - warnings.warn('AutosummaryRenderer.exists() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - self.env.get_template(template_name) - return True - except TemplateNotFound: - return False + if app.translator: + self.env.add_extension("jinja2.ext.i18n") + self.env.install_gettext_translations(app.translator) - def render(self, template_name: str, context: Dict) -> str: + def render(self, template_name: str, context: dict) -> str: """Render a template file.""" try: template = self.env.get_template(template_name) @@ -199,8 +174,14 @@ class ModuleScanner: name, exc, type='autosummary') return False - def scan(self, imported_members: bool) -> List[str]: + def scan(self, imported_members: bool) -> list[str]: members = [] + try: + analyzer = ModuleAnalyzer.for_module(self.object.__name__) + attr_docs = analyzer.find_attr_docs() + except PycodeError: + attr_docs = {} + for name in members_of(self.object, self.app.config): try: value = safe_getattr(self.object, name) @@ -212,7 +193,9 @@ class ModuleScanner: continue try: - if inspect.ismodule(value): + if ('', name) in attr_docs: + imported = False + elif inspect.ismodule(value): # NoQA: SIM114 imported = True elif safe_getattr(value, '__module__') != self.object.__name__: imported = True @@ -222,14 +205,14 @@ class ModuleScanner: imported = False respect_module_all = not self.app.config.autosummary_ignore_module_all - if imported_members: + if ( # list all members up - members.append(name) - elif imported is False: + imported_members # list not-imported members - members.append(name) - elif '__all__' in dir(self.object) and respect_module_all: + or imported is False # list members that have __all__ set + or (respect_module_all and '__all__' in dir(self.object)) + ): members.append(name) return members @@ -249,8 +232,9 @@ def members_of(obj: Any, conf: Config) -> Sequence[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, qualname: str = None) -> str: + recursive: bool, context: dict, + modname: str | None = None, + qualname: str | None = None) -> str: doc = get_documenter(app, obj, parent) def skip_member(obj: Any, name: str, objtype: str) -> bool: @@ -263,11 +247,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, name, exc, type='autosummary') return False - def get_class_members(obj: Any) -> Dict[str, Any]: + def get_class_members(obj: Any) -> dict[str, Any]: members = sphinx.ext.autodoc.get_class_members(obj, [qualname], safe_getattr) return {name: member.object for name, member in members.items()} - def get_module_members(obj: Any) -> Dict[str, Any]: + def get_module_members(obj: Any) -> dict[str, Any]: members = {} for name in members_of(obj, app.config): try: @@ -276,17 +260,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, continue return members - def get_all_members(obj: Any) -> Dict[str, Any]: + def get_all_members(obj: Any) -> dict[str, Any]: if doc.objtype == "module": return get_module_members(obj) elif doc.objtype == "class": return get_class_members(obj) return {} - def get_members(obj: Any, types: Set[str], include_public: List[str] = [], - imported: bool = True) -> Tuple[List[str], List[str]]: - items: List[str] = [] - public: List[str] = [] + def get_members(obj: Any, types: set[str], include_public: list[str] = [], + imported: bool = True) -> tuple[list[str], list[str]]: + items: list[str] = [] + public: list[str] = [] all_members = get_all_members(obj) for name, value in all_members.items(): @@ -308,7 +292,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, public.append(name) return public, items - def get_module_attrs(members: Any) -> Tuple[List[str], List[str]]: + def get_module_attrs(members: Any) -> tuple[list[str], list[str]]: """Find module attributes with docstrings.""" attrs, public = [], [] try: @@ -323,9 +307,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, pass # give up if ModuleAnalyzer fails to parse code return public, attrs - def get_modules(obj: Any) -> Tuple[List[str], List[str]]: - items: 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) @@ -335,15 +327,24 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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] = {} + ns: dict[str, Any] = {} ns.update(context) 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'] = \ @@ -354,7 +355,36 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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'] = \ @@ -389,21 +419,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, return template.render(doc.objtype, ns) -def generate_autosummary_docs(sources: List[str], output_dir: str = None, - suffix: str = '.rst', base_path: str = None, - builder: Builder = None, template_dir: str = None, +def generate_autosummary_docs(sources: list[str], output_dir: str | None = None, + suffix: str = '.rst', base_path: str | None = None, imported_members: bool = False, app: Any = None, overwrite: bool = True, encoding: str = 'utf-8') -> None: - - if builder: - warnings.warn('builder argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - if template_dir: - warnings.warn('template_dir argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - showed_sources = list(sorted(sources)) + showed_sources = sorted(sources) if len(showed_sources) > 20: showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:] logger.info(__('[autosummary] generating autosummary for: %s') % @@ -439,7 +459,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, ensuredir(path) try: - name, obj, parent, modname = import_by_name(entry.name, grouped_exception=True) + name, obj, parent, modname = import_by_name(entry.name) qualname = name.replace(modname + ".", "") except ImportExceptionGroup as exc: try: @@ -448,16 +468,16 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, qualname = name.replace(modname + ".", "") except ImportError as exc2: if exc2.__cause__: - exceptions: List[BaseException] = exc.exceptions + [exc2.__cause__] + exceptions: list[BaseException] = exc.exceptions + [exc2.__cause__] else: exceptions = exc.exceptions + [exc2] - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exceptions)) + 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] = {} + context: dict[str, Any] = {} if app: context.update(app.config.autosummary_context) @@ -484,7 +504,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if content == old_content: continue - elif overwrite: # content has changed + if overwrite: # content has changed with open(filename, 'w', encoding=encoding) as f: f.write(content) new_files.append(filename) @@ -497,19 +517,18 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if new_files: generate_autosummary_docs(new_files, output_dir=output_dir, suffix=suffix, base_path=base_path, - builder=builder, template_dir=template_dir, imported_members=imported_members, app=app, overwrite=overwrite) # -- Finding documented entries in files --------------------------------------- -def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: +def find_autosummary_in_files(filenames: list[str]) -> list[AutosummaryEntry]: """Find out what items are documented in source/*.rst. See `find_autosummary_in_lines`. """ - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] for filename in filenames: with open(filename, encoding='utf-8', errors='ignore') as f: lines = f.read().splitlines() @@ -517,33 +536,31 @@ def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: return documented -def find_autosummary_in_docstring(name: str, module: str = None, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_docstring( + name: str, filename: str | None = None, +) -> list[AutosummaryEntry]: """Find out what items are documented in the given object's docstring. See `find_autosummary_in_lines`. """ - if module: - warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - real_name, obj, parent, modname = import_by_name(name, grouped_exception=True) + real_name, obj, parent, modname = import_by_name(name) lines = pydoc.getdoc(obj).splitlines() return find_autosummary_in_lines(lines, module=name, filename=filename) except AttributeError: pass except ImportExceptionGroup as exc: - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exc.exceptions)) - print('Failed to import %s.\nPossible hints:\n%s' % (name, '\n'.join(errors))) + errors = '\n'.join({f"* {type(e).__name__}: {e}" for e in exc.exceptions}) + print(f'Failed to import {name}.\nPossible hints:\n{errors}') except SystemExit: print("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, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_lines( + 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. @@ -564,10 +581,10 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$') - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] recursive = False - toctree: str = None + toctree: str | None = None template = None current_module = module in_autosummary = False @@ -603,7 +620,7 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st name = name[1:] if current_module and \ not name.startswith(current_module + '.'): - name = "%s.%s" % (current_module, name) + name = f"{current_module}.{name}" documented.append(AutosummaryEntry(name, toctree, template, recursive)) continue @@ -683,12 +700,11 @@ The format of the autosummary directive is documented in the return parser -def main(argv: List[str] = sys.argv[1:]) -> None: - sphinx.locale.setlocale(locale.LC_ALL, '') - sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') - translator, _ = sphinx.locale.init([], None) +def main(argv: list[str] = sys.argv[1:]) -> None: + locale.setlocale(locale.LC_ALL, '') + sphinx.locale.init_console() - app = DummyApplication(translator) + app = DummyApplication(sphinx.locale.get_translator()) logging.setup(app, sys.stdout, sys.stderr) # type: ignore setup_documenters(app) args = get_parser().parse_args(argv) diff --git a/docs/mindquantum/docs/_ext/overwriteobjectiondirective.txt b/docs/mindquantum/docs/_ext/overwriteobjectiondirective.txt index 7e8d31adbd000f457f45d20031c77edc94071edd..1241f6700a13876be0859325afc4078d637192b8 100644 --- a/docs/mindquantum/docs/_ext/overwriteobjectiondirective.txt +++ b/docs/mindquantum/docs/_ext/overwriteobjectiondirective.txt @@ -1,17 +1,11 @@ -""" - sphinx.directives - ~~~~~~~~~~~~~~~~~ +"""Handlers for additional ReST directives.""" - Handlers for additional ReST directives. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import re import inspect import importlib -from typing import TYPE_CHECKING, Any, Dict, Generic, List, Tuple, TypeVar, cast +from typing import TYPE_CHECKING, Any, Generic, List, TypeVar, cast from docutils import nodes from docutils.nodes import Node @@ -19,10 +13,10 @@ from docutils.parsers.rst import directives, roles from sphinx import addnodes from sphinx.addnodes import desc_signature -from sphinx.deprecation import RemovedInSphinx50Warning, deprecated_alias from sphinx.util import docutils, logging from sphinx.util.docfields import DocFieldTransformer, Field, TypedField from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.typing import OptionSpec if TYPE_CHECKING: @@ -32,11 +26,10 @@ if TYPE_CHECKING: # RE to strip backslash escapes nl_escape_re = re.compile(r'\\\n') strip_backslash_re = re.compile(r'\\(.)') - -T = TypeVar('T') +ObjDescT = TypeVar('ObjDescT') logger = logging.getLogger(__name__) -def optional_int(argument): +def optional_int(argument: str) -> int | None: """ Check for an integer argument or None value; raise ``ValueError`` if not. """ @@ -101,7 +94,7 @@ def get_side_effect(name: str): except: return [] -class ObjectDescription(SphinxDirective, Generic[T]): +class ObjectDescription(SphinxDirective, Generic[ObjDescT]): """ Directive to describe a class, function or similar object. Not used directly, but subclassed (in domain-specific directives) to add custom @@ -114,18 +107,20 @@ class ObjectDescription(SphinxDirective, Generic[T]): final_argument_whitespace = True option_spec: OptionSpec = { 'noindex': directives.flag, + 'noindexentry': directives.flag, + 'nocontentsentry': directives.flag, } # types of doc fields that this directive handles, see sphinx.util.docfields - doc_field_types: List[Field] = [] - domain: str = None - objtype: str = None - indexnode: addnodes.index = None + doc_field_types: list[Field] = [] + domain: str | None = None + objtype: str # set when `run` method is called + indexnode: addnodes.index # Warning: this might be removed in future version. Don't touch this from extensions. - _doc_field_type_map: Dict[str, Tuple[Field, bool]] = {} + _doc_field_type_map: dict[str, tuple[Field, bool]] = {} - def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]: + def get_field_type_map(self) -> dict[str, tuple[Field, bool]]: if self._doc_field_type_map == {}: self._doc_field_type_map = {} for field in self.doc_field_types: @@ -139,12 +134,10 @@ class ObjectDescription(SphinxDirective, Generic[T]): return self._doc_field_type_map - def get_signatures(self) -> List[str]: + def get_signatures(self) -> list[str]: """ Retrieve the signatures to document from the directive arguments. By default, signatures are given as arguments, one per line. - - Backslash-escaping of newlines is supported. """ lines = nl_escape_re.sub('', self.arguments[0]).split('\n') if self.config.strip_signature_backslash: @@ -153,7 +146,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): else: return [line.strip() for line in lines] - def handle_signature(self, sig: str, signode: desc_signature) -> T: + def handle_signature(self, sig: str, signode: desc_signature) -> ObjDescT: """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -165,7 +158,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): """ raise ValueError - def add_target_and_index(self, name: T, sig: str, signode: desc_signature) -> None: + def add_target_and_index(self, name: ObjDescT, sig: str, signode: desc_signature) -> None: """ Add cross-reference IDs and entries to self.indexnode, if applicable. @@ -207,8 +200,46 @@ class ObjectDescription(SphinxDirective, Generic[T]): for i in range(1, num+1): ls.append((rst_file, start_num+i)) return ls + + def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]: + """ + Returns a tuple of strings, one entry for each part of the object's + hierarchy (e.g. ``('module', 'submodule', 'Class', 'method')``). The + returned tuple is used to properly nest children within parents in the + table of contents, and can also be used within the + :py:meth:`_toc_entry_name` method. + + This method must not be used outwith table of contents generation. + """ + return () + + def _toc_entry_name(self, sig_node: desc_signature) -> str: + """ + Returns the text of the table of contents entry for the object. + + This function is called once, in :py:meth:`run`, to set the name for the + table of contents entry (a special attribute ``_toc_name`` is set on the + object node, later used in + ``environment.collectors.toctree.TocTreeCollector.process_doc().build_toc()`` + when the table of contents entries are collected). + + To support table of contents entries for their objects, domains must + override this method, also respecting the configuration setting + ``toc_object_entries_show_parents``. Domains must also override + :py:meth:`_object_hierarchy_parts`, with one (string) entry for each part of the + object's hierarchy. The result of this method is set on the signature + node, and can be accessed as ``sig_node['_toc_parts']`` for use within + this method. The resulting tuple is also used to properly nest children + within parents in the table of contents. + + An example implementations of this method is within the python domain + (:meth:`!PyObject._toc_entry_name`). The python domain sets the + ``_toc_parts`` attribute within the :py:meth:`handle_signature()` + method. + """ + return '' - def run(self) -> List[Node]: + def run(self) -> list[Node]: """ Main directive entry function, called by docutils upon encountering the directive. @@ -234,15 +265,28 @@ class ObjectDescription(SphinxDirective, Generic[T]): node = addnodes.desc() node.document = self.state.document + source, line = self.get_source_info() + # If any options were specified to the directive, + # self.state.document.current_line will at this point be set to + # None. To ensure nodes created as part of the signature have a line + # number set, set the document's line number correctly. + # + # Note that we need to subtract one from the line number since + # note_source uses 0-based line numbers. + if line is not None: + line -= 1 + self.state.document.note_source(source, line) node['domain'] = self.domain # 'desctype' is a backwards compatible attribute node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) + node['noindexentry'] = ('noindexentry' in self.options) + node['nocontentsentry'] = ('nocontentsentry' in self.options) if self.domain: node['classes'].append(self.domain) node['classes'].append(node['objtype']) - self.names: List[T] = [] + self.names: list[ObjDescT] = [] signatures = self.get_signatures() for sig in signatures: # add a signature node for each signature in the current unit @@ -260,6 +304,15 @@ class ObjectDescription(SphinxDirective, Generic[T]): signode.clear() signode += addnodes.desc_name(sig, sig) continue # we don't want an index entry here + finally: + # Private attributes for ToC generation. Will be modified or removed + # without notice. + if self.env.app.config.toc_object_entries: + signode['_toc_parts'] = self._object_hierarchy_parts(signode) + signode['_toc_name'] = self._toc_entry_name(signode) + else: + signode['_toc_parts'] = () + signode['_toc_name'] = '' if name not in self.names: self.names.append(name) if not noindex: @@ -269,6 +322,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): contentnode = addnodes.desc_content() node.append(contentnode) + if self.names: # needed for association of version{added,changed} directives self.env.temp_data['object'] = self.names[0] @@ -302,7 +356,7 @@ class ObjectDescription(SphinxDirective, Generic[T]): self.content.items.extend(self.extend_items(self.content.items[0][0], self.content.items[-1][1], len(extra))) except IndexError: logger.warning(f'{self.names[0][0]} has error format.') - self.state.nested_parse(self.content, self.content_offset, contentnode) + nested_parse_with_titles(self.state, self.content, contentnode, self.content_offset) self.transform_content(contentnode) self.env.app.emit('object-description-transform', self.domain, self.objtype, contentnode) @@ -320,14 +374,14 @@ class DefaultRole(SphinxDirective): optional_arguments = 1 final_argument_whitespace = False - def run(self) -> List[Node]: + def run(self) -> list[Node]: if not self.arguments: docutils.unregister_role('') return [] role_name = self.arguments[0] role, messages = roles.role(role_name, self.state_machine.language, self.lineno, self.state.reporter) - if role: + if role: # type: ignore[truthy-function] docutils.register_role('', role) self.env.temp_data['default_role'] = role_name else: @@ -351,7 +405,7 @@ class DefaultDomain(SphinxDirective): final_argument_whitespace = False option_spec: OptionSpec = {} - def run(self) -> List[Node]: + def run(self) -> list[Node]: domain_name = self.arguments[0].lower() # if domain_name not in env.domains: # # try searching by label @@ -363,10 +417,7 @@ class DefaultDomain(SphinxDirective): return [] - - - -def setup(app: "Sphinx") -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value("strip_signature_backslash", False, 'env') directives.register_directive('default-role', DefaultRole) directives.register_directive('default-domain', DefaultDomain) diff --git a/docs/mindquantum/docs/_ext/overwriteviewcode.txt b/docs/mindquantum/docs/_ext/overwriteviewcode.txt index 172780ec56b3ed90e7b0add617257a618cf38ee0..a2fdcfb787bb99e6d96742574675f79151a4848d 100644 --- a/docs/mindquantum/docs/_ext/overwriteviewcode.txt +++ b/docs/mindquantum/docs/_ext/overwriteviewcode.txt @@ -1,18 +1,12 @@ -""" - sphinx.ext.viewcode - ~~~~~~~~~~~~~~~~~~~ +"""Add links to module code in Python object descriptions.""" - Add links to module code in Python object descriptions. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import posixpath import traceback import warnings from os import path -from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast +from typing import Any, Generator, Iterable, cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,12 +16,12 @@ from sphinx import addnodes from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer from sphinx.transforms.post_transforms import SphinxPostTransform -from sphinx.util import get_full_modname, logging, status_iterator +from sphinx.util import get_full_modname, logging +from sphinx.util.display import status_iterator from sphinx.util.nodes import make_refnode @@ -46,13 +40,13 @@ class viewcode_anchor(Element): """ -def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]: +def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str | None: try: return get_full_modname(modname, attribute) except AttributeError: # sphinx.ext.viewcode can't follow class instance attribute # then AttributeError logging output only verbose mode. - logger.verbose('Didn\'t find %s in %s', attribute, modname) + logger.verbose("Didn't find %s in %s", attribute, modname) return None except Exception as e: # sphinx.ext.viewcode follow python domain directives. @@ -67,12 +61,11 @@ def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str def is_supported_builder(builder: Builder) -> bool: if builder.format != 'html': return False - elif builder.name == 'singlehtml': + if builder.name == 'singlehtml': return False - elif builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: + if builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: return False - else: - return True + return True def doctree_read(app: Sphinx, doctree: Node) -> None: @@ -112,7 +105,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: for objnode in list(doctree.findall(addnodes.desc)): if objnode.get('domain') != 'py': continue - names: Set[str] = set() + names: set[str] = set() for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue @@ -211,19 +204,7 @@ class ViewcodeAnchorTransform(SphinxPostTransform): node.parent.remove(node) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node - ) -> Optional[Node]: - # resolve our "viewcode" reference nodes -- they need special treatment - if node['reftype'] == 'viewcode': - warnings.warn('viewcode extension is no longer use pending_xref node. ' - 'Please update your extension.', RemovedInSphinx50Warning) - return make_refnode(app.builder, node['refdoc'], node['reftarget'], - node['refid'], contnode) - - return None - - -def get_module_filename(app: Sphinx, modname: str) -> Optional[str]: +def get_module_filename(app: Sphinx, modname: str) -> str | None: """Get module filename for *modname*.""" source_info = app.emit_firstresult('viewcode-find-source', modname) if source_info: @@ -251,13 +232,13 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool: if path.getmtime(module_filename) <= path.getmtime(page_filename): # generation is not needed if the HTML page is newer than module file. return False - except IOError: + except OSError: pass return True -def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], None, None]: +def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], None, None]: env = app.builder.env if not hasattr(env, '_viewcode_modules'): return @@ -282,7 +263,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non # construct a page name for the highlighted source pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/')) # highlight the source using the builder's highlighter - if env.config.highlight_language in ('python3', 'default', 'none'): + if env.config.highlight_language in {'default', 'none'}: lexer = env.config.highlight_language else: lexer = 'python' @@ -343,10 +324,9 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non stack.pop() html.append('') stack.append(modname + '.') - html.append('
  • %s
  • \n' % ( - urito(posixpath.join(OUTPUT_DIRNAME, 'index'), - posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))), - modname)) + relative_uri = urito(posixpath.join(OUTPUT_DIRNAME, 'index'), + posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))) + html.append(f'
  • {modname}
  • \n') html.append('' * (len(stack) - 1)) context = { 'title': _('Overview: module code'), @@ -357,7 +337,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non yield (posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html') -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) @@ -365,7 +345,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('env-merge-info', env_merge_info) app.connect('env-purge-doc', env_purge_doc) app.connect('html-collect-pages', collect_pages) - app.connect('missing-reference', missing_reference) # app.add_config_value('viewcode_include_modules', [], 'env') # app.add_config_value('viewcode_exclude_modules', [], 'env') app.add_event('viewcode-find-source') @@ -374,5 +353,5 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': sphinx.__display_version__, 'env_version': 1, - 'parallel_read_safe': True + 'parallel_read_safe': True, } diff --git a/docs/mindquantum/docs/requirements.txt b/docs/mindquantum/docs/requirements.txt index 3d9921cabc71a58c8fb11c0d6f72796e9af7a7ba..d01e7ccb0deb0f123ff721825510db0e0fd1e596 100644 --- a/docs/mindquantum/docs/requirements.txt +++ b/docs/mindquantum/docs/requirements.txt @@ -1,6 +1,6 @@ -sphinx == 4.4.0 -docutils == 0.17.1 -myst-parser == 0.18.1 -sphinx_rtd_theme == 1.0.0 +sphinx == 7.0.1 +docutils == 0.20.1 +myst-parser == 2.0.0 +sphinx_rtd_theme == 3.0.0 jieba numpy diff --git a/docs/mindspore/_ext/customdocumenter.txt b/docs/mindspore/_ext/customdocumenter.txt index 2d37ae41f6772a21da2a7dc5c7bff75128e68330..505ed7c57e6d7a30d106ca7a33111df6a8278ad3 100644 --- a/docs/mindspore/_ext/customdocumenter.txt +++ b/docs/mindspore/_ext/customdocumenter.txt @@ -8,7 +8,7 @@ class CustomDocumenter(Documenter): def document_members(self, all_members: bool = False) -> None: """Generate reST for member documentation. - If *all_members* is True, do all members, else those given by + If *all_members* is True, document all members, else those given by *self.options.members*. """ # set current namespace for finding members @@ -16,8 +16,9 @@ class CustomDocumenter(Documenter): if self.objpath: self.env.temp_data['autodoc:class'] = self.objpath[0] - want_all = all_members or self.options.inherited_members or \ - self.options.members is ALL + want_all = (all_members or + self.options.inherited_members or + self.options.members is ALL) # find out which members are documentable members_check_module, members = self.get_object_members(want_all) @@ -59,7 +60,7 @@ class CustomDocumenter(Documenter): ] # document non-skipped members - memberdocumenters = [] # type: List[Tuple[Documenter, bool]] + memberdocumenters: list[tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -111,7 +112,7 @@ class CustomDocumenter(Documenter): if not self.parse_name(): # need a module to import logger.warning( - __('don\'t know which module to import for autodocumenting ' + __("don't know which module to import for autodocumenting " '%r (try placing a "module" or "currentmodule" directive ' 'in the document, or giving an explicit module name)') % self.name, type='autodoc') @@ -126,7 +127,8 @@ class CustomDocumenter(Documenter): # where the attribute documentation would actually be found in. # This is used for situations where you have a module that collects the # functions and classes of internal submodules. - self.real_modname = real_modname or self.get_real_modname() # type: str + guess_modname = self.get_real_modname() + self.real_modname: str = real_modname or guess_modname # try to also get a source code analyzer for attribute docs try: @@ -134,15 +136,28 @@ class CustomDocumenter(Documenter): # parse right now, to get PycodeErrors on parsing (results will # be cached anyway) self.analyzer.find_attr_docs() - except PycodeError as err: - logger.debug('[autodoc] module analyzer failed: %s', err) + except PycodeError as exc: + logger.debug('[autodoc] module analyzer failed: %s', exc) # no source file -- e.g. for builtin and C modules self.analyzer = None # at least add the module.__file__ as a dependency if hasattr(self.module, '__file__') and self.module.__file__: - self.directive.filename_set.add(self.module.__file__) + self.directive.record_dependencies.add(self.module.__file__) else: - self.directive.filename_set.add(self.analyzer.srcname) + self.directive.record_dependencies.add(self.analyzer.srcname) + + if self.real_modname != guess_modname: + # Add module to dependency list if target object is defined in other module. + try: + analyzer = ModuleAnalyzer.for_module(guess_modname) + self.directive.record_dependencies.add(analyzer.srcname) + except PycodeError: + pass + + docstrings: list[str] = sum(self.get_doc() or [], []) + if ismock(self.object) and not docstrings: + logger.warning(__('A mocked object is detected: %r'), + self.name, type='autodoc') # check __module__ of object (for members not given explicitly) if check_module: @@ -160,29 +175,41 @@ class ModuleDocumenter(CustomDocumenter): objtype = 'module' content_indent = '' titles_allowed = True + _extra_indent = ' ' - option_spec = { + option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, - 'noindex': bool_option, 'inherited-members': bool_option, + 'noindex': bool_option, 'inherited-members': inherited_members_option, 'show-inheritance': bool_option, 'synopsis': identity, 'platform': identity, 'deprecated': bool_option, - 'member-order': identity, 'exclude-members': members_set_option, - 'private-members': bool_option, 'special-members': members_option, - 'imported-members': bool_option, 'ignore-module-all': bool_option - } # type: Dict[str, Callable] + 'member-order': member_order_option, 'exclude-members': exclude_members_option, + 'private-members': members_option, 'special-members': members_option, + 'imported-members': bool_option, 'ignore-module-all': bool_option, + 'no-value': bool_option, + } def __init__(self, *args: Any) -> None: super().__init__(*args) merge_members_option(self.options) + self.__all__: Sequence[str] | None = None + + def add_content(self, more_content: StringList | None) -> None: + old_indent = self.indent + self.indent += self._extra_indent + super().add_content(None) + self.indent = old_indent + if more_content: + for line, src in zip(more_content.data, more_content.items): + self.add_line(line, src[0], src[1]) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: # don't document submodules automatically return False - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: if modname is not None: logger.warning(__('"::" in automodule name doesn\'t make sense'), type='autodoc') @@ -196,6 +223,20 @@ class ModuleDocumenter(CustomDocumenter): type='autodoc') return ret + def import_object(self, raiseerror: bool = False) -> bool: + ret = super().import_object(raiseerror) + + try: + if not self.options.ignore_module_all: + self.__all__ = inspect.getall(self.object) + except ValueError as exc: + # invalid __all__ found. + logger.warning(__('__all__ should be a list of strings, not %r ' + '(in module %s) -- ignoring __all__') % + (exc.args[0], self.fullname), type='autodoc') + + return ret + def add_directive_header(self, sig: str) -> None: Documenter.add_directive_header(self, sig) @@ -209,37 +250,57 @@ class ModuleDocumenter(CustomDocumenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) - def get_object_members(self, want_all: bool) -> Tuple[bool, List[Tuple[str, object]]]: + def get_module_members(self) -> dict[str, ObjectMember]: + """Get members of target module.""" + if self.analyzer: + attr_docs = self.analyzer.attr_docs + else: + attr_docs = {} + + members: dict[str, ObjectMember] = {} + for name in dir(self.object): + try: + value = safe_getattr(self.object, name, None) + if ismock(value): + value = undecorate(value) + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, value, docstring="\n".join(docstring)) + except AttributeError: + continue + + # annotation only member (ex. attr: int) + for name in inspect.getannotations(self.object): + if name not in members: + docstring = attr_docs.get(('', name), []) + members[name] = ObjectMember(name, INSTANCEATTR, + docstring="\n".join(docstring)) + + return members + + def get_object_members(self, want_all: bool) -> tuple[bool, ObjectMembers]: + members = self.get_module_members() if want_all: if (self.options.ignore_module_all or not hasattr(self.object, '__all__')): # for implicit module members, check __module__ to avoid # documenting imported objects - return True, get_module_members(self.object) + return True, list(members.values()) else: - memberlist = self.object.__all__ - # Sometimes __all__ is broken... - if not isinstance(memberlist, (list, tuple)) or not \ - all(isinstance(entry, str) for entry in memberlist): - logger.warning( - __('__all__ should be a list of strings, not %r ' - '(in module %s) -- ignoring __all__') % - (memberlist, self.fullname), - type='autodoc' - ) - # fall back to all members - return True, get_module_members(self.object) + for member in members.values(): + if member.__name__ not in self.__all__: + member.skipped = True + + # fall back to all members + return True, list(members.values()) else: memberlist = self.options.members or [] - ret = [] - for mname in memberlist: - try: - ret.append((mname, safe_getattr(self.object, mname))) - except AttributeError: - logger.warning( - __('missing attribute mentioned in :members: or __all__: ' - 'module %s, attribute %s') % - (safe_getattr(self.object, '__name__', '???'), mname), - type='autodoc' - ) - return False, ret + ret = [] + for name in memberlist: + if name in members: + ret.append(members[name]) + else: + logger.warning(__('missing attribute mentioned in :members: option: ' + 'module %s, attribute %s') % + (safe_getattr(self.object, '__name__', '???'), name), + type='autodoc') + return False, ret diff --git a/docs/mindspore/_ext/overwrite_autodoc.txt b/docs/mindspore/_ext/overwrite_autodoc.txt index 58aaf9d028fd7e27382ba02b09be3eec95c8bedc..b6f86c240db5d154251e82249183d9d04f7a5624 100644 --- a/docs/mindspore/_ext/overwrite_autodoc.txt +++ b/docs/mindspore/_ext/overwrite_autodoc.txt @@ -1,44 +1,52 @@ -""" - sphinx.ext.autodoc - ~~~~~~~~~~~~~~~~~~ - - Automatically insert docstrings for functions, classes or whole modules into - the doctree, thus avoiding duplication between docstrings and documentation - for those who like elaborate docstrings. +"""Extension to create automatic documentation from code docstrings. - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. +Automatically insert docstrings for functions, classes or whole modules into +the doctree, thus avoiding duplication between docstrings and documentation +for those who like elaborate docstrings. """ +from __future__ import annotations + import re -import warnings +import sys from inspect import Parameter, Signature -from types import ModuleType -from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterator, List, Optional, Sequence, - Set, Tuple, Type, TypeVar, Union) +from typing import ( + TYPE_CHECKING, + Any, + Callable, + Iterator, + List, + Sequence, + Tuple, + TypeVar, + Union, +) from docutils.statemachine import StringList import sphinx from sphinx.application import Sphinx from sphinx.config import ENUM, Config -from sphinx.deprecation import RemovedInSphinx50Warning, RemovedInSphinx60Warning from sphinx.environment import BuildEnvironment -from sphinx.ext.autodoc.importer import (get_class_members, get_object_members, import_module, - import_object) +from sphinx.ext.autodoc.importer import get_class_members, import_module, import_object from sphinx.ext.autodoc.mock import ismock, mock, undecorate from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.util import inspect, logging, get_full_modname from sphinx.util.docstrings import prepare_docstring, separate_metadata -from sphinx.util.inspect import (evaluate_signature, getdoc, object_description, safe_getattr, - stringify_signature) -from sphinx.util.typing import OptionSpec, get_type_hints, restify -from sphinx.util.typing import stringify as stringify_typehint +from sphinx.util.inspect import ( + evaluate_signature, + getdoc, + object_description, + safe_getattr, + stringify_signature, +) +from sphinx.util.typing import OptionSpec, get_type_hints, restify, stringify_annotation if TYPE_CHECKING: - from sphinx.ext.autodoc.directive import DocumenterBridge + from types import ModuleType + from sphinx.ext.autodoc.directive import DocumenterBridge logger = logging.getLogger(__name__) @@ -88,7 +96,7 @@ INSTANCEATTR = object() SLOTSATTR = object() -def members_option(arg: Any) -> Union[object, List[str]]: +def members_option(arg: Any) -> object | list[str]: """Used to convert the :members: option to auto directives.""" if arg in (None, True): return ALL @@ -98,32 +106,25 @@ def members_option(arg: Any) -> Union[object, List[str]]: return [x.strip() for x in arg.split(',') if x.strip()] -def members_set_option(arg: Any) -> Union[object, Set[str]]: - """Used to convert the :members: option to auto directives.""" - warnings.warn("members_set_option() is deprecated.", - RemovedInSphinx50Warning, stacklevel=2) - if arg is None: - return ALL - return {x.strip() for x in arg.split(',') if x.strip()} - - -def exclude_members_option(arg: Any) -> Union[object, Set[str]]: +def exclude_members_option(arg: Any) -> object | set[str]: """Used to convert the :exclude-members: option.""" if arg in (None, True): return EMPTY return {x.strip() for x in arg.split(',') if x.strip()} -def inherited_members_option(arg: Any) -> Union[object, Set[str]]: - """Used to convert the :members: option to auto directives.""" +def inherited_members_option(arg: Any) -> set[str]: + """Used to convert the :inherited-members: option to auto directives.""" if arg in (None, True): - return 'object' + return {'object'} + elif arg: + return {x.strip() for x in arg.split(',')} else: - return arg + return set() -def member_order_option(arg: Any) -> Optional[str]: - """Used to convert the :members: option to auto directives.""" +def member_order_option(arg: Any) -> str | None: + """Used to convert the :member-order: option to auto directives.""" if arg in (None, True): return None elif arg in ('alphabetical', 'bysource', 'groupwise'): @@ -132,7 +133,7 @@ def member_order_option(arg: Any) -> Optional[str]: raise ValueError(__('invalid value for member-order option: %s') % arg) -def class_doc_from_option(arg: Any) -> Optional[str]: +def class_doc_from_option(arg: Any) -> str | None: """Used to convert the :class-doc-from: option to autoclass directives.""" if arg in ('both', 'class', 'init'): return arg @@ -158,23 +159,10 @@ def bool_option(arg: Any) -> bool: return True -def merge_special_members_option(options: Dict) -> None: - """Merge :special-members: option to :members: option.""" - warnings.warn("merge_special_members_option() is deprecated.", - RemovedInSphinx50Warning, stacklevel=2) - if 'special-members' in options and options['special-members'] is not ALL: - if options.get('members') is ALL: - pass - elif options.get('members'): - for member in options['special-members']: - if member not in options['members']: - options['members'].append(member) - else: - options['members'] = options['special-members'] - - -def merge_members_option(options: Dict) -> None: - """Merge :*-members: option to the :members: option.""" +def merge_members_option(options: dict) -> None: + """Merge :private-members: and :special-members: options to the + :members: option. + """ if options.get('members') is ALL: # merging is not needed when members: ALL return @@ -189,7 +177,7 @@ def merge_members_option(options: Dict) -> None: # Some useful event listener factories for autodoc-process-docstring. -def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable: +def cut_lines(pre: int, post: int = 0, what: str | None = None) -> Callable: """Return a listener that removes the first *pre* and last *post* lines of every docstring. If *what* is a sequence of strings, only docstrings of a type in *what* will be processed. @@ -201,7 +189,7 @@ def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable: This can (and should) be used in place of :confval:`automodule_skip_lines`. """ - def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str] + def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: list[str], ) -> None: if what and what_ not in what: return @@ -217,8 +205,12 @@ def cut_lines(pre: int, post: int = 0, what: str = None) -> Callable: return process -def between(marker: str, what: Sequence[str] = None, keepempty: bool = False, - exclude: bool = False) -> Callable: +def between( + marker: str, + what: Sequence[str] | None = None, + keepempty: bool = False, + exclude: bool = False, +) -> Callable: """Return a listener that either keeps, or if *exclude* is True excludes, lines between lines that match the *marker* regular expression. If no line matches, the resulting docstring would be empty, so no change will be made @@ -229,7 +221,7 @@ def between(marker: str, what: Sequence[str] = None, keepempty: bool = False, """ marker_re = re.compile(marker) - def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: List[str] + def process(app: Sphinx, what_: str, name: str, obj: Any, options: Any, lines: list[str], ) -> None: if what and what_ not in what: return @@ -257,7 +249,7 @@ def between(marker: str, what: Sequence[str] = None, keepempty: bool = False, # But we define this class here to keep compatibility (see #4538) class Options(dict): """A dict/attribute hybrid that returns None on nonexisting keys.""" - def copy(self) -> "Options": + def copy(self) -> Options: return Options(super().copy()) def __getattr__(self, name: str) -> Any: @@ -270,7 +262,7 @@ class Options(dict): class ObjectMember(tuple): """A member of object. - This is used for the result of `Documenter.get_object_members()` to + This is used for the result of `Documenter.get_module_members()` to represent each member of the object. .. Note:: @@ -284,7 +276,7 @@ class ObjectMember(tuple): def __new__(cls, name: str, obj: Any, **kwargs: Any) -> Any: return super().__new__(cls, (name, obj)) # type: ignore - def __init__(self, name: str, obj: Any, docstring: Optional[str] = None, + def __init__(self, name: str, obj: Any, docstring: str | None = None, class_: Any = None, skipped: bool = False) -> None: self.__name__ = name self.object = obj @@ -321,10 +313,10 @@ class Documenter: #: order if autodoc_member_order is set to 'groupwise' member_order = 0 #: true if the generated content may contain titles - titles_allowed = False + titles_allowed = True option_spec: OptionSpec = { - 'noindex': bool_option + 'noindex': bool_option, } def get_attr(self, obj: Any, name: str, *defargs: Any) -> Any: @@ -332,12 +324,12 @@ class Documenter: return autodoc_attrgetter(self.env.app, obj, name, *defargs) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: """Called to see if a member can be documented by this Documenter.""" raise NotImplementedError('must be implemented in subclasses') - def __init__(self, directive: "DocumenterBridge", name: str, indent: str = '') -> None: + def __init__(self, directive: DocumenterBridge, name: str, indent: str = '') -> None: self.directive = directive self.config: Config = directive.env.config self.env: BuildEnvironment = directive.env @@ -348,7 +340,7 @@ class Documenter: # qualified name (all set after resolve_name succeeds) self.modname: str = None self.module: ModuleType = None - self.objpath: List[str] = None + self.objpath: list[str] = None self.fullname: str = None # extra signature items (arguments and return annotation, # also set after resolve_name succeeds) @@ -363,7 +355,7 @@ class Documenter: self.analyzer: ModuleAnalyzer = None @property - def documenters(self) -> Dict[str, Type["Documenter"]]: + def documenters(self) -> dict[str, type[Documenter]]: """Returns registered Documenter classes""" return self.env.app.registry.documenters @@ -374,8 +366,8 @@ class Documenter: else: self.directive.result.append('', source, *lineno) - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: """Resolve the module and name of the object to document given by the arguments and the current module/class. @@ -440,10 +432,9 @@ class Documenter: except ImportError as exc: if raiseerror: raise - else: - logger.warning(exc.args[0], type='autodoc', subtype='import_object') - self.env.note_reread() - return False + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False def get_real_modname(self) -> str: """Get the real module name of an object to document. @@ -466,12 +457,12 @@ class Documenter: return False return True - def format_args(self, **kwargs: Any) -> str: + def format_args(self, **kwargs: Any) -> str | None: """Format the argument signature of *self.object*. Should return None if the object does not have a signature. """ - return None + pass def format_name(self) -> str: """Format the name of *self.object*. @@ -484,7 +475,7 @@ class Documenter: # directives of course) return '.'.join(self.objpath) or self.modname - def _call_format_args(self, **kwargs: Any) -> str: + def _call_format_args(self, **kwargs: Any) -> str | None: if kwargs: try: return self.format_args(**kwargs) @@ -538,9 +529,9 @@ class Documenter: sourcename = self.get_sourcename() # one signature per line, indented by column - prefix = '.. %s:%s:: ' % (domain, directive) + prefix = f'.. {domain}:{directive}:: ' for i, sig_line in enumerate(sig.split("\n")): - self.add_line('%s%s%s' % (prefix, name, sig_line), + self.add_line(f'{prefix}{name}{sig_line}', sourcename) if i == 0: prefix = " " * len(prefix) @@ -552,16 +543,12 @@ class Documenter: # etc. don't support a prepended module name self.add_line(' :module: %s' % self.modname, sourcename) - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: """Decode and return lines of the docstring(s) for the object. When it returns None, autodoc-process-docstring will not be called for this object. """ - if ignore is not None: - warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." - % self.__class__.__name__, - RemovedInSphinx50Warning, stacklevel=2) docstring = getdoc(self.object, self.get_attr, self.config.autodoc_inherit_docstrings, self.parent, self.object_name) spec_tp = [('ops.dense', 'ops.linear'), ('ops.inverse_ext', ['mint.linalg.inv', 'mint.inverse'])] @@ -590,10 +577,10 @@ class Documenter: if docstring: tab_width = self.directive.state.document.settings.tab_width - return [prepare_docstring(docstring, ignore, tab_width)] + return [prepare_docstring(docstring, tab_width)] return [] - def process_doc(self, docstrings: List[List[str]]) -> Iterator[str]: + def process_doc(self, docstrings: list[list[str]]) -> Iterator[str]: """Let the user process the docstrings before adding them.""" for docstringlines in docstrings: if self.env.app: @@ -613,22 +600,18 @@ class Documenter: inspect.safe_getattr(self.object, '__qualname__', None)): # Get the correct location of docstring from self.object # to support inherited methods - fullname = '%s.%s' % (self.object.__module__, self.object.__qualname__) + fullname = f'{self.object.__module__}.{self.object.__qualname__}' else: fullname = self.fullname if self.analyzer: - return '%s:docstring of %s' % (self.analyzer.srcname, fullname) + return f'{self.analyzer.srcname}:docstring of {fullname}' else: return 'docstring of %s' % fullname - def add_content(self, more_content: Optional[StringList], no_docstring: bool = False - ) -> None: + def add_content(self, more_content: StringList | None) -> None: """Add content from docstrings, attribute documentation and user.""" - if no_docstring: - warnings.warn("The 'no_docstring' argument to %s.add_content() is deprecated." - % self.__class__.__name__, - RemovedInSphinx50Warning, stacklevel=2) + docstring = True # set sourcename and add content from attribute documentation sourcename = self.get_sourcename() @@ -637,7 +620,7 @@ class Documenter: if self.objpath: key = ('.'.join(self.objpath[:-1]), self.objpath[-1]) if key in attr_docs: - no_docstring = True + docstring = False # make a copy of docstring for attributes to avoid cache # the change of autodoc-process-docstring event. docstrings = [list(attr_docs[key])] @@ -646,7 +629,7 @@ class Documenter: self.add_line(line, sourcename, i) # add content from docstrings - if not no_docstring: + if docstring: docstrings = self.get_doc() if docstrings is None: # Do not call autodoc-process-docstring on get_doc() returns None. @@ -665,36 +648,17 @@ class Documenter: for line, src in zip(more_content.data, more_content.items): self.add_line(line, src[0], src[1]) - def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: + def get_object_members(self, want_all: bool) -> tuple[bool, ObjectMembers]: """Return `(members_check_module, members)` where `members` is a list of `(membername, member)` pairs of the members of *self.object*. If *want_all* is True, return all members. Else, only return those members given by *self.options.members* (which may also be None). """ - warnings.warn('The implementation of Documenter.get_object_members() will be ' - 'removed from Sphinx-6.0.', RemovedInSphinx60Warning) - members = get_object_members(self.object, self.objpath, self.get_attr, self.analyzer) - if not want_all: - if not self.options.members: - return False, [] # type: ignore - # specific members given - selected = [] - for name in self.options.members: # type: str - if name in members: - selected.append((name, members[name].value)) - else: - logger.warning(__('missing attribute %s in object %s') % - (name, self.fullname), type='autodoc') - return False, selected - elif self.options.inherited_members: - return False, [(m.name, m.value) for m in members.values()] - else: - return False, [(m.name, m.value) for m in members.values() - if m.directly_defined] + raise NotImplementedError('must be implemented in subclasses') - def filter_members(self, members: ObjectMembers, want_all: bool - ) -> List[Tuple[str, Any, bool]]: + def filter_members(self, members: ObjectMembers, want_all: bool, + ) -> list[tuple[str, Any, bool]]: """Filter the given member list. Members are skipped if @@ -709,16 +673,18 @@ class Documenter: ``autodoc-skip-member`` event. """ def is_filtered_inherited_member(name: str, obj: Any) -> bool: + inherited_members = self.options.inherited_members or set() + if inspect.isclass(self.object): for cls in self.object.__mro__: - if cls.__name__ == self.options.inherited_members and cls != self.object: + if cls.__name__ in inherited_members and cls != self.object: # given member is a member of specified *super class* return True - elif name in cls.__dict__: + if name in cls.__dict__: return False - elif name in self.get_attr(cls, '__annotations__', {}): + if name in self.get_attr(cls, '__annotations__', {}): return False - elif isinstance(obj, ObjectMember) and obj.class_ is cls: + if isinstance(obj, ObjectMember) and obj.class_ is cls: return False return False @@ -738,12 +704,7 @@ class Documenter: try: membername, member = obj # if isattr is True, the member is documented as an attribute - if member is INSTANCEATTR: - isattr = True - elif (namespace, membername) in attr_docs: - isattr = True - else: - isattr = False + isattr = member is INSTANCEATTR or (namespace, membername) in attr_docs doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings, self.object, membername) @@ -787,7 +748,7 @@ class Documenter: # special __methods__ if (self.options.special_members and membername in self.options.special_members): - if membername == '__doc__': + if membername == '__doc__': # NoQA: SIM114 keep = False elif is_filtered_inherited_member(membername, obj): keep = False @@ -806,7 +767,7 @@ class Documenter: keep = True elif want_all and isprivate: if has_doc or self.options.undoc_members: - if self.options.private_members is None: + if self.options.private_members is None: # NoQA: SIM114 keep = False elif is_filtered_inherited_member(membername, obj): keep = False @@ -864,7 +825,7 @@ class Documenter: members_check_module, members = self.get_object_members(want_all) # document non-skipped members - memberdocumenters: List[Tuple[Documenter, bool]] = [] + memberdocumenters: list[tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -891,33 +852,35 @@ class Documenter: self.env.temp_data['autodoc:module'] = None self.env.temp_data['autodoc:class'] = None - def sort_members(self, documenters: List[Tuple["Documenter", bool]], - order: str) -> List[Tuple["Documenter", bool]]: + def sort_members(self, documenters: list[tuple[Documenter, bool]], + order: str) -> list[tuple[Documenter, bool]]: """Sort the given member list.""" if order == 'groupwise': # sort by group; alphabetically within groups documenters.sort(key=lambda e: (e[0].member_order, e[0].name)) elif order == 'bysource': + # By default, member discovery order matches source order, + # as dicts are insertion-ordered from Python 3.7. if self.analyzer: # sort by source order, by virtue of the module analyzer tagorder = self.analyzer.tagorder - def keyfunc(entry: Tuple[Documenter, bool]) -> int: + def keyfunc(entry: tuple[Documenter, bool]) -> int: fullname = entry[0].name.split('::')[1] return tagorder.get(fullname, len(tagorder)) documenters.sort(key=keyfunc) - else: - # Assume that member discovery order matches source order. - # This is a reasonable assumption in Python 3.6 and up, where - # module.__dict__ is insertion-ordered. - pass else: # alphabetical documenters.sort(key=lambda e: e[0].name) return documenters - def generate(self, more_content: Optional[StringList] = None, real_modname: str = None, - check_module: bool = False, all_members: bool = False) -> None: + def generate( + self, + more_content: StringList | None = None, + real_modname: str | None = None, + check_module: bool = False, + all_members: bool = False, + ) -> None: """Generate reST for the object given by *self.name*, and possibly for its members. @@ -929,7 +892,7 @@ class Documenter: if not self.parse_name(): # need a module to import logger.warning( - __('don\'t know which module to import for autodocumenting ' + __("don't know which module to import for autodocumenting " '%r (try placing a "module" or "currentmodule" directive ' 'in the document, or giving an explicit module name)') % self.name, type='autodoc') @@ -971,8 +934,7 @@ class Documenter: except PycodeError: pass - docstrings: List[str] = sum(self.get_doc() or [], []) - + docstrings: list[str] = sum(self.get_doc() or [], []) if ismock(self.object) and not docstrings: logger.warning(__('A mocked object is detected: %r'), self.name, type='autodoc') @@ -1017,7 +979,7 @@ class ModuleDocumenter(Documenter): """ objtype = 'module' content_indent = '' - titles_allowed = True + _extra_indent = ' ' option_spec: OptionSpec = { 'members': members_option, 'undoc-members': bool_option, @@ -1026,22 +988,32 @@ class ModuleDocumenter(Documenter): 'platform': identity, 'deprecated': bool_option, 'member-order': member_order_option, 'exclude-members': exclude_members_option, 'private-members': members_option, 'special-members': members_option, - 'imported-members': bool_option, 'ignore-module-all': bool_option + 'imported-members': bool_option, 'ignore-module-all': bool_option, + 'no-value': bool_option, } def __init__(self, *args: Any) -> None: super().__init__(*args) merge_members_option(self.options) - self.__all__: Optional[Sequence[str]] = None + self.__all__: Sequence[str] | None = None + + def add_content(self, more_content: StringList | None) -> None: + old_indent = self.indent + self.indent += self._extra_indent + super().add_content(None) + self.indent = old_indent + if more_content: + for line, src in zip(more_content.data, more_content.items): + self.add_line(line, src[0], src[1]) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: # don't document submodules automatically return False - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: if modname is not None: logger.warning(__('"::" in automodule name doesn\'t make sense'), type='autodoc') @@ -1082,14 +1054,14 @@ class ModuleDocumenter(Documenter): if self.options.deprecated: self.add_line(' :deprecated:', sourcename) - def get_module_members(self) -> Dict[str, ObjectMember]: + def get_module_members(self) -> dict[str, ObjectMember]: """Get members of target module.""" if self.analyzer: attr_docs = self.analyzer.attr_docs else: attr_docs = {} - members: Dict[str, ObjectMember] = {} + members: dict[str, ObjectMember] = {} for name in dir(self.object): try: value = safe_getattr(self.object, name, None) @@ -1109,7 +1081,7 @@ class ModuleDocumenter(Documenter): return members - def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: + def get_object_members(self, want_all: bool) -> tuple[bool, ObjectMembers]: members = self.get_module_members() if want_all: if self.__all__ is None: @@ -1135,14 +1107,14 @@ class ModuleDocumenter(Documenter): type='autodoc') return False, ret - def sort_members(self, documenters: List[Tuple["Documenter", bool]], - order: str) -> List[Tuple["Documenter", bool]]: + def sort_members(self, documenters: list[tuple[Documenter, bool]], + order: str) -> list[tuple[Documenter, bool]]: if order == 'bysource' and self.__all__: # Sort alphabetically first (for members not listed on the __all__) documenters.sort(key=lambda e: e[0].name) # Sort by __all__ - def keyfunc(entry: Tuple[Documenter, bool]) -> int: + def keyfunc(entry: tuple[Documenter, bool]) -> int: name = entry[0].name.split('::')[1] if self.__all__ and name in self.__all__: return self.__all__.index(name) @@ -1160,8 +1132,8 @@ class ModuleLevelDocumenter(Documenter): Specialized Documenter subclass for objects on module level (functions, classes, data/constants). """ - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: if modname is None: if path: modname = path.rstrip('.') @@ -1181,8 +1153,8 @@ class ClassLevelDocumenter(Documenter): Specialized Documenter subclass for objects on class level (methods, attributes). """ - def resolve_name(self, modname: str, parents: Any, path: str, base: Any - ) -> Tuple[str, List[str]]: + def resolve_name(self, modname: str, parents: Any, path: str, base: Any, + ) -> tuple[str, list[str]]: if modname is None: if path: mod_cls = path.rstrip('.') @@ -1214,10 +1186,10 @@ class DocstringSignatureMixin: Mixin for FunctionDocumenter and MethodDocumenter to provide the feature of reading the signature from the docstring. """ - _new_docstrings: List[List[str]] = None - _signatures: List[str] = None + _new_docstrings: list[list[str]] = None + _signatures: list[str] = None - def _find_signature(self) -> Tuple[str, str]: + def _find_signature(self) -> tuple[str | None, str | None]: # candidates of the object name valid_names = [self.objpath[-1]] # type: ignore if isinstance(self, ClassDocumenter): @@ -1253,14 +1225,14 @@ class DocstringSignatureMixin: # re-prepare docstring to ignore more leading indentation tab_width = self.directive.state.document.settings.tab_width # type: ignore self._new_docstrings[i] = prepare_docstring('\n'.join(doclines[j + 1:]), - tabsize=tab_width) + tab_width) if result is None: # first signature result = args, retann else: # subsequent signatures - self._signatures.append("(%s) -> %s" % (args, retann)) + self._signatures.append(f"({args}) -> {retann}") if result: # finish the loop when signature found @@ -1268,10 +1240,10 @@ class DocstringSignatureMixin: return result - def get_doc(self, ignore: int = None) -> List[List[str]]: + def get_doc(self) -> list[list[str]]: if self._new_docstrings is not None: return self._new_docstrings - return super().get_doc(ignore) # type: ignore + return super().get_doc() # type: ignore[misc] def format_signature(self, **kwargs: Any) -> str: if self.args is None and self.config.autodoc_docstring_signature: # type: ignore @@ -1293,7 +1265,10 @@ class DocstringStripSignatureMixin(DocstringSignatureMixin): feature of stripping any function signature from the docstring. """ def format_signature(self, **kwargs: Any) -> str: - if self.args is None and self.config.autodoc_docstring_signature: # type: ignore + if ( + self.args is None + and self.config.autodoc_docstring_signature # type: ignore[attr-defined] + ): # only act if a signature is not explicitly given already, and if # the feature is enabled result = self._find_signature() @@ -1313,13 +1288,13 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ member_order = 30 @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: # supports functions, builtins and bound methods exported at the module level return (inspect.isfunction(member) or inspect.isbuiltin(member) or (inspect.isroutine(member) and isinstance(parent, ModuleDocumenter))) - def format_args(self, **kwargs: Any) -> str: + def format_args(self, **kwargs: Any) -> str | None: if self.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) if self.config.autodoc_typehints_format == "short": @@ -1402,7 +1377,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ return overload.replace(parameters=parameters) - def annotate_to_first_argument(self, func: Callable, typ: Type) -> Optional[Callable]: + def annotate_to_first_argument(self, func: Callable, typ: type) -> Callable | None: """Annotate type hint to the first argument of function if needed.""" try: sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases) @@ -1428,8 +1403,8 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ except (AttributeError, TypeError): # failed to update signature (ex. built-in or extension types) return None - else: - return None + + return func class DecoratorDocumenter(FunctionDocumenter): @@ -1478,6 +1453,11 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: 'class-doc-from': class_doc_from_option, } + # Must be higher than FunctionDocumenter, ClassDocumenter, and + # AttributeDocumenter as NewType can be an attribute and is a class + # after Python 3.10. Before 3.10 it is a kind of function object + priority = 15 + _signature_class: Any = None _signature_method_name: str = None @@ -1497,9 +1477,10 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: merge_members_option(self.options) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: - return isinstance(member, type) + return isinstance(member, type) or ( + isattr and (inspect.isNewType(member) or isinstance(member, TypeVar))) def import_object(self, raiseerror: bool = False) -> bool: ret = super().import_object(raiseerror) @@ -1512,9 +1493,19 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: self.doc_as_attr = False else: self.doc_as_attr = True + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + modname = getattr(self.object, '__module__', self.modname) + if modname != self.modname and self.modname.startswith(modname): + bases = self.modname[len(modname):].strip('.').split('.') + self.objpath = bases + self.objpath + self.modname = modname return ret - def _get_signature(self) -> Tuple[Optional[Any], Optional[str], Optional[Signature]]: + def _get_signature(self) -> tuple[Any | None, str | None, Signature | None]: + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + # Suppress signature + return None, None, None + def get_user_defined_function_or_method(obj: Any, attr: str) -> Any: """ Get the `attr` function or method from `obj`, if it is user-defined. """ if inspect.is_builtin_class_method(obj, attr): @@ -1537,7 +1528,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: call = get_user_defined_function_or_method(type(self.object), '__call__') if call is not None: - if "{0.__module__}.{0.__qualname__}".format(call) in _METACLASS_CALL_BLACKLIST: + if f"{call.__module__}.{call.__qualname__}" in _METACLASS_CALL_BLACKLIST: call = None if call is not None: @@ -1553,7 +1544,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: new = get_user_defined_function_or_method(self.object, '__new__') if new is not None: - if "{0.__module__}.{0.__qualname__}".format(new) in _CLASS_NEW_BLACKLIST: + if f"{new.__module__}.{new.__qualname__}" in _CLASS_NEW_BLACKLIST: new = None if new is not None: @@ -1592,7 +1583,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: # with __init__ in C and no `__text_signature__`. return None, None, None - def format_args(self, **kwargs: Any) -> str: + def format_args(self, **kwargs: Any) -> str | None: if self.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) if self.config.autodoc_typehints_format == "short": @@ -1611,6 +1602,20 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: return stringify_signature(sig, show_return_annotation=False, **kwargs) + def _find_signature(self) -> tuple[str, str]: + result = super()._find_signature() + if result is not None: + # Strip a return value from signature of constructor in docstring (first entry) + result = (result[0], None) + + for i, sig in enumerate(self._signatures): + if sig.endswith(' -> None'): + # Strip a return value from signatures of constructor in docstring (subsequent + # entries) + self._signatures[i] = sig[:-8] + + return result + def format_signature(self, **kwargs: Any) -> str: if self.doc_as_attr: return '' @@ -1643,7 +1648,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: return "\n".join(sigs) - def get_overloaded_signatures(self) -> List[Signature]: + def get_overloaded_signatures(self) -> list[Signature]: if self._signature_class and self._signature_method_name: for cls in self._signature_class.__mro__: try: @@ -1651,7 +1656,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: analyzer.analyze() qualname = '.'.join([cls.__qualname__, self._signature_method_name]) if qualname in analyzer.overloads: - return analyzer.overloads.get(qualname) + return analyzer.overloads.get(qualname, []) elif qualname in analyzer.tagorder: # the constructor is defined in the class, but not overridden. return [] @@ -1660,7 +1665,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: return [] - def get_canonical_fullname(self) -> Optional[str]: + def get_canonical_fullname(self) -> str | None: __modname__ = safe_getattr(self.object, '__module__', self.modname) __qualname__ = safe_getattr(self.object, '__qualname__', None) if __qualname__ is None: @@ -1681,18 +1686,22 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: self.directivetype = 'attribute' super().add_directive_header(sig) + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + return + if self.analyzer and '.'.join(self.objpath) in self.analyzer.finals: self.add_line(' :final:', sourcename) canonical_fullname = self.get_canonical_fullname() - if not self.doc_as_attr and canonical_fullname and self.fullname != canonical_fullname: + if (not self.doc_as_attr and not inspect.isNewType(self.object) + and canonical_fullname and self.fullname != canonical_fullname): self.add_line(' :canonical: %s' % canonical_fullname, sourcename) # add inheritance info, if wanted if not self.doc_as_attr and self.options.show_inheritance: if inspect.getorigbases(self.object): # A subclass of generic types - # refs: PEP-560 + # refs: PEP-560 bases = list(self.object.__orig_bases__) elif hasattr(self.object, '__bases__') and len(self.object.__bases__): # A normal class @@ -1712,14 +1721,15 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: self.add_line('', sourcename) self.add_line(' ' + _('Bases: %s') % ', '.join(base_classes), sourcename) - def get_object_members(self, want_all: bool) -> Tuple[bool, ObjectMembers]: - members = get_class_members(self.object, self.objpath, self.get_attr) + def get_object_members(self, want_all: bool) -> tuple[bool, ObjectMembers]: + members = get_class_members(self.object, self.objpath, self.get_attr, + self.config.autodoc_inherit_docstrings) if not want_all: if not self.options.members: return False, [] # type: ignore # specific members given selected = [] - for name in self.options.members: # type: str + for name in self.options.members: if name in members: selected.append(members[name]) else: @@ -1731,7 +1741,28 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: else: return False, [m for m in members.values() if m.class_ == self.object] - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: + if isinstance(self.object, TypeVar): + if self.object.__doc__ == TypeVar.__doc__: + return [] + if sys.version_info[:2] < (3, 10): + if inspect.isNewType(self.object) or isinstance(self.object, TypeVar): + parts = self.modname.strip('.').split('.') + orig_objpath = self.objpath + for i in range(len(parts)): + new_modname = '.'.join(parts[:len(parts) - i]) + new_objpath = parts[len(parts) - i:] + orig_objpath + try: + analyzer = ModuleAnalyzer.for_module(new_modname) + analyzer.analyze() + key = ('', new_objpath[-1]) + comment = list(analyzer.attr_docs.get(key, [])) + if comment: + self.objpath = new_objpath + self.modname = new_modname + return [comment] + except PycodeError: + pass if self.doc_as_attr: # Don't show the docstring of the class when it is an alias. comment = self.get_variable_comment() @@ -1811,9 +1842,9 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: docstrings[i] = mint_doc tab_width = self.directive.state.document.settings.tab_width - return [prepare_docstring(docstring, ignore, tab_width) for docstring in docstrings] + return [prepare_docstring(docstring, tab_width) for docstring in docstrings] - def get_variable_comment(self) -> Optional[List[str]]: + def get_variable_comment(self) -> list[str] | None: try: key = ('', '.'.join(self.objpath)) if self.doc_as_attr: @@ -1825,12 +1856,43 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: except PycodeError: return None - def add_content(self, more_content: Optional[StringList], no_docstring: bool = False - ) -> None: + def add_content(self, more_content: StringList | None) -> None: + if inspect.isNewType(self.object): + if self.config.autodoc_typehints_format == "short": + supertype = restify(self.object.__supertype__, "smart") + else: + supertype = restify(self.object.__supertype__) + + more_content = StringList([_('alias of %s') % supertype, ''], source='') + if isinstance(self.object, TypeVar): + attrs = [repr(self.object.__name__)] + for constraint in self.object.__constraints__: + if self.config.autodoc_typehints_format == "short": + attrs.append(stringify_annotation(constraint, "smart")) + else: + attrs.append(stringify_annotation(constraint)) + if self.object.__bound__: + if self.config.autodoc_typehints_format == "short": + bound = restify(self.object.__bound__, "smart") + else: + bound = restify(self.object.__bound__) + attrs.append(r"bound=\ " + bound) + if self.object.__covariant__: + attrs.append("covariant=True") + if self.object.__contravariant__: + attrs.append("contravariant=True") + + more_content = StringList( + [_('alias of TypeVar(%s)') % ", ".join(attrs), ''], + source='', + ) if self.doc_as_attr and self.modname != self.get_real_modname(): - # override analyzer to obtain doccomment around its definition. - self.analyzer = ModuleAnalyzer.for_module(self.modname) - self.analyzer.analyze() + try: + # override analyzer to obtain doccomment around its definition. + self.analyzer = ModuleAnalyzer.for_module(self.modname) + self.analyzer.analyze() + except PycodeError: + pass if self.doc_as_attr and not self.get_variable_comment(): try: @@ -1849,8 +1911,13 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type: return super().document_members(all_members) - def generate(self, more_content: Optional[StringList] = None, real_modname: str = None, - check_module: bool = False, all_members: bool = False) -> None: + def generate( + self, + more_content: StringList | None = None, + real_modname: str | None = None, + check_module: bool = False, + all_members: bool = False, + ) -> None: # Do not pass real_modname and use the name from the __module__ # attribute of the class. # If a class gets imported into the module real_modname @@ -1869,10 +1936,10 @@ class ExceptionDocumenter(ClassDocumenter): member_order = 10 # needs a higher priority than ClassDocumenter - priority = 10 + priority = ClassDocumenter.priority + 5 @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: return isinstance(member, type) and issubclass(member, BaseException) @@ -1884,7 +1951,7 @@ class DataDocumenterMixinBase: modname: str = None parent: Any = None object: Any = None - objpath: List[str] = None + objpath: list[str] = None def should_suppress_directive_header(self) -> bool: """Check directive header should be suppressed.""" @@ -1895,7 +1962,7 @@ class DataDocumenterMixinBase: return False def update_content(self, more_content: StringList) -> None: - """Update docstring for the NewType object.""" + """Update docstring, for example with TypeVar variance.""" pass @@ -1922,75 +1989,6 @@ class GenericAliasMixin(DataDocumenterMixinBase): super().update_content(more_content) -class NewTypeMixin(DataDocumenterMixinBase): - """ - Mixin for DataDocumenter and AttributeDocumenter to provide the feature for - supporting NewTypes. - """ - - def should_suppress_directive_header(self) -> bool: - return (inspect.isNewType(self.object) or - super().should_suppress_directive_header()) - - def update_content(self, more_content: StringList) -> None: - if inspect.isNewType(self.object): - if self.config.autodoc_typehints_format == "short": - supertype = restify(self.object.__supertype__, "smart") - else: - supertype = restify(self.object.__supertype__) - - more_content.append(_('alias of %s') % supertype, '') - more_content.append('', '') - - super().update_content(more_content) - - -class TypeVarMixin(DataDocumenterMixinBase): - """ - Mixin for DataDocumenter and AttributeDocumenter to provide the feature for - supporting TypeVars. - """ - - def should_suppress_directive_header(self) -> bool: - return (isinstance(self.object, TypeVar) or - super().should_suppress_directive_header()) - - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: - if ignore is not None: - warnings.warn("The 'ignore' argument to autodoc.%s.get_doc() is deprecated." - % self.__class__.__name__, - RemovedInSphinx50Warning, stacklevel=2) - - if isinstance(self.object, TypeVar): - if self.object.__doc__ != TypeVar.__doc__: - return super().get_doc() # type: ignore - else: - return [] - else: - return super().get_doc() # type: ignore - - def update_content(self, more_content: StringList) -> None: - if isinstance(self.object, TypeVar): - attrs = [repr(self.object.__name__)] - for constraint in self.object.__constraints__: - attrs.append(stringify_typehint(constraint)) - if self.object.__bound__: - if self.config.autodoc_typehints_format == "short": - bound = restify(self.object.__bound__, "smart") - else: - bound = restify(self.object.__bound__) - attrs.append(r"bound=\ " + bound) - if self.object.__covariant__: - attrs.append("covariant=True") - if self.object.__contravariant__: - attrs.append("contravariant=True") - - more_content.append(_('alias of TypeVar(%s)') % ", ".join(attrs), '') - more_content.append('', '') - - super().update_content(more_content) - - class UninitializedGlobalVariableMixin(DataDocumenterMixinBase): """ Mixin for DataDocumenter to provide the feature for supporting uninitialized @@ -2016,23 +2014,22 @@ class UninitializedGlobalVariableMixin(DataDocumenterMixinBase): if raiseerror: raise - else: - logger.warning(exc.args[0], type='autodoc', subtype='import_object') - self.env.note_reread() - return False + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False def should_suppress_value_header(self) -> bool: return (self.object is UNINITIALIZED_ATTR or super().should_suppress_value_header()) - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: if self.object is UNINITIALIZED_ATTR: return [] else: - return super().get_doc(ignore) # type: ignore + return super().get_doc() # type: ignore -class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, +class DataDocumenter(GenericAliasMixin, UninitializedGlobalVariableMixin, ModuleLevelDocumenter): """ Specialized Documenter subclass for data items. @@ -2045,7 +2042,7 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, option_spec["no-value"] = bool_option @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: return isinstance(parent, ModuleDocumenter) and isattr @@ -2095,7 +2092,12 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases) if self.objpath[-1] in annotations: - objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + if self.config.autodoc_typehints_format == "short": + objrepr = stringify_annotation(annotations.get(self.objpath[-1]), + "smart") + else: + objrepr = stringify_annotation(annotations.get(self.objpath[-1]), + "fully-qualified-except-typing") self.add_line(' :type: ' + objrepr, sourcename) try: @@ -2115,7 +2117,7 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, real_modname = self.get_attr(self.parent or self.object, '__module__', None) return real_modname or self.modname - def get_module_comment(self, attrname: str) -> Optional[List[str]]: + def get_module_comment(self, attrname: str) -> list[str] | None: try: analyzer = ModuleAnalyzer.for_module(self.modname) analyzer.analyze() @@ -2127,16 +2129,15 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, return None - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: # Check the variable has a docstring-comment comment = self.get_module_comment(self.objpath[-1]) if comment: return [comment] else: - return super().get_doc(ignore) + return super().get_doc() - def add_content(self, more_content: Optional[StringList], no_docstring: bool = False - ) -> None: + def add_content(self, more_content: StringList | None) -> None: # Disable analyzing variable comment on Documenter.add_content() to control it on # DataDocumenter.add_content() self.analyzer = None @@ -2145,25 +2146,7 @@ class DataDocumenter(GenericAliasMixin, NewTypeMixin, TypeVarMixin, more_content = StringList() self.update_content(more_content) - super().add_content(more_content, no_docstring=no_docstring) - - -class NewTypeDataDocumenter(DataDocumenter): - """ - Specialized Documenter subclass for NewTypes. - - Note: This must be invoked before FunctionDocumenter because NewType is a kind of - function object. - """ - - objtype = 'newtypedata' - directivetype = 'data' - priority = FunctionDocumenter.priority + 1 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - return inspect.isNewType(member) and isattr + super().add_content(more_content) class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: ignore @@ -2176,7 +2159,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: priority = 1 # must be more than FunctionDocumenter @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: return inspect.isroutine(member) and not isinstance(parent, ModuleDocumenter) @@ -2197,7 +2180,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return ret - def format_args(self, **kwargs: Any) -> str: + def format_args(self, **kwargs: Any) -> str | None: if self.config.autodoc_typehints in ('none', 'description'): kwargs.setdefault('show_annotation', False) if self.config.autodoc_typehints_format == "short": @@ -2313,7 +2296,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: return overload.replace(parameters=parameters) - def annotate_to_first_argument(self, func: Callable, typ: Type) -> Optional[Callable]: + def annotate_to_first_argument(self, func: Callable, typ: type) -> Callable | None: """Annotate type hint to the first argument of function if needed.""" try: sig = inspect.signature(func, type_aliases=self.config.autodoc_type_aliases) @@ -2339,10 +2322,10 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type: except (AttributeError, TypeError): # failed to update signature (ex. built-in or extension types) return None - else: - return None - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + return func + + def get_doc(self) -> list[list[str]] | None: if self._new_docstrings is not None: # docstring already returned previously, then modified by # `DocstringSignatureMixin`. Just return the previously-computed @@ -2401,13 +2384,13 @@ class NonDataDescriptorMixin(DataDocumenterMixinBase): return (not getattr(self, 'non_data_descriptor', False) or super().should_suppress_directive_header()) - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: if getattr(self, 'non_data_descriptor', False): # the docstring of non datadescriptor is very probably the wrong thing # to display return None else: - return super().get_doc(ignore) # type: ignore + return super().get_doc() # type: ignore class SlotsMixin(DataDocumenterMixinBase): @@ -2419,10 +2402,7 @@ class SlotsMixin(DataDocumenterMixinBase): """Check the subject is an attribute in __slots__.""" try: __slots__ = inspect.getslots(self.parent) - if __slots__ and self.objpath[-1] in __slots__: - return True - else: - return False + return bool(__slots__) and self.objpath[-1] in __slots__ except (ValueError, TypeError): return False @@ -2439,7 +2419,7 @@ class SlotsMixin(DataDocumenterMixinBase): else: return super().should_suppress_value_header() - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: if self.object is SLOTSATTR: try: __slots__ = inspect.getslots(self.parent) @@ -2453,16 +2433,7 @@ class SlotsMixin(DataDocumenterMixinBase): (self.parent.__qualname__, exc), type='autodoc') return [] else: - return super().get_doc(ignore) # type: ignore - - @property - def _datadescriptor(self) -> bool: - warnings.warn('AttributeDocumenter._datadescriptor() is deprecated.', - RemovedInSphinx60Warning) - if self.object is SLOTSATTR: - return True - else: - return False + return super().get_doc() # type: ignore class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase): @@ -2484,10 +2455,9 @@ class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase): # An instance variable defined in __init__(). if self.get_attribute_comment(parent, self.objpath[-1]): # type: ignore return True - elif self.is_runtime_instance_attribute_not_commented(parent): + if self.is_runtime_instance_attribute_not_commented(parent): return True - else: - return False + return False def is_runtime_instance_attribute_not_commented(self, parent: Any) -> bool: """Check the subject is an attribute defined in __init__() without comment.""" @@ -2528,21 +2498,20 @@ class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase): if raiseerror: raise - else: - logger.warning(exc.args[0], type='autodoc', subtype='import_object') - self.env.note_reread() - return False + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False def should_suppress_value_header(self) -> bool: return (self.object is self.RUNTIME_INSTANCE_ATTRIBUTE or super().should_suppress_value_header()) - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: if (self.object is self.RUNTIME_INSTANCE_ATTRIBUTE and self.is_runtime_instance_attribute_not_commented(self.parent)): return None else: - return super().get_doc(ignore) # type: ignore + return super().get_doc() # type: ignore class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase): @@ -2559,10 +2528,7 @@ class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase): def is_uninitialized_instance_attribute(self, parent: Any) -> bool: """Check the subject is an annotation only attribute.""" annotations = get_type_hints(parent, None, self.config.autodoc_type_aliases) - if self.objpath[-1] in annotations: - return True - else: - return False + return self.objpath[-1] in annotations def import_object(self, raiseerror: bool = False) -> bool: """Check the exisitence of uninitialized instance attribute when failed to import @@ -2584,24 +2550,23 @@ class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase): if raiseerror: raise - else: - logger.warning(exc.args[0], type='autodoc', subtype='import_object') - self.env.note_reread() - return False + logger.warning(exc.args[0], type='autodoc', subtype='import_object') + self.env.note_reread() + return False def should_suppress_value_header(self) -> bool: return (self.object is UNINITIALIZED_ATTR or super().should_suppress_value_header()) - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: if self.object is UNINITIALIZED_ATTR: return None else: - return super().get_doc(ignore) # type: ignore + return super().get_doc() # type: ignore -class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: ignore - TypeVarMixin, RuntimeInstanceAttributeMixin, +class AttributeDocumenter(GenericAliasMixin, SlotsMixin, # type: ignore + RuntimeInstanceAttributeMixin, UninitializedInstanceAttributeMixin, NonDataDescriptorMixin, DocstringStripSignatureMixin, ClassLevelDocumenter): """ @@ -2622,41 +2587,19 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: return inspect.isfunction(obj) or inspect.isbuiltin(obj) or inspect.ismethod(obj) @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: if isinstance(parent, ModuleDocumenter): return False - elif inspect.isattributedescriptor(member): + if inspect.isattributedescriptor(member): return True - elif not inspect.isroutine(member) and not isinstance(member, type): + if not inspect.isroutine(member) and not isinstance(member, type): return True - else: - return False + return False def document_members(self, all_members: bool = False) -> None: pass - def isinstanceattribute(self) -> bool: - """Check the subject is an instance attribute.""" - warnings.warn('AttributeDocumenter.isinstanceattribute() is deprecated.', - RemovedInSphinx50Warning) - # uninitialized instance variable (PEP-526) - with mock(self.config.autodoc_mock_imports): - try: - ret = import_object(self.modname, self.objpath[:-1], 'class', - attrgetter=self.get_attr, - warningiserror=self.config.autodoc_warningiserror) - self.parent = ret[3] - annotations = get_type_hints(self.parent, None, - self.config.autodoc_type_aliases) - if self.objpath[-1] in annotations: - self.object = UNINITIALIZED_ATTR - return True - except ImportError: - pass - - return False - def update_annotations(self, parent: Any) -> None: """Update __annotations__ to support type_comment and so on.""" try: @@ -2717,7 +2660,12 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: annotations = get_type_hints(self.parent, None, self.config.autodoc_type_aliases) if self.objpath[-1] in annotations: - objrepr = stringify_typehint(annotations.get(self.objpath[-1])) + if self.config.autodoc_typehints_format == "short": + objrepr = stringify_annotation(annotations.get(self.objpath[-1]), + "smart") + else: + objrepr = stringify_annotation(annotations.get(self.objpath[-1]), + "fully-qualified-except-typing") self.add_line(' :type: ' + objrepr, sourcename) try: @@ -2730,7 +2678,7 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: except ValueError: pass - def get_attribute_comment(self, parent: Any, attrname: str) -> Optional[List[str]]: + def get_attribute_comment(self, parent: Any, attrname: str) -> list[str] | None: for cls in inspect.getmro(parent): try: module = safe_getattr(cls, '__module__') @@ -2747,7 +2695,7 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: return None - def get_doc(self, ignore: int = None) -> Optional[List[List[str]]]: + def get_doc(self) -> list[list[str]] | None: # Check the attribute has a docstring-comment comment = self.get_attribute_comment(self.parent, self.objpath[-1]) if comment: @@ -2759,12 +2707,11 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: # ref: https://github.com/sphinx-doc/sphinx/issues/7805 orig = self.config.autodoc_inherit_docstrings self.config.autodoc_inherit_docstrings = False # type: ignore - return super().get_doc(ignore) + return super().get_doc() finally: self.config.autodoc_inherit_docstrings = orig # type: ignore - def add_content(self, more_content: Optional[StringList], no_docstring: bool = False - ) -> None: + def add_content(self, more_content: StringList | None) -> None: # Disable analyzing attribute comment on Documenter.add_content() to control it on # AttributeDocumenter.add_content() self.analyzer = None @@ -2772,7 +2719,7 @@ class AttributeDocumenter(GenericAliasMixin, NewTypeMixin, SlotsMixin, # type: if more_content is None: more_content = StringList() self.update_content(more_content) - super().add_content(more_content, no_docstring) + super().add_content(more_content) class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # type: ignore @@ -2786,7 +2733,7 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # priority = AttributeDocumenter.priority + 1 @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any + def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any, ) -> bool: if isinstance(parent, ClassDocumenter): if inspect.isproperty(member): @@ -2815,6 +2762,16 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # self.isclassmethod = False return ret + def format_args(self, **kwargs: Any) -> str | None: + func = self._get_property_getter() + if func is None: + return None + + # update the annotations of the property getter + self.env.app.emit('autodoc-before-process-signature', func, False) + # correctly format the arguments for a property + return super().format_args(**kwargs) + def document_members(self, all_members: bool = False) -> None: pass @@ -2830,50 +2787,33 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): # if self.isclassmethod: self.add_line(' :classmethod:', sourcename) - if safe_getattr(self.object, 'fget', None): # property - func = self.object.fget - elif safe_getattr(self.object, 'func', None): # cached_property - func = self.object.func - else: - func = None - - if func and self.config.autodoc_typehints != 'none': - try: - signature = inspect.signature(func, - type_aliases=self.config.autodoc_type_aliases) - if signature.return_annotation is not Parameter.empty: - objrepr = stringify_typehint(signature.return_annotation) - self.add_line(' :type: ' + objrepr, sourcename) - except TypeError as exc: - logger.warning(__("Failed to get a function signature for %s: %s"), - self.fullname, exc) - return None - except ValueError: - return None - - -class NewTypeAttributeDocumenter(AttributeDocumenter): - """ - Specialized Documenter subclass for NewTypes. - - Note: This must be invoked before MethodDocumenter because NewType is a kind of - function object. - """ - - objtype = 'newvarattribute' - directivetype = 'attribute' - priority = MethodDocumenter.priority + 1 - - @classmethod - def can_document_member(cls, member: Any, membername: str, isattr: bool, parent: Any - ) -> bool: - return not isinstance(parent, ModuleDocumenter) and inspect.isNewType(member) + func = self._get_property_getter() + if func is None or self.config.autodoc_typehints == 'none': + return + try: + signature = inspect.signature(func, + type_aliases=self.config.autodoc_type_aliases) + if signature.return_annotation is not Parameter.empty: + if self.config.autodoc_typehints_format == "short": + objrepr = stringify_annotation(signature.return_annotation, "smart") + else: + objrepr = stringify_annotation(signature.return_annotation, + "fully-qualified-except-typing") + self.add_line(' :type: ' + objrepr, sourcename) + except TypeError as exc: + logger.warning(__("Failed to get a function signature for %s: %s"), + self.fullname, exc) + pass + except ValueError: + pass -def get_documenters(app: Sphinx) -> Dict[str, Type[Documenter]]: - """Returns registered Documenter classes""" - warnings.warn("get_documenters() is deprecated.", RemovedInSphinx50Warning, stacklevel=2) - return app.registry.documenters + def _get_property_getter(self): + if safe_getattr(self.object, 'fget', None): # property + return self.object.fget + if safe_getattr(self.object, 'func', None): # cached_property + return self.object.func + return None def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any: @@ -2885,40 +2825,20 @@ def autodoc_attrgetter(app: Sphinx, obj: Any, name: str, *defargs: Any) -> Any: return safe_getattr(obj, name, *defargs) -def migrate_autodoc_member_order(app: Sphinx, config: Config) -> None: - if config.autodoc_member_order == 'alphabetic': - # RemovedInSphinx50Warning - logger.warning(__('autodoc_member_order now accepts "alphabetical" ' - 'instead of "alphabetic". Please update your setting.')) - config.autodoc_member_order = 'alphabetical' # type: ignore - - -# for compatibility -from sphinx.ext.autodoc.deprecated import DataDeclarationDocumenter # NOQA -from sphinx.ext.autodoc.deprecated import GenericAliasDocumenter # NOQA -from sphinx.ext.autodoc.deprecated import InstanceAttributeDocumenter # NOQA -from sphinx.ext.autodoc.deprecated import SingledispatchFunctionDocumenter # NOQA -from sphinx.ext.autodoc.deprecated import SingledispatchMethodDocumenter # NOQA -from sphinx.ext.autodoc.deprecated import SlotsAttributeDocumenter # NOQA -from sphinx.ext.autodoc.deprecated import TypeVarDocumenter # NOQA - - -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.add_autodocumenter(ModuleDocumenter) app.add_autodocumenter(ClassDocumenter) app.add_autodocumenter(ExceptionDocumenter) app.add_autodocumenter(DataDocumenter) - app.add_autodocumenter(NewTypeDataDocumenter) app.add_autodocumenter(FunctionDocumenter) app.add_autodocumenter(DecoratorDocumenter) app.add_autodocumenter(MethodDocumenter) app.add_autodocumenter(AttributeDocumenter) app.add_autodocumenter(PropertyDocumenter) - app.add_autodocumenter(NewTypeAttributeDocumenter) app.add_config_value('autoclass_content', 'class', True, ENUM('both', 'class', 'init')) app.add_config_value('autodoc_member_order', 'alphabetical', True, - ENUM('alphabetic', 'alphabetical', 'bysource', 'groupwise')) + ENUM('alphabetical', 'bysource', 'groupwise')) app.add_config_value('autodoc_class_signature', 'mixed', True, ENUM('mixed', 'separated')) app.add_config_value('autodoc_default_options', {}, True) app.add_config_value('autodoc_docstring_signature', True, True) @@ -2926,9 +2846,9 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_config_value('autodoc_typehints', "signature", True, ENUM("signature", "description", "none", "both")) app.add_config_value('autodoc_typehints_description_target', 'all', True, - ENUM('all', 'documented')) + ENUM('all', 'documented', 'documented_params')) app.add_config_value('autodoc_type_aliases', {}, True) - app.add_config_value('autodoc_typehints_format', "fully-qualified", 'env', + app.add_config_value('autodoc_typehints_format', "short", 'env', ENUM("fully-qualified", "short")) app.add_config_value('autodoc_warningiserror', True, True) app.add_config_value('autodoc_inherit_docstrings', True, True) @@ -2938,8 +2858,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.add_event('autodoc-skip-member') app.add_event('autodoc-process-bases') - app.connect('config-inited', migrate_autodoc_member_order, priority=800) - app.setup_extension('sphinx.ext.autodoc.preserve_defaults') app.setup_extension('sphinx.ext.autodoc.type_comment') app.setup_extension('sphinx.ext.autodoc.typehints') diff --git a/docs/mindspore/_ext/overwriteautosummary_generate.txt b/docs/mindspore/_ext/overwriteautosummary_generate.txt index 6014b006a3c3587ffe4f921be187489078918fc2..51163af969addc818e0665da15fd8f024e6d8f0c 100644 --- a/docs/mindspore/_ext/overwriteautosummary_generate.txt +++ b/docs/mindspore/_ext/overwriteautosummary_generate.txt @@ -1,22 +1,19 @@ -""" - sphinx.ext.autosummary.generate - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - Usable as a library or script to generate automatic RST source files for - items referred to in autosummary:: directives. +"""Generates reST source files for autosummary. - Each generated RST file contains a single auto*:: directive which - extracts the docstring of the referred item. +Usable as a library or script to generate automatic RST source files for +items referred to in autosummary:: directives. - Example Makefile rule:: +Each generated RST file contains a single auto*:: directive which +extracts the docstring of the referred item. - generate: - sphinx-autogen -o source/generated source/*.rst +Example Makefile rule:: - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. + generate: + sphinx-autogen -o source/generated source/*.rst """ +from __future__ import annotations + import argparse import importlib import inspect @@ -26,10 +23,8 @@ import pkgutil import pydoc import re import sys -import warnings -from gettext import NullTranslations from os import path -from typing import Any, Dict, List, NamedTuple, Sequence, Set, Tuple, Type, Union +from typing import TYPE_CHECKING, Any, NamedTuple, Sequence from jinja2 import TemplateNotFound from jinja2.sandbox import SandboxedEnvironment @@ -39,11 +34,14 @@ from sphinx import __display_version__, package_dir from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.config import Config -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.ext.autodoc import Documenter from sphinx.ext.autodoc.importer import import_module -from sphinx.ext.autosummary import (ImportExceptionGroup, get_documenter, import_by_name, - import_ivar_by_name) +from sphinx.ext.autosummary import ( + ImportExceptionGroup, + get_documenter, + import_by_name, + import_ivar_by_name, +) from sphinx.locale import __ from sphinx.pycode import ModuleAnalyzer, PycodeError from sphinx.registry import SphinxComponentRegistry @@ -52,6 +50,9 @@ from sphinx.util.inspect import getall, safe_getattr from sphinx.util.osutil import ensuredir from sphinx.util.template import SphinxTemplateLoader +if TYPE_CHECKING: + from gettext import NullTranslations + logger = logging.getLogger(__name__) @@ -61,7 +62,7 @@ class DummyApplication: def __init__(self, translator: NullTranslations) -> None: self.config = Config() self.registry = SphinxComponentRegistry() - self.messagelog: List[str] = [] + self.messagelog: list[str] = [] self.srcdir = "/" self.translator = translator self.verbosity = 0 @@ -95,32 +96,26 @@ class AutosummaryEntry(NamedTuple): def setup_documenters(app: Any) -> None: - from sphinx.ext.autodoc import (AttributeDocumenter, ClassDocumenter, DataDocumenter, - DecoratorDocumenter, ExceptionDocumenter, - FunctionDocumenter, MethodDocumenter, ModuleDocumenter, - NewTypeAttributeDocumenter, NewTypeDataDocumenter, - PropertyDocumenter) - documenters: List[Type[Documenter]] = [ + from sphinx.ext.autodoc import ( + AttributeDocumenter, + ClassDocumenter, + DataDocumenter, + DecoratorDocumenter, + ExceptionDocumenter, + FunctionDocumenter, + MethodDocumenter, + ModuleDocumenter, + PropertyDocumenter, + ) + documenters: list[type[Documenter]] = [ ModuleDocumenter, ClassDocumenter, ExceptionDocumenter, DataDocumenter, - FunctionDocumenter, MethodDocumenter, NewTypeAttributeDocumenter, - NewTypeDataDocumenter, AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, + FunctionDocumenter, MethodDocumenter, + AttributeDocumenter, DecoratorDocumenter, PropertyDocumenter, ] for documenter in documenters: app.registry.add_documenter(documenter.objtype, documenter) -def _simple_info(msg: str) -> None: - warnings.warn('_simple_info() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print(msg) - - -def _simple_warn(msg: str) -> None: - warnings.warn('_simple_warn() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - print('WARNING: ' + msg, file=sys.stderr) - - def _underline(title: str, line: str = '=') -> str: if '\n' in title: raise ValueError('Can only underline single lines') @@ -130,14 +125,9 @@ def _underline(title: str, line: str = '=') -> str: class AutosummaryRenderer: """A helper class for rendering.""" - def __init__(self, app: Union[Builder, Sphinx], template_dir: str = None) -> None: + def __init__(self, app: Sphinx) -> None: if isinstance(app, Builder): - warnings.warn('The first argument for AutosummaryRenderer has been ' - 'changed to Sphinx object', - RemovedInSphinx50Warning, stacklevel=2) - if template_dir: - warnings.warn('template_dir argument for AutosummaryRenderer is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) + raise ValueError('Expected a Sphinx application object!') system_templates_path = [os.path.join(package_dir, 'ext', 'autosummary', 'templates')] loader = SphinxTemplateLoader(app.srcdir, app.config.templates_path, @@ -148,26 +138,11 @@ class AutosummaryRenderer: self.env.filters['e'] = rst.escape self.env.filters['underline'] = _underline - if isinstance(app, (Sphinx, DummyApplication)): - if app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.translator) - elif isinstance(app, Builder): - if app.app.translator: - self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.app.translator) - - def exists(self, template_name: str) -> bool: - """Check if template file exists.""" - warnings.warn('AutosummaryRenderer.exists() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - self.env.get_template(template_name) - return True - except TemplateNotFound: - return False + if app.translator: + self.env.add_extension("jinja2.ext.i18n") + self.env.install_gettext_translations(app.translator) - def render(self, template_name: str, context: Dict) -> str: + def render(self, template_name: str, context: dict) -> str: """Render a template file.""" try: template = self.env.get_template(template_name) @@ -203,8 +178,14 @@ class ModuleScanner: name, exc, type='autosummary') return False - def scan(self, imported_members: bool) -> List[str]: + def scan(self, imported_members: bool) -> list[str]: members = [] + try: + analyzer = ModuleAnalyzer.for_module(self.object.__name__) + attr_docs = analyzer.find_attr_docs() + except PycodeError: + attr_docs = {} + for name in members_of(self.object, self.app.config): try: value = safe_getattr(self.object, name) @@ -216,7 +197,9 @@ class ModuleScanner: continue try: - if inspect.ismodule(value): + if ('', name) in attr_docs: + imported = False + elif inspect.ismodule(value): # NoQA: SIM114 imported = True elif safe_getattr(value, '__module__') != self.object.__name__: imported = True @@ -226,14 +209,14 @@ class ModuleScanner: imported = False respect_module_all = not self.app.config.autosummary_ignore_module_all - if imported_members: + if ( # list all members up - members.append(name) - elif imported is False: + imported_members # list not-imported members - members.append(name) - elif '__all__' in dir(self.object) and respect_module_all: + or imported is False # list members that have __all__ set + or (respect_module_all and '__all__' in dir(self.object)) + ): members.append(name) return members @@ -253,8 +236,9 @@ def members_of(obj: Any, conf: Config) -> Sequence[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, qualname: str = None) -> str: + recursive: bool, context: dict, + modname: str | None = None, + qualname: str | None = None) -> str: doc = get_documenter(app, obj, parent) def skip_member(obj: Any, name: str, objtype: str) -> bool: @@ -267,11 +251,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, name, exc, type='autosummary') return False - def get_class_members(obj: Any) -> Dict[str, Any]: + def get_class_members(obj: Any) -> dict[str, Any]: members = sphinx.ext.autodoc.get_class_members(obj, [qualname], safe_getattr) return {name: member.object for name, member in members.items()} - def get_module_members(obj: Any) -> Dict[str, Any]: + def get_module_members(obj: Any) -> dict[str, Any]: members = {} for name in members_of(obj, app.config): try: @@ -280,17 +264,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, continue return members - def get_all_members(obj: Any) -> Dict[str, Any]: + def get_all_members(obj: Any) -> dict[str, Any]: if doc.objtype == "module": return get_module_members(obj) elif doc.objtype == "class": return get_class_members(obj) return {} - def get_members(obj: Any, types: Set[str], include_public: List[str] = [], - imported: bool = True) -> Tuple[List[str], List[str]]: - items: List[str] = [] - public: List[str] = [] + def get_members(obj: Any, types: set[str], include_public: list[str] = [], + imported: bool = True) -> tuple[list[str], list[str]]: + items: list[str] = [] + public: list[str] = [] all_members = get_all_members(obj) for name, value in all_members.items(): @@ -312,7 +296,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, public.append(name) return public, items - def get_module_attrs(members: Any) -> Tuple[List[str], List[str]]: + def get_module_attrs(members: Any) -> tuple[list[str], list[str]]: """Find module attributes with docstrings.""" attrs, public = [], [] try: @@ -327,9 +311,17 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, pass # give up if ModuleAnalyzer fails to parse code return public, attrs - def get_modules(obj: Any) -> Tuple[List[str], List[str]]: - items: 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) @@ -339,15 +331,24 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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] = {} + ns: dict[str, Any] = {} ns.update(context) 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'] = \ @@ -358,7 +359,36 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, 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'] = \ @@ -393,21 +423,11 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any, return template.render(doc.objtype, ns) -def generate_autosummary_docs(sources: List[str], output_dir: str = None, - suffix: str = '.rst', base_path: str = None, - builder: Builder = None, template_dir: str = None, +def generate_autosummary_docs(sources: list[str], output_dir: str | None = None, + suffix: str = '.rst', base_path: str | None = None, imported_members: bool = False, app: Any = None, overwrite: bool = True, encoding: str = 'utf-8') -> None: - - if builder: - warnings.warn('builder argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - if template_dir: - warnings.warn('template_dir argument for generate_autosummary_docs() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - - showed_sources = list(sorted(sources)) + showed_sources = sorted(sources) if len(showed_sources) > 20: showed_sources = showed_sources[:10] + ['...'] + showed_sources[-10:] logger.info(__('[autosummary] generating autosummary for: %s') % @@ -443,7 +463,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, ensuredir(path) try: - name, obj, parent, modname = import_by_name(entry.name, grouped_exception=True) + name, obj, parent, modname = import_by_name(entry.name) qualname = name.replace(modname + ".", "") except ImportExceptionGroup as exc: try: @@ -452,16 +472,16 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, qualname = name.replace(modname + ".", "") except ImportError as exc2: if exc2.__cause__: - exceptions: List[BaseException] = exc.exceptions + [exc2.__cause__] + exceptions: list[BaseException] = exc.exceptions + [exc2.__cause__] else: exceptions = exc.exceptions + [exc2] - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exceptions)) + 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] = {} + context: dict[str, Any] = {} if app: context.update(app.config.autosummary_context) @@ -533,7 +553,7 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if content == old_content: continue - elif overwrite: # content has changed + if overwrite: # content has changed with open(filename, 'w', encoding=encoding) as f: f.write(content) new_files.append(filename) @@ -546,19 +566,18 @@ def generate_autosummary_docs(sources: List[str], output_dir: str = None, if new_files: generate_autosummary_docs(new_files, output_dir=output_dir, suffix=suffix, base_path=base_path, - builder=builder, template_dir=template_dir, imported_members=imported_members, app=app, overwrite=overwrite) # -- Finding documented entries in files --------------------------------------- -def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: +def find_autosummary_in_files(filenames: list[str]) -> list[AutosummaryEntry]: """Find out what items are documented in source/*.rst. See `find_autosummary_in_lines`. """ - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] for filename in filenames: with open(filename, encoding='utf-8', errors='ignore') as f: lines = f.read().splitlines() @@ -566,33 +585,31 @@ def find_autosummary_in_files(filenames: List[str]) -> List[AutosummaryEntry]: return documented -def find_autosummary_in_docstring(name: str, module: str = None, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_docstring( + name: str, filename: str | None = None, +) -> list[AutosummaryEntry]: """Find out what items are documented in the given object's docstring. See `find_autosummary_in_lines`. """ - if module: - warnings.warn('module argument for find_autosummary_in_docstring() is deprecated.', - RemovedInSphinx50Warning, stacklevel=2) - try: - real_name, obj, parent, modname = import_by_name(name, grouped_exception=True) + real_name, obj, parent, modname = import_by_name(name) lines = pydoc.getdoc(obj).splitlines() return find_autosummary_in_lines(lines, module=name, filename=filename) except AttributeError: pass except ImportExceptionGroup as exc: - errors = list(set("* %s: %s" % (type(e).__name__, e) for e in exc.exceptions)) - print('Failed to import %s.\nPossible hints:\n%s' % (name, '\n'.join(errors))) + errors = '\n'.join({f"* {type(e).__name__}: {e}" for e in exc.exceptions}) + print(f'Failed to import {name}.\nPossible hints:\n{errors}') except SystemExit: print("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, filename: str = None - ) -> List[AutosummaryEntry]: +def find_autosummary_in_lines( + 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. @@ -613,10 +630,10 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st toctree_arg_re = re.compile(r'^\s+:toctree:\s*(.*?)\s*$') template_arg_re = re.compile(r'^\s+:template:\s*(.*?)\s*$') - documented: List[AutosummaryEntry] = [] + documented: list[AutosummaryEntry] = [] recursive = False - toctree: str = None + toctree: str | None = None template = None current_module = module in_autosummary = False @@ -652,7 +669,7 @@ def find_autosummary_in_lines(lines: List[str], module: str = None, filename: st name = name[1:] if current_module and \ not name.startswith(current_module + '.'): - name = "%s.%s" % (current_module, name) + name = f"{current_module}.{name}" documented.append(AutosummaryEntry(name, toctree, template, recursive)) continue @@ -732,12 +749,11 @@ The format of the autosummary directive is documented in the return parser -def main(argv: List[str] = sys.argv[1:]) -> None: - sphinx.locale.setlocale(locale.LC_ALL, '') - sphinx.locale.init_console(os.path.join(package_dir, 'locale'), 'sphinx') - translator, _ = sphinx.locale.init([], None) +def main(argv: list[str] = sys.argv[1:]) -> None: + locale.setlocale(locale.LC_ALL, '') + sphinx.locale.init_console() - app = DummyApplication(translator) + app = DummyApplication(sphinx.locale.get_translator()) logging.setup(app, sys.stdout, sys.stderr) # type: ignore setup_documenters(app) args = get_parser().parse_args(argv) diff --git a/docs/mindspore/_ext/overwriteobjectiondirective.txt b/docs/mindspore/_ext/overwriteobjectiondirective.txt index 2088e9f22a6a82ffb26935a9f8de8306646145f7..761d35fb5c36344fab630d3b5abfb58e81dae06f 100644 --- a/docs/mindspore/_ext/overwriteobjectiondirective.txt +++ b/docs/mindspore/_ext/overwriteobjectiondirective.txt @@ -1,17 +1,11 @@ -""" - sphinx.directives - ~~~~~~~~~~~~~~~~~ +"""Handlers for additional ReST directives.""" - Handlers for additional ReST directives. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import re import inspect import importlib -from typing import TYPE_CHECKING, Any, Dict, Generic, List, Tuple, TypeVar, cast +from typing import TYPE_CHECKING, Any, Generic, List, TypeVar, cast from docutils import nodes from docutils.nodes import Node @@ -19,12 +13,11 @@ from docutils.parsers.rst import directives, roles from sphinx import addnodes from sphinx.addnodes import desc_signature -from sphinx.deprecation import RemovedInSphinx50Warning, deprecated_alias from sphinx.util import docutils, logging from sphinx.util.docfields import DocFieldTransformer, Field, TypedField from sphinx.util.docutils import SphinxDirective +from sphinx.util.nodes import nested_parse_with_titles from sphinx.util.typing import OptionSpec -from sphinx.environment.collectors.toctree import N if TYPE_CHECKING: from sphinx.application import Sphinx @@ -34,11 +27,11 @@ if TYPE_CHECKING: nl_escape_re = re.compile(r'\\\n') strip_backslash_re = re.compile(r'\\(.)') -logger = logging.getLogger(__name__) +ObjDescT = TypeVar('ObjDescT') -# T = TypeVar('T') +logger = logging.getLogger(__name__) -def optional_int(argument: str) -> int: +def optional_int(argument: str) -> int | None: """ Check for an integer argument or None value; raise ``ValueError`` if not. """ @@ -122,7 +115,7 @@ def get_platforms(name: str): except: return [] -class ObjectDescription(SphinxDirective, Generic[N]): +class ObjectDescription(SphinxDirective, Generic[ObjDescT]): """ Directive to describe a class, function or similar object. Not used directly, but subclassed (in domain-specific directives) to add custom @@ -135,18 +128,20 @@ class ObjectDescription(SphinxDirective, Generic[N]): final_argument_whitespace = True option_spec: OptionSpec = { 'noindex': directives.flag, - } # type: Dict[str, DirectiveOption] + 'noindexentry': directives.flag, + 'nocontentsentry': directives.flag, + } # types of doc fields that this directive handles, see sphinx.util.docfields - doc_field_types: List[Field] = [] - domain: str = None - objtype: str = None - indexnode: addnodes.index = None + doc_field_types: list[Field] = [] + domain: str | None = None + objtype: str # set when `run` method is called + indexnode: addnodes.index # Warning: this might be removed in future version. Don't touch this from extensions. - _doc_field_type_map: Dict[str, Tuple[Field, bool]] = {} + _doc_field_type_map: dict[str, tuple[Field, bool]] = {} - def get_field_type_map(self) -> Dict[str, Tuple[Field, bool]]: + def get_field_type_map(self) -> dict[str, tuple[Field, bool]]: if self._doc_field_type_map == {}: self._doc_field_type_map = {} for field in self.doc_field_types: @@ -160,12 +155,10 @@ class ObjectDescription(SphinxDirective, Generic[N]): return self._doc_field_type_map - def get_signatures(self) -> List[str]: + def get_signatures(self) -> list[str]: """ Retrieve the signatures to document from the directive arguments. By default, signatures are given as arguments, one per line. - - Backslash-escaping of newlines is supported. """ lines = nl_escape_re.sub('', self.arguments[0]).split('\n') if self.config.strip_signature_backslash: @@ -174,7 +167,7 @@ class ObjectDescription(SphinxDirective, Generic[N]): else: return [line.strip() for line in lines] - def handle_signature(self, sig: str, signode: desc_signature): + def handle_signature(self, sig: str, signode: desc_signature) -> ObjDescT: """ Parse the signature *sig* into individual nodes and append them to *signode*. If ValueError is raised, parsing is aborted and the whole @@ -186,7 +179,7 @@ class ObjectDescription(SphinxDirective, Generic[N]): """ raise ValueError - def add_target_and_index(self, name, sig: str, signode: desc_signature) -> None: + def add_target_and_index(self, name: ObjDescT, sig: str, signode: desc_signature) -> None: """ Add cross-reference IDs and entries to self.indexnode, if applicable. @@ -217,6 +210,44 @@ class ObjectDescription(SphinxDirective, Generic[N]): """ pass + def _object_hierarchy_parts(self, sig_node: desc_signature) -> tuple[str, ...]: + """ + Returns a tuple of strings, one entry for each part of the object's + hierarchy (e.g. ``('module', 'submodule', 'Class', 'method')``). The + returned tuple is used to properly nest children within parents in the + table of contents, and can also be used within the + :py:meth:`_toc_entry_name` method. + + This method must not be used outwith table of contents generation. + """ + return () + + def _toc_entry_name(self, sig_node: desc_signature) -> str: + """ + Returns the text of the table of contents entry for the object. + + This function is called once, in :py:meth:`run`, to set the name for the + table of contents entry (a special attribute ``_toc_name`` is set on the + object node, later used in + ``environment.collectors.toctree.TocTreeCollector.process_doc().build_toc()`` + when the table of contents entries are collected). + + To support table of contents entries for their objects, domains must + override this method, also respecting the configuration setting + ``toc_object_entries_show_parents``. Domains must also override + :py:meth:`_object_hierarchy_parts`, with one (string) entry for each part of the + object's hierarchy. The result of this method is set on the signature + node, and can be accessed as ``sig_node['_toc_parts']`` for use within + this method. The resulting tuple is also used to properly nest children + within parents in the table of contents. + + An example implementations of this method is within the python domain + (:meth:`!PyObject._toc_entry_name`). The python domain sets the + ``_toc_parts`` attribute within the :py:meth:`handle_signature()` + method. + """ + return '' + def check_class_end(self, content): for i in content: if not i.startswith('.. include::') and i != "\n" and i != "": @@ -229,7 +260,7 @@ class ObjectDescription(SphinxDirective, Generic[N]): ls.append((rst_file, start_num+i)) return ls - def run(self) -> List[Node]: + def run(self) -> list[Node]: """ Main directive entry function, called by docutils upon encountering the directive. @@ -255,15 +286,28 @@ class ObjectDescription(SphinxDirective, Generic[N]): node = addnodes.desc() node.document = self.state.document + source, line = self.get_source_info() + # If any options were specified to the directive, + # self.state.document.current_line will at this point be set to + # None. To ensure nodes created as part of the signature have a line + # number set, set the document's line number correctly. + # + # Note that we need to subtract one from the line number since + # note_source uses 0-based line numbers. + if line is not None: + line -= 1 + self.state.document.note_source(source, line) node['domain'] = self.domain # 'desctype' is a backwards compatible attribute node['objtype'] = node['desctype'] = self.objtype node['noindex'] = noindex = ('noindex' in self.options) + node['noindexentry'] = ('noindexentry' in self.options) + node['nocontentsentry'] = ('nocontentsentry' in self.options) if self.domain: node['classes'].append(self.domain) node['classes'].append(node['objtype']) - self.names = [] # type: List[Any] + self.names: list[ObjDescT] = [] signatures = self.get_signatures() for sig in signatures: # add a signature node for each signature in the current unit @@ -281,6 +325,15 @@ class ObjectDescription(SphinxDirective, Generic[N]): signode.clear() signode += addnodes.desc_name(sig, sig) continue # we don't want an index entry here + finally: + # Private attributes for ToC generation. Will be modified or removed + # without notice. + if self.env.app.config.toc_object_entries: + signode['_toc_parts'] = self._object_hierarchy_parts(signode) + signode['_toc_name'] = self._toc_entry_name(signode) + else: + signode['_toc_parts'] = () + signode['_toc_name'] = '' if name not in self.names: self.names.append(name) if not noindex: @@ -290,6 +343,7 @@ class ObjectDescription(SphinxDirective, Generic[N]): contentnode = addnodes.desc_content() node.append(contentnode) + if self.names: # needed for association of version{added,changed} directives self.env.temp_data['object'] = self.names[0] @@ -434,7 +488,7 @@ class ObjectDescription(SphinxDirective, Generic[N]): self.content.items.extend(self.extend_items(self.content.items[0][0], self.content.items[-1][1], len(extra))) except IndexError: logger.warning(f'{self.names[0][0]} has error format.') - self.state.nested_parse(self.content, self.content_offset, contentnode) + nested_parse_with_titles(self.state, self.content, contentnode, self.content_offset) self.transform_content(contentnode) self.env.app.emit('object-description-transform', self.domain, self.objtype, contentnode) @@ -452,14 +506,14 @@ class DefaultRole(SphinxDirective): optional_arguments = 1 final_argument_whitespace = False - def run(self) -> List[Node]: + def run(self) -> list[Node]: if not self.arguments: docutils.unregister_role('') return [] role_name = self.arguments[0] role, messages = roles.role(role_name, self.state_machine.language, self.lineno, self.state.reporter) - if role: + if role: # type: ignore[truthy-function] docutils.register_role('', role) self.env.temp_data['default_role'] = role_name else: @@ -483,7 +537,7 @@ class DefaultDomain(SphinxDirective): final_argument_whitespace = False option_spec: OptionSpec = {} - def run(self) -> List[Node]: + def run(self) -> list[Node]: domain_name = self.arguments[0].lower() # if domain_name not in env.domains: # # try searching by label @@ -494,7 +548,8 @@ class DefaultDomain(SphinxDirective): self.env.temp_data['default_domain'] = self.env.domains.get(domain_name) return [] -def setup(app: "Sphinx") -> Dict[str, Any]: + +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value("strip_signature_backslash", False, 'env') directives.register_directive('default-role', DefaultRole) directives.register_directive('default-domain', DefaultDomain) diff --git a/docs/mindspore/_ext/overwriteviewcode.txt b/docs/mindspore/_ext/overwriteviewcode.txt index 6b67087770dc200552aed4db09465a7663d151de..98374badbfd15db080d35c56025eafeb895faae1 100644 --- a/docs/mindspore/_ext/overwriteviewcode.txt +++ b/docs/mindspore/_ext/overwriteviewcode.txt @@ -1,18 +1,11 @@ -""" - sphinx.ext.viewcode - ~~~~~~~~~~~~~~~~~~~ +"""Add links to module code in Python object descriptions.""" - Add links to module code in Python object descriptions. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import posixpath import traceback -import warnings from os import path -from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast +from typing import Any, Generator, Iterable, cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,12 +15,12 @@ from sphinx import addnodes from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer from sphinx.transforms.post_transforms import SphinxPostTransform -from sphinx.util import get_full_modname, logging, status_iterator +from sphinx.util import get_full_modname, logging +from sphinx.util.display import status_iterator from sphinx.util.nodes import make_refnode @@ -46,13 +39,13 @@ class viewcode_anchor(Element): """ -def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]: +def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str | None: try: return get_full_modname(modname, attribute) except AttributeError: # sphinx.ext.viewcode can't follow class instance attribute # then AttributeError logging output only verbose mode. - logger.verbose('Didn\'t find %s in %s', attribute, modname) + logger.verbose("Didn't find %s in %s", attribute, modname) return None except Exception as e: # sphinx.ext.viewcode follow python domain directives. @@ -67,12 +60,11 @@ def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str def is_supported_builder(builder: Builder) -> bool: if builder.format != 'html': return False - elif builder.name == 'singlehtml': + if builder.name == 'singlehtml': return False - elif builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: + if builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: return False - else: - return True + return True def doctree_read(app: Sphinx, doctree: Node) -> None: @@ -122,7 +114,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: for objnode in list(doctree.findall(addnodes.desc)): if objnode.get('domain') != 'py': continue - names: Set[str] = set() + names: set[str] = set() for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue @@ -232,19 +224,7 @@ class ViewcodeAnchorTransform(SphinxPostTransform): node.parent.remove(node) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node - ) -> Optional[Node]: - # resolve our "viewcode" reference nodes -- they need special treatment - if node['reftype'] == 'viewcode': - warnings.warn('viewcode extension is no longer use pending_xref node. ' - 'Please update your extension.', RemovedInSphinx50Warning) - return make_refnode(app.builder, node['refdoc'], node['reftarget'], - node['refid'], contnode) - - return None - - -def get_module_filename(app: Sphinx, modname: str) -> Optional[str]: +def get_module_filename(app: Sphinx, modname: str) -> str | None: """Get module filename for *modname*.""" source_info = app.emit_firstresult('viewcode-find-source', modname) if source_info: @@ -272,13 +252,13 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool: if path.getmtime(module_filename) <= path.getmtime(page_filename): # generation is not needed if the HTML page is newer than module file. return False - except IOError: + except OSError: pass return True -def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], None, None]: +def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], None, None]: env = app.builder.env if not hasattr(env, '_viewcode_modules'): return @@ -303,7 +283,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non # construct a page name for the highlighted source pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/')) # highlight the source using the builder's highlighter - if env.config.highlight_language in ('python3', 'default', 'none'): + if env.config.highlight_language in {'default', 'none'}: lexer = env.config.highlight_language else: lexer = 'python' @@ -376,10 +356,9 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non stack.pop() html.append('') stack.append(modname + '.') - html.append('
  • %s
  • \n' % ( - urito(posixpath.join(OUTPUT_DIRNAME, 'index'), - posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))), - modname)) + relative_uri = urito(posixpath.join(OUTPUT_DIRNAME, 'index'), + posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))) + html.append(f'
  • {modname}
  • \n') html.append('' * (len(stack) - 1)) context = { 'title': _('Overview: module code'), @@ -390,7 +369,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non yield (posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html') -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) @@ -398,7 +377,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('env-merge-info', env_merge_info) app.connect('env-purge-doc', env_purge_doc) app.connect('html-collect-pages', collect_pages) - app.connect('missing-reference', missing_reference) # app.add_config_value('viewcode_include_modules', [], 'env') # app.add_config_value('viewcode_exclude_modules', [], 'env') app.add_event('viewcode-find-source') @@ -407,5 +385,5 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': sphinx.__display_version__, 'env_version': 1, - 'parallel_read_safe': True + 'parallel_read_safe': True, } diff --git a/docs/mindspore/_ext/overwriteviewcode_en.txt b/docs/mindspore/_ext/overwriteviewcode_en.txt index a61bcece86be0ea42a8bcd707a3d7d9d42b4933b..48b945d415bf84d93b6ddc4a47ee22a8305b7b81 100644 --- a/docs/mindspore/_ext/overwriteviewcode_en.txt +++ b/docs/mindspore/_ext/overwriteviewcode_en.txt @@ -1,18 +1,11 @@ -""" - sphinx.ext.viewcode - ~~~~~~~~~~~~~~~~~~~ +"""Add links to module code in Python object descriptions.""" - Add links to module code in Python object descriptions. - - :copyright: Copyright 2007-2022 by the Sphinx team, see AUTHORS. - :license: BSD, see LICENSE for details. -""" +from __future__ import annotations import posixpath import traceback -import warnings from os import path -from typing import Any, Dict, Generator, Iterable, Optional, Set, Tuple, cast +from typing import Any, Generator, Iterable, cast from docutils import nodes from docutils.nodes import Element, Node @@ -22,12 +15,12 @@ from sphinx import addnodes from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.builders.html import StandaloneHTMLBuilder -from sphinx.deprecation import RemovedInSphinx50Warning from sphinx.environment import BuildEnvironment from sphinx.locale import _, __ from sphinx.pycode import ModuleAnalyzer from sphinx.transforms.post_transforms import SphinxPostTransform -from sphinx.util import get_full_modname, logging, status_iterator +from sphinx.util import get_full_modname, logging +from sphinx.util.display import status_iterator from sphinx.util.nodes import make_refnode logger = logging.getLogger(__name__) @@ -45,13 +38,13 @@ class viewcode_anchor(Element): """ -def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str]: +def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> str | None: try: return get_full_modname(modname, attribute) except AttributeError: # sphinx.ext.viewcode can't follow class instance attribute # then AttributeError logging output only verbose mode. - logger.verbose('Didn\'t find %s in %s', attribute, modname) + logger.verbose("Didn't find %s in %s", attribute, modname) return None except Exception as e: # sphinx.ext.viewcode follow python domain directives. @@ -66,12 +59,11 @@ def _get_full_modname(app: Sphinx, modname: str, attribute: str) -> Optional[str def is_supported_builder(builder: Builder) -> bool: if builder.format != 'html': return False - elif builder.name == 'singlehtml': + if builder.name == 'singlehtml': return False - elif builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: + if builder.name.startswith('epub') and not builder.config.viewcode_enable_epub: return False - else: - return True + return True def doctree_read(app: Sphinx, doctree: Node) -> None: @@ -121,7 +113,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None: for objnode in list(doctree.findall(addnodes.desc)): if objnode.get('domain') != 'py': continue - names: Set[str] = set() + names: set[str] = set() for signode in objnode: if not isinstance(signode, addnodes.desc_signature): continue @@ -216,19 +208,7 @@ class ViewcodeAnchorTransform(SphinxPostTransform): node.parent.remove(node) -def missing_reference(app: Sphinx, env: BuildEnvironment, node: Element, contnode: Node - ) -> Optional[Node]: - # resolve our "viewcode" reference nodes -- they need special treatment - if node['reftype'] == 'viewcode': - warnings.warn('viewcode extension is no longer use pending_xref node. ' - 'Please update your extension.', RemovedInSphinx50Warning) - return make_refnode(app.builder, node['refdoc'], node['reftarget'], - node['refid'], contnode) - - return None - - -def get_module_filename(app: Sphinx, modname: str) -> Optional[str]: +def get_module_filename(app: Sphinx, modname: str) -> str | None: """Get module filename for *modname*.""" source_info = app.emit_firstresult('viewcode-find-source', modname) if source_info: @@ -256,13 +236,13 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool: if path.getmtime(module_filename) <= path.getmtime(page_filename): # generation is not needed if the HTML page is newer than module file. return False - except IOError: + except OSError: pass return True -def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], None, None]: +def collect_pages(app: Sphinx) -> Generator[tuple[str, dict[str, Any], str], None, None]: env = app.builder.env if not hasattr(env, '_viewcode_modules'): return @@ -287,7 +267,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non # construct a page name for the highlighted source pagename = posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/')) # highlight the source using the builder's highlighter - if env.config.highlight_language in ('python3', 'default', 'none'): + if env.config.highlight_language in {'default', 'none'}: lexer = env.config.highlight_language else: lexer = 'python' @@ -360,10 +340,9 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non stack.pop() html.append('') stack.append(modname + '.') - html.append('
  • %s
  • \n' % ( - urito(posixpath.join(OUTPUT_DIRNAME, 'index'), - posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))), - modname)) + relative_uri = urito(posixpath.join(OUTPUT_DIRNAME, 'index'), + posixpath.join(OUTPUT_DIRNAME, modname.replace('.', '/'))) + html.append(f'
  • {modname}
  • \n') html.append('' * (len(stack) - 1)) context = { 'title': _('Overview: module code'), @@ -374,7 +353,7 @@ def collect_pages(app: Sphinx) -> Generator[Tuple[str, Dict[str, Any], str], Non yield (posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html') -def setup(app: Sphinx) -> Dict[str, Any]: +def setup(app: Sphinx) -> dict[str, Any]: app.add_config_value('viewcode_import', None, False) app.add_config_value('viewcode_enable_epub', False, False) app.add_config_value('viewcode_follow_imported_members', True, False) @@ -382,7 +361,6 @@ def setup(app: Sphinx) -> Dict[str, Any]: app.connect('env-merge-info', env_merge_info) app.connect('env-purge-doc', env_purge_doc) app.connect('html-collect-pages', collect_pages) - app.connect('missing-reference', missing_reference) # app.add_config_value('viewcode_include_modules', [], 'env') # app.add_config_value('viewcode_exclude_modules', [], 'env') app.add_event('viewcode-find-source') @@ -391,5 +369,5 @@ def setup(app: Sphinx) -> Dict[str, Any]: return { 'version': sphinx.__display_version__, 'env_version': 1, - 'parallel_read_safe': True + 'parallel_read_safe': True, } diff --git a/docs/mindspore/requirements.txt b/docs/mindspore/requirements.txt index 15a923bbf3f3adce685f577d3acaa91f8a841afb..6262c316906e36c210139b49b924e6aeeb787971 100644 --- a/docs/mindspore/requirements.txt +++ b/docs/mindspore/requirements.txt @@ -1,7 +1,7 @@ -sphinx == 4.4.0 -docutils == 0.17.1 -myst-parser == 0.18.1 -sphinx_rtd_theme == 1.0.0 +sphinx == 7.0.1 +docutils == 0.20.1 +myst-parser == 2.0.0 +sphinx_rtd_theme == 3.0.0 numpy nbsphinx == 0.8.11 IPython diff --git a/docs/mindspore/source_en/conf.py b/docs/mindspore/source_en/conf.py index d0187a1d0a36c638849ba517c820ab47de0fc1be..c96187587811ff6db6ae1cf33073818f5912b78a 100644 --- a/docs/mindspore/source_en/conf.py +++ b/docs/mindspore/source_en/conf.py @@ -27,14 +27,11 @@ from docutils.writers import _html_base with open(_html_base.__file__, "r", encoding="utf-8") as f: code_str = f.read() - old_str = ''' if self.is_compactable(node): - classes.append('simple')''' - new_str = ''' if classes == []: - classes.append('simple')''' + old_str = "classes = ['simple'] if self.is_compactable(node) else []" + new_str = "classes = ['simple']" code_str = code_str.replace(old_str, new_str) exec(code_str, _html_base.__dict__) - # Fix mathjax tags from sphinx.ext import mathjax as sphinx_mathjax @@ -263,8 +260,8 @@ try: except: pass -sys.path.append(os.path.abspath('../../../resource/search')) -import search_code +# sys.path.append(os.path.abspath('../../../resource/search')) +# import search_code # Copy source files of en python api from mindspore repository. copy_path = 'docs/api/api_python_en' @@ -324,7 +321,7 @@ def mint_interface_name(): try: primitive_list = ops_interface_name() except: - pass + primitive_list = [] mint_sum = mint_interface_name() diff --git a/docs/mindspore/source_zh_cn/conf.py b/docs/mindspore/source_zh_cn/conf.py index 94f7fc5cb25ac8cc40c6e0de8d3b35549335d57c..c86fd1a71dcb2b8ef1863df633a1e78bd2156ae0 100644 --- a/docs/mindspore/source_zh_cn/conf.py +++ b/docs/mindspore/source_zh_cn/conf.py @@ -26,10 +26,8 @@ from docutils.writers import _html_base with open(_html_base.__file__, "r", encoding="utf-8") as f: code_str = f.read() - old_str = ''' if self.is_compactable(node): - classes.append('simple')''' - new_str = ''' if classes == []: - classes.append('simple')''' + old_str = "classes = ['simple'] if self.is_compactable(node) else []" + new_str = "classes = ['simple']" code_str = code_str.replace(old_str, new_str) exec(code_str, _html_base.__dict__) @@ -229,7 +227,7 @@ with open(gfile_abs_path, "r", encoding="utf8") as f: sys.path.append(os.path.abspath('../../../resource/search')) -import search_code +# import search_code # Copy source files of chinese python api from mindspore repository. from sphinx.util import logging @@ -557,8 +555,11 @@ except Exception as e: print(e) primitive_list = ops_interface_name() +# primitive_list = [] mint_sum = mint_interface_name() +# mint_sum = [] + try: nn_interface_name() diff --git a/resource/_static/layout.html b/resource/_static/layout.html index a49fd97a4f6a7bd42b47c1cd35e6f3c7b0f8559b..c46b4faa6d98dccb787849d478fe1c2b07adbdea 100644 --- a/resource/_static/layout.html +++ b/resource/_static/layout.html @@ -7,16 +7,18 @@ {%- set titlesuffix = "" %} {%- endif %} {%- set lang_attr = 'en' if language == None else (language | replace('_', '-')) %} -{%- set sphinx_writer = 'writer-html5' if html5_doctype else 'writer-html4' -%} {# Build sphinx_version_info tuple from sphinx_version string in pure Jinja #} -{%- set (_ver_major, _ver_minor, _ver_bugfix) = sphinx_version.split('.') | map('int') -%} -{%- set sphinx_version_info = (_ver_major, _ver_minor, _ver_bugfix) -%} +{%- set (_ver_major, _ver_minor) = (sphinx_version.split('.') | list)[:2] | map('int') -%} +{%- set sphinx_version_info = (_ver_major, _ver_minor, -1) -%} - += (7, 2) %} data-content_root="{{ content_root }}"{% endif %}> + {%- if READTHEDOCS and not embedded %} + + {%- endif %} {{- metatags }} {%- block htmltitle %} @@ -25,34 +27,31 @@ {#- CSS #} - {%- if sphinx_version_info < (4, 0) -%} - - - {%- endif %} - - {%- for css in css_files %} - {%- if css|attr("rel") %} - - {%- elif 'css/theme.css' not in css and '/pygments.css' not in css %} - + {%- for css_file in css_files %} + {%- if css_file|attr("rel") %} + + {%- elif 'css/theme.css' not in css_file and '/pygments.css' not in css_file %} + {%- endif %} {%- endfor %} - {%- for cssfile in extra_css_files %} - + {# + "extra_css_files" is an undocumented Read the Docs theme specific option. + There is no need to check for ``|attr("filename")`` here because it's always a string. + Note that this option should be removed in favor of regular ``html_css_files``: + https://www.sphinx-doc.org/en/master/usage/configuration.html#confval-html_css_files + #} + {%- for css_file in extra_css_files %} + {%- endfor -%} - + {#- FAVICON #} - {%- if favicon %} - {%- if sphinx_version_info < (4, 0) -%} - - {%- else %} + {%- if favicon_url %} - {%- endif %} - {%- endif -%} + {%- endif %} {#- CANONICAL URL (deprecated) #} {%- if theme_canonical_url and not pageurl %} @@ -67,34 +66,13 @@ {#- JAVASCRIPTS #} {%- block scripts %} {%- if not embedded %} - {# XXX Sphinx 1.8.0 made this an external js-file, quick fix until we refactor the template to inherert more blocks directly from sphinx #} - {%- if sphinx_version_info >= (1, 8) -%} - {%- if sphinx_version_info < (4, 0) -%} - - {%- endif -%} - {%- for scriptfile in script_files %} - {%- if 'jquery.js' in scriptfile -%} - {{ js_tag(scriptfile) }} - {{ js_tag('_static/js/theme.js') }} - {%- else -%} - {{ js_tag(scriptfile) }} - {%- endif -%} - {%- endfor %} - {%- else %} - - {%- for scriptfile in script_files %} - - {%- endfor %} + {%- for scriptfile in script_files %} + {{ js_tag(scriptfile) }} + {%- endfor %} + + + {%- if READTHEDOCS or DEBUG %} + {%- endif %} {#- OPENSEARCH #} @@ -139,32 +117,22 @@