mirror of
https://github.com/sphinx-doc/sphinx.git
synced 2025-02-25 18:55:22 -06:00
Enable `strict-optional
` for several more modules (#11523)
This enables mypy's ``strict-optional`` mode for: * ``sphinx.builders.html`` * ``sphinx.builders.latex`` * ``sphinx.domains.python`` * ``sphinx.domains.std`` * ``sphinx.environment`` * ``sphinx.ext.apidoc`` * ``sphinx.ext.autodoc`` * ``sphinx.ext.autodoc.importer`` * ``sphinx.ext.autosummary`` * ``sphinx.ext.autosummary.generate`` * ``sphinx.ext.inheritance_diagram`` * ``sphinx.ext.intersphinx`` * ``sphinx.ext.imgmath`` * ``sphinx.ext.mathjax`` * ``sphinx.ext.napoleon.docstring`` * ``sphinx.registry`` * ``sphinx.writers.latex``
This commit is contained in:
@@ -310,25 +310,8 @@ disallow_any_generics = true
|
||||
|
||||
[[tool.mypy.overrides]]
|
||||
module = [
|
||||
"sphinx.builders.html",
|
||||
"sphinx.builders.latex",
|
||||
"sphinx.domains.c",
|
||||
"sphinx.domains.cpp",
|
||||
"sphinx.domains.python",
|
||||
"sphinx.domains.std",
|
||||
"sphinx.environment",
|
||||
"sphinx.ext.apidoc",
|
||||
"sphinx.ext.autodoc",
|
||||
"sphinx.ext.autodoc.importer",
|
||||
"sphinx.ext.autosummary",
|
||||
"sphinx.ext.autosummary.generate",
|
||||
"sphinx.ext.inheritance_diagram",
|
||||
"sphinx.ext.intersphinx",
|
||||
"sphinx.ext.imgmath",
|
||||
"sphinx.ext.mathjax",
|
||||
"sphinx.ext.napoleon.docstring",
|
||||
"sphinx.registry",
|
||||
"sphinx.writers.latex",
|
||||
]
|
||||
strict_optional = false
|
||||
|
||||
|
@@ -1240,8 +1240,8 @@ class Sphinx:
|
||||
def add_html_math_renderer(
|
||||
self,
|
||||
name: str,
|
||||
inline_renderers: tuple[Callable | None, Callable | None] | None = None,
|
||||
block_renderers: tuple[Callable | None, Callable | None] | None = None,
|
||||
inline_renderers: tuple[Callable, Callable | None] | None = None,
|
||||
block_renderers: tuple[Callable, Callable | None] | None = None,
|
||||
) -> None:
|
||||
"""Register a math renderer for HTML.
|
||||
|
||||
|
@@ -102,11 +102,11 @@ class Stylesheet(str):
|
||||
its filename (str).
|
||||
"""
|
||||
|
||||
attributes: dict[str, str] = None
|
||||
filename: str = None
|
||||
priority: int = None
|
||||
attributes: dict[str, str]
|
||||
filename: str
|
||||
priority: int
|
||||
|
||||
def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: Any,
|
||||
def __new__(cls, filename: str, *args: str, priority: int = 500, **attributes: str,
|
||||
) -> Stylesheet:
|
||||
self = str.__new__(cls, filename)
|
||||
self.filename = filename
|
||||
@@ -128,9 +128,9 @@ class JavaScript(str):
|
||||
its filename (str).
|
||||
"""
|
||||
|
||||
attributes: dict[str, str] = None
|
||||
filename: str = None
|
||||
priority: int = None
|
||||
attributes: dict[str, str]
|
||||
filename: str
|
||||
priority: int
|
||||
|
||||
def __new__(cls, filename: str, priority: int = 500, **attributes: str) -> JavaScript:
|
||||
self = str.__new__(cls, filename)
|
||||
@@ -221,10 +221,10 @@ class StandaloneHTMLBuilder(Builder):
|
||||
use_index = False
|
||||
download_support = True # enable download role
|
||||
|
||||
imgpath: str = None
|
||||
imgpath: str = ''
|
||||
domain_indices: list[DOMAIN_INDEX_TYPE] = []
|
||||
|
||||
def __init__(self, app: Sphinx, env: BuildEnvironment | None = None) -> None:
|
||||
def __init__(self, app: Sphinx, env: BuildEnvironment) -> None:
|
||||
super().__init__(app, env)
|
||||
|
||||
# CSS files
|
||||
@@ -256,7 +256,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
# section numbers for headings in the currently visited document
|
||||
self.secnumbers: dict[str, tuple[int, ...]] = {}
|
||||
# currently written docname
|
||||
self.current_docname: str = None
|
||||
self.current_docname: str = ''
|
||||
|
||||
self.init_templates()
|
||||
self.init_highlighter()
|
||||
@@ -290,7 +290,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
for jsfile in candidates:
|
||||
if path.isfile(jsfile):
|
||||
return jsfile
|
||||
return None
|
||||
return ''
|
||||
|
||||
def _get_style_filenames(self) -> Iterator[str]:
|
||||
if isinstance(self.config.html_style, str):
|
||||
@@ -329,6 +329,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
else:
|
||||
dark_style = None
|
||||
|
||||
self.dark_highlighter: PygmentsBridge | None
|
||||
if dark_style is not None:
|
||||
self.dark_highlighter = PygmentsBridge('html', dark_style)
|
||||
self.app.add_css_file('pygments_dark.css',
|
||||
@@ -365,11 +366,11 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self.add_js_file('sphinx_highlight.js', priority=200)
|
||||
|
||||
for filename, attrs in self.app.registry.js_files:
|
||||
self.add_js_file(filename, **attrs)
|
||||
self.add_js_file(filename or '', **attrs)
|
||||
|
||||
for filename, attrs in self.get_builder_config('js_files', 'html'):
|
||||
attrs.setdefault('priority', 800) # User's JSs are loaded after extensions'
|
||||
self.add_js_file(filename, **attrs)
|
||||
self.add_js_file(filename or '', **attrs)
|
||||
|
||||
if self._get_translations_js():
|
||||
self.add_js_file('translations.js')
|
||||
@@ -381,7 +382,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
self.script_files.append(JavaScript(filename, **kwargs))
|
||||
|
||||
@property
|
||||
def math_renderer_name(self) -> str:
|
||||
def math_renderer_name(self) -> str | None:
|
||||
name = self.get_builder_config('math_renderer', 'html')
|
||||
if name is not None:
|
||||
# use given name
|
||||
@@ -459,7 +460,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
doc.append(node)
|
||||
self._publisher.set_source(doc)
|
||||
self._publisher.publish()
|
||||
return self._publisher.writer.parts
|
||||
return self._publisher.writer.parts # type: ignore[union-attr]
|
||||
|
||||
def prepare_writing(self, docnames: set[str]) -> None:
|
||||
# create the search indexer
|
||||
@@ -502,6 +503,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
|
||||
# format the "last updated on" string, only once is enough since it
|
||||
# typically doesn't include the time of day
|
||||
self.last_updated: str | None
|
||||
lufmt = self.config.html_last_updated_fmt
|
||||
if lufmt is not None:
|
||||
self.last_updated = format_date(lufmt or _('%b %d, %Y'),
|
||||
@@ -824,9 +826,9 @@ class StandaloneHTMLBuilder(Builder):
|
||||
for jsfile in self.indexer.get_js_stemmer_rawcodes():
|
||||
copyfile(jsfile, path.join(self.outdir, '_static', path.basename(jsfile)))
|
||||
else:
|
||||
jsfile = self.indexer.get_js_stemmer_rawcode()
|
||||
if jsfile:
|
||||
copyfile(jsfile, path.join(self.outdir, '_static', '_stemmer.js'))
|
||||
if js_stemmer_rawcode := self.indexer.get_js_stemmer_rawcode():
|
||||
copyfile(js_stemmer_rawcode,
|
||||
path.join(self.outdir, '_static', '_stemmer.js'))
|
||||
|
||||
def copy_theme_static_files(self, context: dict[str, Any]) -> None:
|
||||
def onerror(filename: str, error: Exception) -> None:
|
||||
@@ -934,6 +936,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
reference.append(node)
|
||||
|
||||
def load_indexer(self, docnames: Iterable[str]) -> None:
|
||||
assert self.indexer is not None
|
||||
keep = set(self.env.all_docs) - set(docnames)
|
||||
try:
|
||||
searchindexfn = path.join(self.outdir, self.searchindex_filename)
|
||||
@@ -954,7 +957,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
def index_page(self, pagename: str, doctree: nodes.document, title: str) -> None:
|
||||
# only index pages with title
|
||||
if self.indexer is not None and title:
|
||||
filename = self.env.doc2path(pagename, base=None)
|
||||
filename = self.env.doc2path(pagename, base=False)
|
||||
metadata = self.env.metadata.get(pagename, {})
|
||||
if 'nosearch' in metadata:
|
||||
self.indexer.feed(pagename, filename, '', new_document(''))
|
||||
@@ -1131,8 +1134,7 @@ class StandaloneHTMLBuilder(Builder):
|
||||
pass
|
||||
|
||||
def handle_finish(self) -> None:
|
||||
if self.indexer:
|
||||
self.finish_tasks.add_task(self.dump_search_index)
|
||||
self.finish_tasks.add_task(self.dump_search_index)
|
||||
self.finish_tasks.add_task(self.dump_inventory)
|
||||
|
||||
@progress_message(__('dumping object inventory'))
|
||||
@@ -1140,6 +1142,9 @@ class StandaloneHTMLBuilder(Builder):
|
||||
InventoryFile.dump(path.join(self.outdir, INVENTORY_FILENAME), self.env, self)
|
||||
|
||||
def dump_search_index(self) -> None:
|
||||
if self.indexer is None:
|
||||
return
|
||||
|
||||
with progress_message(__('dumping search index in %s') % self.indexer.label()):
|
||||
self.indexer.prune(self.env.all_docs)
|
||||
searchindexfn = path.join(self.outdir, self.searchindex_filename)
|
||||
@@ -1194,7 +1199,7 @@ def setup_css_tag_helper(app: Sphinx, pagename: str, templatename: str,
|
||||
|
||||
.. note:: This set up function is added to keep compatibility with webhelper.
|
||||
"""
|
||||
pathto = context.get('pathto')
|
||||
pathto = context['pathto']
|
||||
|
||||
def css_tag(css: Stylesheet) -> str:
|
||||
attrs = []
|
||||
@@ -1217,7 +1222,7 @@ def setup_js_tag_helper(app: Sphinx, pagename: str, templatename: str,
|
||||
|
||||
.. note:: This set up function is added to keep compatibility with webhelper.
|
||||
"""
|
||||
pathto = context.get('pathto')
|
||||
pathto = context['pathto']
|
||||
|
||||
def js_tag(js: JavaScript) -> str:
|
||||
attrs = []
|
||||
@@ -1267,7 +1272,7 @@ def _file_checksum(outdir: str, filename: str) -> str:
|
||||
def setup_resource_paths(app: Sphinx, pagename: str, templatename: str,
|
||||
context: dict, doctree: Node) -> None:
|
||||
"""Set up relative resource paths."""
|
||||
pathto = context.get('pathto')
|
||||
pathto = context['pathto']
|
||||
|
||||
# favicon_url
|
||||
favicon_url = context.get('favicon_url')
|
||||
@@ -1387,7 +1392,7 @@ def setup(app: Sphinx) -> dict[str, Any]:
|
||||
app.add_config_value('html_secnumber_suffix', '. ', 'html')
|
||||
app.add_config_value('html_search_language', None, 'html', [str])
|
||||
app.add_config_value('html_search_options', {}, 'html')
|
||||
app.add_config_value('html_search_scorer', '', None)
|
||||
app.add_config_value('html_search_scorer', '', '')
|
||||
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', # RemovedInSphinx70Warning # noqa: E501
|
||||
|
@@ -119,7 +119,7 @@ class LaTeXBuilder(Builder):
|
||||
default_translator_class = LaTeXTranslator
|
||||
|
||||
def init(self) -> None:
|
||||
self.babel: ExtBabel = None
|
||||
self.babel: ExtBabel
|
||||
self.context: dict[str, Any] = {}
|
||||
self.docnames: Iterable[str] = {}
|
||||
self.document_data: list[tuple[str, str, str, str, str, bool]] = []
|
||||
@@ -317,7 +317,7 @@ class LaTeXBuilder(Builder):
|
||||
|
||||
def get_contentsname(self, indexfile: str) -> str:
|
||||
tree = self.env.get_doctree(indexfile)
|
||||
contentsname = None
|
||||
contentsname = ''
|
||||
for toctree in tree.findall(addnodes.toctree):
|
||||
if 'caption' in toctree:
|
||||
contentsname = toctree['caption']
|
||||
|
@@ -60,7 +60,7 @@ class ENUM:
|
||||
Example:
|
||||
app.add_config_value('latex_show_urls', 'no', None, ENUM('no', 'footnote', 'inline'))
|
||||
"""
|
||||
def __init__(self, *candidates: str | bool) -> None:
|
||||
def __init__(self, *candidates: str | bool | None) -> None:
|
||||
self.candidates = candidates
|
||||
|
||||
def match(self, value: str | list | tuple) -> bool:
|
||||
|
@@ -108,7 +108,7 @@ def parse_reftarget(reftarget: str, suppress_prefix: bool = False,
|
||||
return reftype, reftarget, title, refspecific
|
||||
|
||||
|
||||
def type_to_xref(target: str, env: BuildEnvironment | None = None,
|
||||
def type_to_xref(target: str, env: BuildEnvironment, *,
|
||||
suppress_prefix: bool = False) -> addnodes.pending_xref:
|
||||
"""Convert a type string to a cross reference node."""
|
||||
if env:
|
||||
@@ -134,7 +134,7 @@ def type_to_xref(target: str, env: BuildEnvironment | None = None,
|
||||
refspecific=refspecific, **kwargs)
|
||||
|
||||
|
||||
def _parse_annotation(annotation: str, env: BuildEnvironment | None) -> list[Node]:
|
||||
def _parse_annotation(annotation: str, env: BuildEnvironment) -> list[Node]:
|
||||
"""Parse type annotation."""
|
||||
short_literals = env.config.python_display_short_literal_types
|
||||
|
||||
@@ -269,25 +269,25 @@ class _TypeParameterListParser(TokenProcessor):
|
||||
|
||||
def fetch_type_param_spec(self) -> list[Token]:
|
||||
tokens = []
|
||||
while self.fetch_token():
|
||||
tokens.append(self.current)
|
||||
while current := self.fetch_token():
|
||||
tokens.append(current)
|
||||
for ldelim, rdelim in ('(', ')'), ('{', '}'), ('[', ']'):
|
||||
if self.current == [token.OP, ldelim]:
|
||||
if current == [token.OP, ldelim]:
|
||||
tokens += self.fetch_until([token.OP, rdelim])
|
||||
break
|
||||
else:
|
||||
if self.current == token.INDENT:
|
||||
if current == token.INDENT:
|
||||
tokens += self.fetch_until(token.DEDENT)
|
||||
elif self.current.match(
|
||||
elif current.match(
|
||||
[token.OP, ':'], [token.OP, '='], [token.OP, ',']):
|
||||
tokens.pop()
|
||||
break
|
||||
return tokens
|
||||
|
||||
def parse(self) -> None:
|
||||
while self.fetch_token():
|
||||
if self.current == token.NAME:
|
||||
tp_name = self.current.value.strip()
|
||||
while current := self.fetch_token():
|
||||
if current == token.NAME:
|
||||
tp_name = current.value.strip()
|
||||
if self.previous and self.previous.match([token.OP, '*'], [token.OP, '**']):
|
||||
if self.previous == [token.OP, '*']:
|
||||
tp_kind = Parameter.VAR_POSITIONAL
|
||||
@@ -299,13 +299,13 @@ class _TypeParameterListParser(TokenProcessor):
|
||||
tp_ann: Any = Parameter.empty
|
||||
tp_default: Any = Parameter.empty
|
||||
|
||||
self.fetch_token()
|
||||
if self.current and self.current.match([token.OP, ':'], [token.OP, '=']):
|
||||
if self.current == [token.OP, ':']:
|
||||
current = self.fetch_token()
|
||||
if current and current.match([token.OP, ':'], [token.OP, '=']):
|
||||
if current == [token.OP, ':']:
|
||||
tokens = self.fetch_type_param_spec()
|
||||
tp_ann = self._build_identifier(tokens)
|
||||
|
||||
if self.current == [token.OP, '=']:
|
||||
if self.current and self.current == [token.OP, '=']:
|
||||
tokens = self.fetch_type_param_spec()
|
||||
tp_default = self._build_identifier(tokens)
|
||||
|
||||
@@ -395,7 +395,7 @@ class _TypeParameterListParser(TokenProcessor):
|
||||
|
||||
|
||||
def _parse_type_list(
|
||||
tp_list: str, env: BuildEnvironment | None = None,
|
||||
tp_list: str, env: BuildEnvironment,
|
||||
multi_line_parameter_list: bool = False,
|
||||
) -> addnodes.desc_type_parameter_list:
|
||||
"""Parse a list of type parameters according to PEP 695."""
|
||||
@@ -456,7 +456,7 @@ def _parse_type_list(
|
||||
|
||||
|
||||
def _parse_arglist(
|
||||
arglist: str, env: BuildEnvironment | None = None, multi_line_parameter_list: bool = False,
|
||||
arglist: str, env: BuildEnvironment, multi_line_parameter_list: bool = False,
|
||||
) -> addnodes.desc_parameterlist:
|
||||
"""Parse a list of arguments using AST parser"""
|
||||
params = addnodes.desc_parameterlist(arglist)
|
||||
@@ -580,6 +580,7 @@ class PyXrefMixin:
|
||||
innernode, contnode,
|
||||
env, inliner=None, location=None)
|
||||
if isinstance(result, pending_xref):
|
||||
assert env is not None
|
||||
result['refspecific'] = True
|
||||
result['py:module'] = env.ref_context.get('py:module')
|
||||
result['py:class'] = env.ref_context.get('py:class')
|
||||
@@ -968,9 +969,9 @@ class PyFunction(PyObject):
|
||||
text = f'built-in function; {name}()'
|
||||
self.indexnode['entries'].append(('pair', text, node_id, '', None))
|
||||
|
||||
def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str | None:
|
||||
def get_index_text(self, modname: str, name_cls: tuple[str, str]) -> str:
|
||||
# add index in own add_target_and_index() instead.
|
||||
return None
|
||||
return ''
|
||||
|
||||
|
||||
class PyDecoratorFunction(PyFunction):
|
||||
@@ -1556,7 +1557,7 @@ class PythonDomain(Domain):
|
||||
newname = None
|
||||
if searchmode == 1:
|
||||
if type is None:
|
||||
objtypes = list(self.object_types)
|
||||
objtypes: list[str] | None = list(self.object_types)
|
||||
else:
|
||||
objtypes = self.objtypes_for_role(type)
|
||||
if objtypes is not None:
|
||||
@@ -1671,9 +1672,9 @@ class PythonDomain(Domain):
|
||||
# if not found, use contnode
|
||||
children = [contnode]
|
||||
|
||||
results.append(('py:' + self.role_for_objtype(obj[2]),
|
||||
make_refnode(builder, fromdocname, obj[0], obj[1],
|
||||
children, name)))
|
||||
role = 'py:' + self.role_for_objtype(obj[2]) # type: ignore[operator]
|
||||
results.append((role, make_refnode(builder, fromdocname, obj[0], obj[1],
|
||||
children, name)))
|
||||
return results
|
||||
|
||||
def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str,
|
||||
|
@@ -216,7 +216,7 @@ class Cmdoption(ObjectDescription[str]):
|
||||
|
||||
self.state.document.note_explicit_target(signode)
|
||||
|
||||
domain = cast(StandardDomain, self.env.get_domain('std'))
|
||||
domain = self.env.domains['std']
|
||||
for optname in signode.get('allnames', []):
|
||||
domain.add_program_option(currprogram, optname,
|
||||
self.env.docname, signode['ids'][0])
|
||||
@@ -383,10 +383,12 @@ class Glossary(SphinxDirective):
|
||||
parts = split_term_classifiers(line)
|
||||
# parse the term with inline markup
|
||||
# classifiers (parts[1:]) will not be shown on doctree
|
||||
textnodes, sysmsg = self.state.inline_text(parts[0], lineno)
|
||||
textnodes, sysmsg = self.state.inline_text(parts[0], # type: ignore[arg-type]
|
||||
lineno)
|
||||
|
||||
# use first classifier as a index key
|
||||
term = make_glossary_term(self.env, textnodes, parts[1], source, lineno,
|
||||
term = make_glossary_term(self.env, textnodes,
|
||||
parts[1], source, lineno, # type: ignore[arg-type]
|
||||
node_id=None, document=self.state.document)
|
||||
term.rawsource = line
|
||||
system_messages.extend(sysmsg)
|
||||
@@ -647,7 +649,7 @@ class StandardDomain(Domain):
|
||||
self._terms[term.lower()] = (self.env.docname, labelid)
|
||||
|
||||
@property
|
||||
def progoptions(self) -> dict[tuple[str, str], tuple[str, str]]:
|
||||
def progoptions(self) -> dict[tuple[str | None, str], tuple[str, str]]:
|
||||
return self.data.setdefault('progoptions', {}) # (program, name) -> docname, labelid
|
||||
|
||||
@property
|
||||
@@ -706,7 +708,7 @@ class StandardDomain(Domain):
|
||||
node = document.ids[labelid]
|
||||
if isinstance(node, nodes.target) and 'refid' in node:
|
||||
# indirect hyperlink targets
|
||||
node = document.ids.get(node['refid'])
|
||||
node = document.ids.get(node['refid']) # type: ignore[assignment]
|
||||
labelid = node['names'][0]
|
||||
if (node.tagname == 'footnote' or
|
||||
'refuri' in node or
|
||||
@@ -721,11 +723,11 @@ class StandardDomain(Domain):
|
||||
self.anonlabels[name] = docname, labelid
|
||||
if node.tagname == 'section':
|
||||
title = cast(nodes.title, node[0])
|
||||
sectname: str | None = clean_astext(title)
|
||||
sectname = clean_astext(title)
|
||||
elif node.tagname == 'rubric':
|
||||
sectname = clean_astext(node)
|
||||
elif self.is_enumerable_node(node):
|
||||
sectname = self.get_numfig_title(node)
|
||||
sectname = self.get_numfig_title(node) or ''
|
||||
if not sectname:
|
||||
continue
|
||||
else:
|
||||
@@ -740,13 +742,14 @@ class StandardDomain(Domain):
|
||||
else:
|
||||
toctree = next(node.findall(addnodes.toctree), None)
|
||||
if toctree and toctree.get('caption'):
|
||||
sectname = toctree.get('caption')
|
||||
sectname = toctree['caption']
|
||||
else:
|
||||
# anonymous-only labels
|
||||
continue
|
||||
self.labels[name] = docname, labelid, sectname
|
||||
|
||||
def add_program_option(self, program: str, name: str, docname: str, labelid: str) -> None:
|
||||
def add_program_option(self, program: str | None, name: str,
|
||||
docname: str, labelid: str) -> None:
|
||||
# prefer first command option entry
|
||||
if (program, name) not in self.progoptions:
|
||||
self.progoptions[program, name] = (docname, labelid)
|
||||
@@ -827,6 +830,7 @@ class StandardDomain(Domain):
|
||||
return None
|
||||
|
||||
target_node = env.get_doctree(docname).ids.get(labelid)
|
||||
assert target_node is not None
|
||||
figtype = self.get_enumerable_node_type(target_node)
|
||||
if figtype is None:
|
||||
return None
|
||||
@@ -985,9 +989,9 @@ class StandardDomain(Domain):
|
||||
key = (objtype, ltarget)
|
||||
if key in self.objects:
|
||||
docname, labelid = self.objects[key]
|
||||
results.append(('std:' + self.role_for_objtype(objtype),
|
||||
make_refnode(builder, fromdocname, docname,
|
||||
labelid, contnode)))
|
||||
role = 'std:' + self.role_for_objtype(objtype) # type: ignore[operator]
|
||||
results.append((role, make_refnode(builder, fromdocname, docname,
|
||||
labelid, contnode)))
|
||||
return results
|
||||
|
||||
def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:
|
||||
|
@@ -60,6 +60,7 @@ default_settings: dict[str, Any] = {
|
||||
ENV_VERSION = 58
|
||||
|
||||
# config status
|
||||
CONFIG_UNSET = -1
|
||||
CONFIG_OK = 1
|
||||
CONFIG_NEW = 2
|
||||
CONFIG_CHANGED = 3
|
||||
@@ -145,25 +146,25 @@ class BuildEnvironment:
|
||||
# --------- ENVIRONMENT INITIALIZATION -------------------------------------
|
||||
|
||||
def __init__(self, app: Sphinx):
|
||||
self.app: Sphinx = None
|
||||
self.doctreedir: str = None
|
||||
self.srcdir: str = None
|
||||
self.config: Config = None
|
||||
self.config_status: int = None
|
||||
self.config_status_extra: str = None
|
||||
self.events: EventManager = None
|
||||
self.project: Project = None
|
||||
self.version: dict[str, str] = None
|
||||
self.app: Sphinx = app
|
||||
self.doctreedir: str = app.doctreedir
|
||||
self.srcdir: str = app.srcdir
|
||||
self.config: Config = None # type: ignore[assignment]
|
||||
self.config_status: int = CONFIG_UNSET
|
||||
self.config_status_extra: str = ''
|
||||
self.events: EventManager = app.events
|
||||
self.project: Project = app.project
|
||||
self.version: dict[str, str] = app.registry.get_envversion(app)
|
||||
|
||||
# the method of doctree versioning; see set_versioning_method
|
||||
self.versioning_condition: bool | Callable = None
|
||||
self.versioning_compare: bool = None
|
||||
self.versioning_condition: bool | Callable | None = None
|
||||
self.versioning_compare: bool | None = None
|
||||
|
||||
# all the registered domains, set by the application
|
||||
self.domains = _DomainsType()
|
||||
|
||||
# the docutils settings for building
|
||||
self.settings = default_settings.copy()
|
||||
self.settings: dict[str, Any] = default_settings.copy()
|
||||
self.settings['env'] = self
|
||||
|
||||
# All "docnames" here are /-separated and relative and exclude
|
||||
|
@@ -57,7 +57,7 @@ def is_initpy(filename: str) -> bool:
|
||||
)
|
||||
|
||||
|
||||
def module_join(*modnames: str) -> str:
|
||||
def module_join(*modnames: str | None) -> str:
|
||||
"""Join module names with dots."""
|
||||
return '.'.join(filter(None, modnames))
|
||||
|
||||
@@ -91,7 +91,7 @@ def write_file(name: str, text: str, opts: Any) -> None:
|
||||
f.write(text)
|
||||
|
||||
|
||||
def create_module_file(package: str, basename: str, opts: Any,
|
||||
def create_module_file(package: str | None, basename: str, opts: Any,
|
||||
user_template_dir: str | None = None) -> None:
|
||||
"""Build the text of the file and write the file."""
|
||||
options = copy(OPTIONS)
|
||||
@@ -105,11 +105,16 @@ def create_module_file(package: str, basename: str, opts: Any,
|
||||
'qualname': qualname,
|
||||
'automodule_options': options,
|
||||
}
|
||||
text = ReSTRenderer([user_template_dir, template_dir]).render('module.rst_t', context)
|
||||
if user_template_dir is not None:
|
||||
template_path = [user_template_dir, template_dir]
|
||||
else:
|
||||
template_path = [template_dir]
|
||||
text = ReSTRenderer(template_path).render('module.rst_t', context)
|
||||
write_file(qualname, text, opts)
|
||||
|
||||
|
||||
def create_package_file(root: str, master_package: str, subroot: str, py_files: list[str],
|
||||
def create_package_file(root: str, master_package: str | None, subroot: str,
|
||||
py_files: list[str],
|
||||
opts: Any, subs: list[str], is_namespace: bool,
|
||||
excludes: list[str] = [], user_template_dir: str | None = None,
|
||||
) -> None:
|
||||
@@ -141,7 +146,11 @@ def create_package_file(root: str, master_package: str, subroot: str, py_files:
|
||||
'show_headings': not opts.noheadings,
|
||||
'maxdepth': opts.maxdepth,
|
||||
}
|
||||
text = ReSTRenderer([user_template_dir, template_dir]).render('package.rst_t', context)
|
||||
if user_template_dir is not None:
|
||||
template_path = [user_template_dir, template_dir]
|
||||
else:
|
||||
template_path = [template_dir]
|
||||
text = ReSTRenderer(template_path).render('package.rst_t', context)
|
||||
write_file(pkgname, text, opts)
|
||||
|
||||
if submodules and opts.separatemodules:
|
||||
@@ -166,7 +175,11 @@ def create_modules_toc_file(modules: list[str], opts: Any, name: str = 'modules'
|
||||
'maxdepth': opts.maxdepth,
|
||||
'docnames': modules,
|
||||
}
|
||||
text = ReSTRenderer([user_template_dir, template_dir]).render('toc.rst_t', context)
|
||||
if user_template_dir is not None:
|
||||
template_path = [user_template_dir, template_dir]
|
||||
else:
|
||||
template_path = [template_dir]
|
||||
text = ReSTRenderer(template_path).render('toc.rst_t', context)
|
||||
write_file(name, text, opts)
|
||||
|
||||
|
||||
|
@@ -330,21 +330,21 @@ class Documenter:
|
||||
self.indent = indent
|
||||
# the module and object path within the module, and the fully
|
||||
# qualified name (all set after resolve_name succeeds)
|
||||
self.modname: str = None
|
||||
self.module: ModuleType = None
|
||||
self.objpath: list[str] = None
|
||||
self.fullname: str = None
|
||||
self.modname: str = ''
|
||||
self.module: ModuleType | None = None
|
||||
self.objpath: list[str] = []
|
||||
self.fullname = ''
|
||||
# extra signature items (arguments and return annotation,
|
||||
# also set after resolve_name succeeds)
|
||||
self.args: str = None
|
||||
self.retann: str = None
|
||||
self.args: str | None = None
|
||||
self.retann = ''
|
||||
# the object to document (set after import_object succeeds)
|
||||
self.object: Any = None
|
||||
self.object_name: str = None
|
||||
self.object_name = ''
|
||||
# the parent/owner of the object to document
|
||||
self.parent: Any = None
|
||||
# the module analyzer to get at attribute docs, or None
|
||||
self.analyzer: ModuleAnalyzer = None
|
||||
self.analyzer: ModuleAnalyzer | None = None
|
||||
|
||||
@property
|
||||
def documenters(self) -> dict[str, type[Documenter]]:
|
||||
@@ -358,8 +358,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 | None, parents: Any, path: str, base: str,
|
||||
) -> tuple[str | None, list[str]]:
|
||||
"""Resolve the module and name of the object to document given by the
|
||||
arguments and the current module/class.
|
||||
|
||||
@@ -380,6 +380,7 @@ class Documenter:
|
||||
# an autogenerated one
|
||||
try:
|
||||
matched = py_ext_sig_re.match(self.name)
|
||||
assert matched is not None
|
||||
explicit_modname, path, base, tp_list, args, retann = matched.groups()
|
||||
except AttributeError:
|
||||
logger.warning(__('invalid signature for auto%s (%r)') % (self.objtype, self.name),
|
||||
@@ -395,11 +396,12 @@ class Documenter:
|
||||
parents = []
|
||||
|
||||
with mock(self.config.autodoc_mock_imports):
|
||||
self.modname, self.objpath = self.resolve_name(modname, parents, path, base)
|
||||
modname, self.objpath = self.resolve_name(modname, parents, path, base)
|
||||
|
||||
if not self.modname:
|
||||
if not modname:
|
||||
return False
|
||||
|
||||
self.modname = modname
|
||||
self.args = args
|
||||
self.retann = retann
|
||||
self.fullname = ((self.modname or '') +
|
||||
@@ -449,12 +451,12 @@ class Documenter:
|
||||
return False
|
||||
return True
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str | None:
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
"""Format the argument signature of *self.object*.
|
||||
|
||||
Should return None if the object does not have a signature.
|
||||
"""
|
||||
pass
|
||||
return ''
|
||||
|
||||
def format_name(self) -> str:
|
||||
"""Format the name of *self.object*.
|
||||
@@ -467,7 +469,7 @@ class Documenter:
|
||||
# directives of course)
|
||||
return '.'.join(self.objpath) or self.modname
|
||||
|
||||
def _call_format_args(self, **kwargs: Any) -> str | None:
|
||||
def _call_format_args(self, **kwargs: Any) -> str:
|
||||
if kwargs:
|
||||
try:
|
||||
return self.format_args(**kwargs)
|
||||
@@ -591,9 +593,9 @@ class Documenter:
|
||||
docstring = False
|
||||
# make a copy of docstring for attributes to avoid cache
|
||||
# the change of autodoc-process-docstring event.
|
||||
docstrings = [list(attr_docs[key])]
|
||||
attribute_docstrings = [list(attr_docs[key])]
|
||||
|
||||
for i, line in enumerate(self.process_doc(docstrings)):
|
||||
for i, line in enumerate(self.process_doc(attribute_docstrings)):
|
||||
self.add_line(line, sourcename, i)
|
||||
|
||||
# add content from docstrings
|
||||
@@ -804,7 +806,7 @@ class Documenter:
|
||||
classes.sort(key=lambda cls: cls.priority)
|
||||
# give explicitly separated module name, so that members
|
||||
# of inner classes can be documented
|
||||
full_mname = self.modname + '::' + '.'.join(self.objpath + [mname])
|
||||
full_mname = f'{self.modname}::' + '.'.join((*self.objpath, mname))
|
||||
documenter = classes[-1](self.directive, full_mname, self.indent)
|
||||
memberdocumenters.append((documenter, isattr))
|
||||
|
||||
@@ -889,8 +891,8 @@ class Documenter:
|
||||
# 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.record_dependencies.add(self.module.__file__)
|
||||
if module___file__ := getattr(self.module, '__file__', ''):
|
||||
self.directive.record_dependencies.add(module___file__)
|
||||
else:
|
||||
self.directive.record_dependencies.add(self.analyzer.srcname)
|
||||
|
||||
@@ -980,8 +982,8 @@ class ModuleDocumenter(Documenter):
|
||||
# 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 | None, parents: Any, path: str, base: str,
|
||||
) -> tuple[str | None, list[str]]:
|
||||
if modname is not None:
|
||||
logger.warning(__('"::" in automodule name doesn\'t make sense'),
|
||||
type='autodoc')
|
||||
@@ -1078,16 +1080,21 @@ class ModuleDocumenter(Documenter):
|
||||
def sort_members(self, documenters: list[tuple[Documenter, bool]],
|
||||
order: str) -> list[tuple[Documenter, bool]]:
|
||||
if order == 'bysource' and self.__all__:
|
||||
assert self.__all__ is not None
|
||||
module_all = self.__all__
|
||||
module_all_set = set(module_all)
|
||||
module_all_len = len(module_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:
|
||||
name = entry[0].name.split('::')[1]
|
||||
if self.__all__ and name in self.__all__:
|
||||
return self.__all__.index(name)
|
||||
if name in module_all_set:
|
||||
return module_all.index(name)
|
||||
else:
|
||||
return len(self.__all__)
|
||||
return module_all_len
|
||||
documenters.sort(key=keyfunc)
|
||||
|
||||
return documenters
|
||||
@@ -1100,19 +1107,21 @@ 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]]:
|
||||
if modname is None:
|
||||
if path:
|
||||
modname = path.rstrip('.')
|
||||
else:
|
||||
# if documenting a toplevel object without explicit module,
|
||||
# it can be contained in another auto directive ...
|
||||
modname = self.env.temp_data.get('autodoc:module')
|
||||
# ... or in the scope of a module directive
|
||||
if not modname:
|
||||
modname = self.env.ref_context.get('py:module')
|
||||
# ... else, it stays None, which means invalid
|
||||
def resolve_name(self, modname: str | None, parents: Any, path: str, base: str,
|
||||
) -> tuple[str | None, list[str]]:
|
||||
if modname is not None:
|
||||
return modname, parents + [base]
|
||||
if path:
|
||||
modname = path.rstrip('.')
|
||||
return modname, parents + [base]
|
||||
|
||||
# if documenting a toplevel object without explicit module,
|
||||
# it can be contained in another auto directive ...
|
||||
modname = self.env.temp_data.get('autodoc:module')
|
||||
# ... or in the scope of a module directive
|
||||
if not modname:
|
||||
modname = self.env.ref_context.get('py:module')
|
||||
# ... else, it stays None, which means invalid
|
||||
return modname, parents + [base]
|
||||
|
||||
|
||||
@@ -1121,31 +1130,33 @@ 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]]:
|
||||
if modname is None:
|
||||
if path:
|
||||
mod_cls = path.rstrip('.')
|
||||
else:
|
||||
mod_cls = None
|
||||
# if documenting a class-level object without path,
|
||||
# there must be a current class, either from a parent
|
||||
# auto directive ...
|
||||
mod_cls = self.env.temp_data.get('autodoc:class')
|
||||
# ... or from a class directive
|
||||
if mod_cls is None:
|
||||
mod_cls = self.env.ref_context.get('py:class')
|
||||
def resolve_name(self, modname: str | None, parents: Any, path: str, base: str,
|
||||
) -> tuple[str | None, list[str]]:
|
||||
if modname is not None:
|
||||
return modname, parents + [base]
|
||||
|
||||
if path:
|
||||
mod_cls = path.rstrip('.')
|
||||
else:
|
||||
# if documenting a class-level object without path,
|
||||
# there must be a current class, either from a parent
|
||||
# auto directive ...
|
||||
mod_cls_ = self.env.temp_data.get('autodoc:class')
|
||||
# ... or from a class directive
|
||||
if mod_cls_ is None:
|
||||
mod_cls_ = self.env.ref_context.get('py:class')
|
||||
# ... if still None, there's no way to know
|
||||
if mod_cls is None:
|
||||
if mod_cls_ is None:
|
||||
return None, []
|
||||
modname, sep, cls = mod_cls.rpartition('.')
|
||||
parents = [cls]
|
||||
# if the module name is still missing, get it like above
|
||||
if not modname:
|
||||
modname = self.env.temp_data.get('autodoc:module')
|
||||
if not modname:
|
||||
modname = self.env.ref_context.get('py:module')
|
||||
# ... else, it stays None, which means invalid
|
||||
mod_cls = mod_cls_
|
||||
modname, sep, cls = mod_cls.rpartition('.')
|
||||
parents = [cls]
|
||||
# if the module name is still missing, get it like above
|
||||
if not modname:
|
||||
modname = self.env.temp_data.get('autodoc:module')
|
||||
if not modname:
|
||||
modname = self.env.ref_context.get('py:module')
|
||||
# ... else, it stays None, which means invalid
|
||||
return modname, parents + [base]
|
||||
|
||||
|
||||
@@ -1154,10 +1165,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 = None
|
||||
_signatures: list[str] = []
|
||||
|
||||
def _find_signature(self) -> tuple[str | None, str | None]:
|
||||
def _find_signature(self) -> tuple[str | None, str | None] | None:
|
||||
# candidates of the object name
|
||||
valid_names = [self.objpath[-1]] # type: ignore
|
||||
if isinstance(self, ClassDocumenter):
|
||||
@@ -1202,13 +1213,13 @@ class DocstringSignatureMixin:
|
||||
# subsequent signatures
|
||||
self._signatures.append(f"({args}) -> {retann}")
|
||||
|
||||
if result:
|
||||
if result is not None:
|
||||
# finish the loop when signature found
|
||||
break
|
||||
|
||||
return result
|
||||
|
||||
def get_doc(self) -> list[list[str]]:
|
||||
def get_doc(self) -> list[list[str]] | None:
|
||||
if self._new_docstrings is not None:
|
||||
return self._new_docstrings
|
||||
return super().get_doc() # type: ignore[misc]
|
||||
@@ -1262,7 +1273,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
return (inspect.isfunction(member) or inspect.isbuiltin(member) or
|
||||
(inspect.isroutine(member) and isinstance(parent, ModuleDocumenter)))
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str | None:
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
if self.config.autodoc_typehints in ('none', 'description'):
|
||||
kwargs.setdefault('show_annotation', False)
|
||||
if self.config.autodoc_typehints_format == "short":
|
||||
@@ -1275,7 +1286,7 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
except TypeError as exc:
|
||||
logger.warning(__("Failed to get a function signature for %s: %s"),
|
||||
self.fullname, exc)
|
||||
return None
|
||||
return ''
|
||||
except ValueError:
|
||||
args = ''
|
||||
|
||||
@@ -1319,13 +1330,13 @@ class FunctionDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # typ
|
||||
if dispatchfunc:
|
||||
documenter = FunctionDocumenter(self.directive, '')
|
||||
documenter.object = dispatchfunc
|
||||
documenter.objpath = [None]
|
||||
documenter.objpath = ['']
|
||||
sigs.append(documenter.format_signature())
|
||||
if overloaded:
|
||||
if overloaded and self.analyzer is not None:
|
||||
actual = inspect.signature(self.object,
|
||||
type_aliases=self.config.autodoc_type_aliases)
|
||||
__globals__ = safe_getattr(self.object, '__globals__', {})
|
||||
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||
for overload in self.analyzer.overloads['.'.join(self.objpath)]:
|
||||
overload = self.merge_default_value(actual, overload)
|
||||
overload = evaluate_signature(overload, __globals__,
|
||||
self.config.autodoc_type_aliases)
|
||||
@@ -1384,12 +1395,12 @@ class DecoratorDocumenter(FunctionDocumenter):
|
||||
# must be lower than FunctionDocumenter
|
||||
priority = -1
|
||||
|
||||
def format_args(self, **kwargs: Any) -> Any:
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
args = super().format_args(**kwargs)
|
||||
if ',' in args:
|
||||
return args
|
||||
else:
|
||||
return None
|
||||
return ''
|
||||
|
||||
|
||||
# Types which have confusing metaclass signatures it would be best not to show.
|
||||
@@ -1427,7 +1438,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
priority = 15
|
||||
|
||||
_signature_class: Any = None
|
||||
_signature_method_name: str = None
|
||||
_signature_method_name: str = ''
|
||||
|
||||
def __init__(self, *args: Any) -> None:
|
||||
super().__init__(*args)
|
||||
@@ -1549,26 +1560,27 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
# with __init__ in C and no `__text_signature__`.
|
||||
return None, None, None
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str | None:
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
if self.config.autodoc_typehints in ('none', 'description'):
|
||||
kwargs.setdefault('show_annotation', False)
|
||||
if self.config.autodoc_typehints_format == "short":
|
||||
kwargs.setdefault('unqualified_typehints', True)
|
||||
|
||||
try:
|
||||
self._signature_class, self._signature_method_name, sig = self._get_signature()
|
||||
self._signature_class, _signature_method_name, sig = self._get_signature()
|
||||
except TypeError as exc:
|
||||
# __signature__ attribute contained junk
|
||||
logger.warning(__("Failed to get a constructor signature for %s: %s"),
|
||||
self.fullname, exc)
|
||||
return None
|
||||
return ''
|
||||
self._signature_method_name = _signature_method_name or ''
|
||||
|
||||
if sig is None:
|
||||
return None
|
||||
return ''
|
||||
|
||||
return stringify_signature(sig, show_return_annotation=False, **kwargs)
|
||||
|
||||
def _find_signature(self) -> tuple[str, str]:
|
||||
def _find_signature(self) -> tuple[str | None, str | None] | None:
|
||||
result = super()._find_signature()
|
||||
if result is not None:
|
||||
# Strip a return value from signature of constructor in docstring (first entry)
|
||||
@@ -1731,8 +1743,7 @@ class ClassDocumenter(DocstringSignatureMixin, ModuleLevelDocumenter): # type:
|
||||
pass
|
||||
if self.doc_as_attr:
|
||||
# Don't show the docstring of the class when it is an alias.
|
||||
comment = self.get_variable_comment()
|
||||
if comment:
|
||||
if self.get_variable_comment():
|
||||
return []
|
||||
else:
|
||||
return None
|
||||
@@ -1882,12 +1893,12 @@ class ExceptionDocumenter(ClassDocumenter):
|
||||
|
||||
class DataDocumenterMixinBase:
|
||||
# define types of instance variables
|
||||
config: Config = None
|
||||
env: BuildEnvironment = None
|
||||
modname: str = None
|
||||
parent: Any = None
|
||||
object: Any = None
|
||||
objpath: list[str] = None
|
||||
config: Config
|
||||
env: BuildEnvironment
|
||||
modname: str
|
||||
parent: Any
|
||||
object: Any
|
||||
objpath: list[str]
|
||||
|
||||
def should_suppress_directive_header(self) -> bool:
|
||||
"""Check directive header should be suppressed."""
|
||||
@@ -2007,7 +2018,7 @@ class DataDocumenter(GenericAliasMixin,
|
||||
if super().should_suppress_value_header():
|
||||
return True
|
||||
else:
|
||||
doc = self.get_doc()
|
||||
doc = self.get_doc() or []
|
||||
docstring, metadata = separate_metadata('\n'.join(sum(doc, [])))
|
||||
if 'hide-value' in metadata:
|
||||
return True
|
||||
@@ -2116,7 +2127,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
|
||||
return ret
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str | None:
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
if self.config.autodoc_typehints in ('none', 'description'):
|
||||
kwargs.setdefault('show_annotation', False)
|
||||
if self.config.autodoc_typehints_format == "short":
|
||||
@@ -2142,7 +2153,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
except TypeError as exc:
|
||||
logger.warning(__("Failed to get a method signature for %s: %s"),
|
||||
self.fullname, exc)
|
||||
return None
|
||||
return ''
|
||||
except ValueError:
|
||||
args = ''
|
||||
|
||||
@@ -2197,9 +2208,9 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
documenter = MethodDocumenter(self.directive, '')
|
||||
documenter.parent = self.parent
|
||||
documenter.object = dispatchmeth
|
||||
documenter.objpath = [None]
|
||||
documenter.objpath = ['']
|
||||
sigs.append(documenter.format_signature())
|
||||
if overloaded:
|
||||
if overloaded and self.analyzer is not None:
|
||||
if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name):
|
||||
actual = inspect.signature(self.object, bound_method=False,
|
||||
type_aliases=self.config.autodoc_type_aliases)
|
||||
@@ -2208,7 +2219,7 @@ class MethodDocumenter(DocstringSignatureMixin, ClassLevelDocumenter): # type:
|
||||
type_aliases=self.config.autodoc_type_aliases)
|
||||
|
||||
__globals__ = safe_getattr(self.object, '__globals__', {})
|
||||
for overload in self.analyzer.overloads.get('.'.join(self.objpath)):
|
||||
for overload in self.analyzer.overloads['.'.join(self.objpath)]:
|
||||
overload = self.merge_default_value(actual, overload)
|
||||
overload = evaluate_signature(overload, __globals__,
|
||||
self.config.autodoc_type_aliases)
|
||||
@@ -2337,8 +2348,10 @@ class SlotsMixin(DataDocumenterMixinBase):
|
||||
def isslotsattribute(self) -> bool:
|
||||
"""Check the subject is an attribute in __slots__."""
|
||||
try:
|
||||
__slots__ = inspect.getslots(self.parent)
|
||||
return bool(__slots__) and self.objpath[-1] in __slots__
|
||||
if parent___slots__ := inspect.getslots(self.parent):
|
||||
return self.objpath[-1] in parent___slots__
|
||||
else:
|
||||
return False
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
@@ -2358,9 +2371,9 @@ class SlotsMixin(DataDocumenterMixinBase):
|
||||
def get_doc(self) -> list[list[str]] | None:
|
||||
if self.object is SLOTSATTR:
|
||||
try:
|
||||
__slots__ = inspect.getslots(self.parent)
|
||||
if __slots__ and __slots__.get(self.objpath[-1]):
|
||||
docstring = prepare_docstring(__slots__[self.objpath[-1]])
|
||||
parent___slots__ = inspect.getslots(self.parent)
|
||||
if parent___slots__ and parent___slots__.get(self.objpath[-1]):
|
||||
docstring = prepare_docstring(parent___slots__[self.objpath[-1]])
|
||||
return [docstring]
|
||||
else:
|
||||
return []
|
||||
@@ -2411,7 +2424,7 @@ class RuntimeInstanceAttributeMixin(DataDocumenterMixinBase):
|
||||
except (AttributeError, PycodeError):
|
||||
pass
|
||||
|
||||
return None
|
||||
return False
|
||||
|
||||
def import_object(self, raiseerror: bool = False) -> bool:
|
||||
"""Check the existence of runtime instance attribute after failing to import the
|
||||
@@ -2497,8 +2510,7 @@ class UninitializedInstanceAttributeMixin(DataDocumenterMixinBase):
|
||||
def get_doc(self) -> list[list[str]] | None:
|
||||
if self.object is UNINITIALIZED_ATTR:
|
||||
return None
|
||||
else:
|
||||
return super().get_doc() # type: ignore
|
||||
return super().get_doc() # type: ignore
|
||||
|
||||
|
||||
class AttributeDocumenter(GenericAliasMixin, SlotsMixin, # type: ignore
|
||||
@@ -2698,10 +2710,10 @@ class PropertyDocumenter(DocstringStripSignatureMixin, ClassLevelDocumenter): #
|
||||
self.isclassmethod = False
|
||||
return ret
|
||||
|
||||
def format_args(self, **kwargs: Any) -> str | None:
|
||||
def format_args(self, **kwargs: Any) -> str:
|
||||
func = self._get_property_getter()
|
||||
if func is None:
|
||||
return None
|
||||
return ''
|
||||
|
||||
# update the annotations of the property getter
|
||||
self.env.app.emit('autodoc-before-process-signature', func, False)
|
||||
|
@@ -189,11 +189,11 @@ def get_object_members(
|
||||
|
||||
# members in __slots__
|
||||
try:
|
||||
__slots__ = getslots(subject)
|
||||
if __slots__:
|
||||
subject___slots__ = getslots(subject)
|
||||
if subject___slots__:
|
||||
from sphinx.ext.autodoc import SLOTSATTR
|
||||
|
||||
for name in __slots__:
|
||||
for name in subject___slots__:
|
||||
members[name] = Attribute(name, True, SLOTSATTR)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
@@ -226,7 +226,7 @@ def get_object_members(
|
||||
return members
|
||||
|
||||
|
||||
def get_class_members(subject: Any, objpath: list[str], attrgetter: Callable,
|
||||
def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
|
||||
inherit_docstrings: bool = True) -> dict[str, ObjectMember]:
|
||||
"""Get members and attributes of target class."""
|
||||
from sphinx.ext.autodoc import INSTANCEATTR, ObjectMember
|
||||
@@ -250,11 +250,11 @@ def get_class_members(subject: Any, objpath: list[str], attrgetter: Callable,
|
||||
|
||||
# members in __slots__
|
||||
try:
|
||||
__slots__ = getslots(subject)
|
||||
if __slots__:
|
||||
subject___slots__ = getslots(subject)
|
||||
if subject___slots__:
|
||||
from sphinx.ext.autodoc import SLOTSATTR
|
||||
|
||||
for name, docstring in __slots__.items():
|
||||
for name, docstring in subject___slots__.items():
|
||||
members[name] = ObjectMember(name, SLOTSATTR, class_=subject,
|
||||
docstring=docstring)
|
||||
except (TypeError, ValueError):
|
||||
|
@@ -151,7 +151,7 @@ class FakeApplication:
|
||||
self.extensions: dict[str, Extension] = {}
|
||||
self.srcdir = None
|
||||
self.config = Config()
|
||||
self.project = Project(None, None)
|
||||
self.project = Project('', {})
|
||||
self.registry = SphinxComponentRegistry()
|
||||
|
||||
|
||||
@@ -671,7 +671,7 @@ def _import_by_name(name: str, grouped_exception: bool = True) -> tuple[Any, Any
|
||||
|
||||
# ... then as MODNAME, MODNAME.OBJ1, MODNAME.OBJ1.OBJ2, ...
|
||||
last_j = 0
|
||||
modname = None
|
||||
modname = ''
|
||||
for j in reversed(range(1, len(name_parts) + 1)):
|
||||
last_j = j
|
||||
modname = '.'.join(name_parts[:j])
|
||||
@@ -742,6 +742,7 @@ class AutoLink(SphinxRole):
|
||||
"""
|
||||
def run(self) -> tuple[list[Node], list[system_message]]:
|
||||
pyobj_role = self.env.get_domain('py').role('obj')
|
||||
assert pyobj_role is not None
|
||||
objects, errors = pyobj_role('obj', self.rawtext, self.text, self.lineno,
|
||||
self.inliner, self.options, self.content)
|
||||
if errors:
|
||||
|
@@ -50,7 +50,7 @@ from sphinx.util.osutil import ensuredir
|
||||
from sphinx.util.template import SphinxTemplateLoader
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from collections.abc import Sequence
|
||||
from collections.abc import Sequence, Set
|
||||
from gettext import NullTranslations
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -80,7 +80,7 @@ class DummyApplication:
|
||||
|
||||
class AutosummaryEntry(NamedTuple):
|
||||
name: str
|
||||
path: str
|
||||
path: str | None
|
||||
template: str
|
||||
recursive: bool
|
||||
|
||||
@@ -231,104 +231,6 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
qualname: str | None = None) -> str:
|
||||
doc = get_documenter(app, obj, parent)
|
||||
|
||||
def skip_member(obj: Any, name: str, objtype: str) -> bool:
|
||||
try:
|
||||
return app.emit_firstresult('autodoc-skip-member', objtype, name,
|
||||
obj, False, {})
|
||||
except Exception as exc:
|
||||
logger.warning(__('autosummary: failed to determine %r to be documented, '
|
||||
'the following exception was raised:\n%s'),
|
||||
name, exc, type='autosummary')
|
||||
return False
|
||||
|
||||
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]:
|
||||
members = {}
|
||||
for name in members_of(obj, app.config):
|
||||
try:
|
||||
members[name] = safe_getattr(obj, name)
|
||||
except AttributeError:
|
||||
continue
|
||||
return members
|
||||
|
||||
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] = []
|
||||
|
||||
all_members = get_all_members(obj)
|
||||
for name, value in all_members.items():
|
||||
documenter = get_documenter(app, value, obj)
|
||||
if documenter.objtype in types:
|
||||
# skip imported members if expected
|
||||
if imported or getattr(value, '__module__', None) == obj.__name__:
|
||||
skipped = skip_member(value, name, documenter.objtype)
|
||||
if skipped is True:
|
||||
pass
|
||||
elif skipped is False:
|
||||
# show the member forcedly
|
||||
items.append(name)
|
||||
public.append(name)
|
||||
else:
|
||||
items.append(name)
|
||||
if name in include_public or not name.startswith('_'):
|
||||
# considers member as public
|
||||
public.append(name)
|
||||
return public, items
|
||||
|
||||
def get_module_attrs(members: Any) -> tuple[list[str], list[str]]:
|
||||
"""Find module attributes with docstrings."""
|
||||
attrs, public = [], []
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(name)
|
||||
attr_docs = analyzer.find_attr_docs()
|
||||
for namespace, attr_name in attr_docs:
|
||||
if namespace == '' and attr_name in members:
|
||||
attrs.append(attr_name)
|
||||
if not attr_name.startswith('_'):
|
||||
public.append(attr_name)
|
||||
except PycodeError:
|
||||
pass # give up if ModuleAnalyzer fails to parse code
|
||||
return public, attrs
|
||||
|
||||
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)
|
||||
if module and hasattr(module, '__sphinx_mock__'):
|
||||
continue
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
items.append(fullname)
|
||||
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.update(context)
|
||||
|
||||
@@ -340,13 +242,13 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
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)
|
||||
_get_members(doc, app, obj, {'function'}, imported=imported_members)
|
||||
ns['classes'], ns['all_classes'] = \
|
||||
get_members(obj, {'class'}, imported=imported_members)
|
||||
_get_members(doc, app, obj, {'class'}, imported=imported_members)
|
||||
ns['exceptions'], ns['all_exceptions'] = \
|
||||
get_members(obj, {'exception'}, imported=imported_members)
|
||||
_get_members(doc, app, obj, {'exception'}, imported=imported_members)
|
||||
ns['attributes'], ns['all_attributes'] = \
|
||||
get_module_attrs(ns['members'])
|
||||
_get_module_attrs(name, ns['members'])
|
||||
ispackage = hasattr(obj, '__path__')
|
||||
if ispackage and recursive:
|
||||
# Use members that are not modules as skip list, because it would then mean
|
||||
@@ -366,7 +268,7 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
# Otherwise, use get_modules method normally
|
||||
if respect_module_all and '__all__' in dir(obj):
|
||||
imported_modules, all_imported_modules = \
|
||||
get_members(obj, {'module'}, imported=True)
|
||||
_get_members(doc, app, obj, {'module'}, imported=True)
|
||||
skip += all_imported_modules
|
||||
imported_modules = [name + '.' + modname for modname in imported_modules]
|
||||
all_imported_modules = \
|
||||
@@ -376,7 +278,8 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
imported_modules, all_imported_modules = [], []
|
||||
public_members = None
|
||||
|
||||
modules, all_modules = get_modules(obj, skip=skip, public_members=public_members)
|
||||
modules, all_modules = _get_modules(obj, skip=skip, name=name,
|
||||
public_members=public_members)
|
||||
ns['modules'] = imported_modules + modules
|
||||
ns["all_modules"] = all_imported_modules + all_modules
|
||||
elif doc.objtype == 'class':
|
||||
@@ -384,9 +287,9 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
ns['inherited_members'] = \
|
||||
set(dir(obj)) - set(obj.__dict__.keys())
|
||||
ns['methods'], ns['all_methods'] = \
|
||||
get_members(obj, {'method'}, ['__init__'])
|
||||
_get_members(doc, app, obj, {'method'}, include_public={'__init__'})
|
||||
ns['attributes'], ns['all_attributes'] = \
|
||||
get_members(obj, {'attribute', 'property'})
|
||||
_get_members(doc, app, obj, {'attribute', 'property'})
|
||||
|
||||
if modname is None or qualname is None:
|
||||
modname, qualname = split_full_qualified_name(name)
|
||||
@@ -413,6 +316,114 @@ def generate_autosummary_content(name: str, obj: Any, parent: Any,
|
||||
return template.render(doc.objtype, ns)
|
||||
|
||||
|
||||
def _skip_member(app: Sphinx, obj: Any, name: str, objtype: str) -> bool:
|
||||
try:
|
||||
return app.emit_firstresult('autodoc-skip-member', objtype, name,
|
||||
obj, False, {})
|
||||
except Exception as exc:
|
||||
logger.warning(__('autosummary: failed to determine %r to be documented, '
|
||||
'the following exception was raised:\n%s'),
|
||||
name, exc, type='autosummary')
|
||||
return False
|
||||
|
||||
|
||||
def _get_class_members(obj: Any) -> dict[str, Any]:
|
||||
members = sphinx.ext.autodoc.get_class_members(obj, None, safe_getattr)
|
||||
return {name: member.object for name, member in members.items()}
|
||||
|
||||
|
||||
def _get_module_members(app: Sphinx, obj: Any) -> dict[str, Any]:
|
||||
members = {}
|
||||
for name in members_of(obj, app.config):
|
||||
try:
|
||||
members[name] = safe_getattr(obj, name)
|
||||
except AttributeError:
|
||||
continue
|
||||
return members
|
||||
|
||||
|
||||
def _get_all_members(doc: type[Documenter], app: Sphinx, obj: Any) -> dict[str, Any]:
|
||||
if doc.objtype == 'module':
|
||||
return _get_module_members(app, obj)
|
||||
elif doc.objtype == 'class':
|
||||
return _get_class_members(obj)
|
||||
return {}
|
||||
|
||||
|
||||
def _get_members(doc: type[Documenter], app: Sphinx, obj: Any, types: set[str], *,
|
||||
include_public: Set[str] = frozenset(),
|
||||
imported: bool = True) -> tuple[list[str], list[str]]:
|
||||
items: list[str] = []
|
||||
public: list[str] = []
|
||||
|
||||
all_members = _get_all_members(doc, app, obj)
|
||||
for name, value in all_members.items():
|
||||
documenter = get_documenter(app, value, obj)
|
||||
if documenter.objtype in types:
|
||||
# skip imported members if expected
|
||||
if imported or getattr(value, '__module__', None) == obj.__name__:
|
||||
skipped = _skip_member(app, value, name, documenter.objtype)
|
||||
if skipped is True:
|
||||
pass
|
||||
elif skipped is False:
|
||||
# show the member forcedly
|
||||
items.append(name)
|
||||
public.append(name)
|
||||
else:
|
||||
items.append(name)
|
||||
if name in include_public or not name.startswith('_'):
|
||||
# considers member as public
|
||||
public.append(name)
|
||||
return public, items
|
||||
|
||||
|
||||
def _get_module_attrs(name: str, members: Any) -> tuple[list[str], list[str]]:
|
||||
"""Find module attributes with docstrings."""
|
||||
attrs, public = [], []
|
||||
try:
|
||||
analyzer = ModuleAnalyzer.for_module(name)
|
||||
attr_docs = analyzer.find_attr_docs()
|
||||
for namespace, attr_name in attr_docs:
|
||||
if namespace == '' and attr_name in members:
|
||||
attrs.append(attr_name)
|
||||
if not attr_name.startswith('_'):
|
||||
public.append(attr_name)
|
||||
except PycodeError:
|
||||
pass # give up if ModuleAnalyzer fails to parse code
|
||||
return public, attrs
|
||||
|
||||
|
||||
def _get_modules(
|
||||
obj: Any,
|
||||
*,
|
||||
skip: Sequence[str],
|
||||
name: str,
|
||||
public_members: Sequence[str] | None = None) -> tuple[list[str], list[str]]:
|
||||
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)
|
||||
if module and hasattr(module, '__sphinx_mock__'):
|
||||
continue
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
items.append(fullname)
|
||||
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
|
||||
|
||||
|
||||
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,
|
||||
@@ -567,7 +578,7 @@ def find_autosummary_in_lines(
|
||||
|
||||
recursive = False
|
||||
toctree: str | None = None
|
||||
template = None
|
||||
template = ''
|
||||
current_module = module
|
||||
in_autosummary = False
|
||||
base_indent = ""
|
||||
@@ -617,7 +628,7 @@ def find_autosummary_in_lines(
|
||||
base_indent = m.group(1)
|
||||
recursive = False
|
||||
toctree = None
|
||||
template = None
|
||||
template = ''
|
||||
continue
|
||||
|
||||
m = automodule_re.search(line)
|
||||
|
@@ -163,7 +163,7 @@ class InheritanceGraph:
|
||||
return classes
|
||||
|
||||
def _class_info(self, classes: list[Any], show_builtins: bool, private_bases: bool,
|
||||
parts: int, aliases: dict[str, str], top_classes: list[Any],
|
||||
parts: int, aliases: dict[str, str] | None, top_classes: list[Any],
|
||||
) -> list[tuple[str, str, list[str], str]]:
|
||||
"""Return name and bases for all classes that are ancestors of
|
||||
*classes*.
|
||||
@@ -219,7 +219,7 @@ class InheritanceGraph:
|
||||
for cls in classes:
|
||||
recurse(cls)
|
||||
|
||||
return list(all_classes.values())
|
||||
return list(all_classes.values()) # type: ignore[arg-type]
|
||||
|
||||
def class_name(
|
||||
self, cls: Any, parts: int = 0, aliases: dict[str, str] | None = None,
|
||||
@@ -361,7 +361,7 @@ class InheritanceDiagram(SphinxDirective):
|
||||
# Create a graph starting with the list of classes
|
||||
try:
|
||||
graph = InheritanceGraph(
|
||||
class_names, self.env.ref_context.get('py:module'),
|
||||
class_names, self.env.ref_context.get('py:module'), # type: ignore[arg-type]
|
||||
parts=node['parts'],
|
||||
private_bases='private-bases' in self.options,
|
||||
aliases=self.config.inheritance_alias,
|
||||
|
@@ -119,7 +119,7 @@ def _strip_basic_auth(url: str) -> str:
|
||||
return urlunsplit(frags)
|
||||
|
||||
|
||||
def _read_from_url(url: str, config: Config | None = None) -> IO:
|
||||
def _read_from_url(url: str, *, config: Config) -> IO:
|
||||
"""Reads data from *url* with an HTTP *GET*.
|
||||
|
||||
This function supports fetching from resources which use basic HTTP auth as
|
||||
@@ -430,7 +430,7 @@ def _resolve_reference(env: BuildEnvironment, inv_name: str | None, inventory: I
|
||||
and (domain_name + ":*") in env.config.intersphinx_disabled_reftypes:
|
||||
return None
|
||||
domain = env.get_domain(domain_name)
|
||||
objtypes = domain.objtypes_for_role(typ)
|
||||
objtypes = domain.objtypes_for_role(typ) or ()
|
||||
if not objtypes:
|
||||
return None
|
||||
return _resolve_reference_in_domain(env, inv_name, inventory,
|
||||
@@ -555,15 +555,20 @@ class IntersphinxRole(SphinxRole):
|
||||
|
||||
def get_inventory_and_name_suffix(self, name: str) -> tuple[str | None, str]:
|
||||
assert name.startswith('external'), name
|
||||
assert name[8] in ':+', name
|
||||
# either we have an explicit inventory name, i.e,
|
||||
# :external+inv:role: or
|
||||
# :external+inv:domain:role:
|
||||
# or we look in all inventories, i.e.,
|
||||
# :external:role: or
|
||||
# :external:domain:role:
|
||||
inv, suffix = IntersphinxRole._re_inv_ref.fullmatch(name, 8).group(2, 3)
|
||||
return inv, suffix
|
||||
suffix = name[9:]
|
||||
if name[8] == '+':
|
||||
inv_name, suffix = suffix.split(':', 1)
|
||||
return inv_name, suffix
|
||||
elif name[8] == ':':
|
||||
return None, suffix
|
||||
else:
|
||||
raise ValueError(f'Malformed :external: role name: {name}')
|
||||
|
||||
def get_role_name(self, name: str) -> tuple[str, str] | None:
|
||||
names = name.split(':')
|
||||
@@ -597,6 +602,7 @@ class IntersphinxRole(SphinxRole):
|
||||
domain = self.env.get_domain(role[0])
|
||||
if domain:
|
||||
role_func = domain.role(role[1])
|
||||
assert role_func is not None
|
||||
|
||||
return role_func(':'.join(role), self.rawtext, self.text, self.lineno,
|
||||
self.inliner, self.options, self.content)
|
||||
@@ -704,8 +710,8 @@ def inspect_main(argv: list[str]) -> None:
|
||||
class MockConfig:
|
||||
intersphinx_timeout: int | None = None
|
||||
tls_verify = False
|
||||
tls_cacerts = None
|
||||
user_agent = None
|
||||
tls_cacerts: str | dict[str, str] | None = None
|
||||
user_agent: str = ''
|
||||
|
||||
class MockApp:
|
||||
srcdir = ''
|
||||
|
@@ -156,12 +156,15 @@ class GoogleDocstring:
|
||||
obj: Any = None,
|
||||
options: Any = None,
|
||||
) -> None:
|
||||
self._config = config
|
||||
self._app = app
|
||||
|
||||
if not self._config:
|
||||
if config:
|
||||
self._config = config
|
||||
elif app:
|
||||
self._config = app.config
|
||||
else:
|
||||
from sphinx.ext.napoleon import Config
|
||||
self._config = self._app.config if self._app else Config() # type: ignore
|
||||
|
||||
self._config = Config() # type: ignore
|
||||
|
||||
if not what:
|
||||
if inspect.isclass(obj):
|
||||
@@ -1048,7 +1051,8 @@ def _convert_numpy_type_spec(
|
||||
"reference": lambda x: x,
|
||||
}
|
||||
|
||||
converted = "".join(converters.get(type_)(token) for token, type_ in types)
|
||||
converted = "".join(converters.get(type_)(token) # type: ignore[misc]
|
||||
for token, type_ in types)
|
||||
|
||||
return converted
|
||||
|
||||
@@ -1273,7 +1277,7 @@ class NumpyDocstring(GoogleDocstring):
|
||||
return g[2], g[1]
|
||||
raise ValueError("%s is not a item name" % text)
|
||||
|
||||
def push_item(name: str, rest: list[str]) -> None:
|
||||
def push_item(name: str | None, rest: list[str]) -> None:
|
||||
if not name:
|
||||
return
|
||||
name, role = parse_item_name(name)
|
||||
|
@@ -145,21 +145,21 @@ class TokenProcessor:
|
||||
|
||||
return self.current
|
||||
|
||||
def fetch_until(self, condition: Any) -> list[Token | None]:
|
||||
def fetch_until(self, condition: Any) -> list[Token]:
|
||||
"""Fetch tokens until specified token appeared.
|
||||
|
||||
.. note:: This also handles parenthesis well.
|
||||
"""
|
||||
tokens = []
|
||||
while self.fetch_token():
|
||||
tokens.append(self.current)
|
||||
if self.current == condition:
|
||||
while current := self.fetch_token():
|
||||
tokens.append(current)
|
||||
if current == condition:
|
||||
break
|
||||
if self.current == [OP, '(']:
|
||||
if current == [OP, '(']:
|
||||
tokens += self.fetch_until([OP, ')'])
|
||||
elif self.current == [OP, '{']:
|
||||
elif current == [OP, '{']:
|
||||
tokens += self.fetch_until([OP, '}'])
|
||||
elif self.current == [OP, '[']:
|
||||
elif current == [OP, '[']:
|
||||
tokens += self.fetch_until([OP, ']'])
|
||||
|
||||
return tokens
|
||||
@@ -176,22 +176,22 @@ class AfterCommentParser(TokenProcessor):
|
||||
super().__init__(lines)
|
||||
self.comment: str | None = None
|
||||
|
||||
def fetch_rvalue(self) -> list[Token | None]:
|
||||
def fetch_rvalue(self) -> list[Token]:
|
||||
"""Fetch right-hand value of assignment."""
|
||||
tokens = []
|
||||
while self.fetch_token():
|
||||
tokens.append(self.current)
|
||||
if self.current == [OP, '(']:
|
||||
while current := self.fetch_token():
|
||||
tokens.append(current)
|
||||
if current == [OP, '(']:
|
||||
tokens += self.fetch_until([OP, ')'])
|
||||
elif self.current == [OP, '{']:
|
||||
elif current == [OP, '{']:
|
||||
tokens += self.fetch_until([OP, '}'])
|
||||
elif self.current == [OP, '[']:
|
||||
elif current == [OP, '[']:
|
||||
tokens += self.fetch_until([OP, ']'])
|
||||
elif self.current == INDENT:
|
||||
elif current == INDENT:
|
||||
tokens += self.fetch_until(DEDENT)
|
||||
elif self.current == [OP, ';']: # NoQA: SIM114
|
||||
elif current == [OP, ';']: # NoQA: SIM114
|
||||
break
|
||||
elif self.current and self.current.kind not in {OP, NAME, NUMBER, STRING}:
|
||||
elif current and current.kind not in {OP, NAME, NUMBER, STRING}:
|
||||
break
|
||||
|
||||
return tokens
|
||||
@@ -200,14 +200,17 @@ class AfterCommentParser(TokenProcessor):
|
||||
"""Parse the code and obtain comment after assignment."""
|
||||
# skip lvalue (or whole of AnnAssign)
|
||||
while (tok := self.fetch_token()) and not tok.match([OP, '='], NEWLINE, COMMENT):
|
||||
assert self.current
|
||||
assert tok
|
||||
assert tok is not None
|
||||
|
||||
# skip rvalue (if exists)
|
||||
if self.current == [OP, '=']:
|
||||
if tok == [OP, '=']:
|
||||
self.fetch_rvalue()
|
||||
tok = self.current
|
||||
assert tok is not None
|
||||
|
||||
if self.current == COMMENT:
|
||||
self.comment = self.current.value # type: ignore[union-attr]
|
||||
if tok == COMMENT:
|
||||
self.comment = tok.value
|
||||
|
||||
|
||||
class VariableCommentPicker(ast.NodeVisitor):
|
||||
|
@@ -85,12 +85,14 @@ class SphinxComponentRegistry:
|
||||
|
||||
#: additional enumerable nodes
|
||||
#: a dict of node class -> tuple of figtype and title_getter function
|
||||
self.enumerable_nodes: dict[type[Node], tuple[str, TitleGetter]] = {}
|
||||
self.enumerable_nodes: dict[type[Node], tuple[str, TitleGetter | None]] = {}
|
||||
|
||||
#: HTML inline and block math renderers
|
||||
#: a dict of name -> tuple of visit function and depart function
|
||||
self.html_inline_math_renderers: dict[str, tuple[Callable, Callable]] = {}
|
||||
self.html_block_math_renderers: dict[str, tuple[Callable, Callable]] = {}
|
||||
self.html_inline_math_renderers: dict[str,
|
||||
tuple[Callable, Callable | None]] = {}
|
||||
self.html_block_math_renderers: dict[str,
|
||||
tuple[Callable, Callable | None]] = {}
|
||||
|
||||
#: HTML assets
|
||||
self.html_assets_policy: str = 'per_page'
|
||||
@@ -99,12 +101,12 @@ class SphinxComponentRegistry:
|
||||
self.html_themes: dict[str, str] = {}
|
||||
|
||||
#: js_files; list of JS paths or URLs
|
||||
self.js_files: list[tuple[str, dict[str, Any]]] = []
|
||||
self.js_files: list[tuple[str | None, dict[str, Any]]] = []
|
||||
|
||||
#: LaTeX packages; list of package names and its options
|
||||
self.latex_packages: list[tuple[str, str]] = []
|
||||
self.latex_packages: list[tuple[str, str | None]] = []
|
||||
|
||||
self.latex_packages_after_hyperref: list[tuple[str, str]] = []
|
||||
self.latex_packages_after_hyperref: list[tuple[str, str | None]] = []
|
||||
|
||||
#: post transforms; list of transforms
|
||||
self.post_transforms: list[type[Transform]] = []
|
||||
@@ -120,7 +122,7 @@ class SphinxComponentRegistry:
|
||||
|
||||
#: custom handlers for translators
|
||||
#: a dict of builder name -> dict of node name -> visitor and departure functions
|
||||
self.translation_handlers: dict[str, dict[str, tuple[Callable, Callable]]] = {}
|
||||
self.translation_handlers: dict[str, dict[str, tuple[Callable, Callable | None]]] = {}
|
||||
|
||||
#: additional transforms; list of transforms
|
||||
self.transforms: list[type[Transform]] = []
|
||||
@@ -151,8 +153,7 @@ class SphinxComponentRegistry:
|
||||
|
||||
self.load_extension(app, entry_point.module)
|
||||
|
||||
def create_builder(self, app: Sphinx, name: str,
|
||||
env: BuildEnvironment | None = None) -> Builder:
|
||||
def create_builder(self, app: Sphinx, name: str, env: BuildEnvironment) -> Builder:
|
||||
if name not in self.builders:
|
||||
raise SphinxError(__('Builder name %s not registered') % name)
|
||||
|
||||
@@ -234,7 +235,7 @@ class SphinxComponentRegistry:
|
||||
directive = type(directivename,
|
||||
(GenericObject, object),
|
||||
{'indextemplate': indextemplate,
|
||||
'parse_node': staticmethod(parse_node),
|
||||
'parse_node': parse_node and staticmethod(parse_node),
|
||||
'doc_field_types': doc_field_types})
|
||||
|
||||
self.add_directive_to_domain('std', directivename, directive)
|
||||
@@ -314,7 +315,7 @@ class SphinxComponentRegistry:
|
||||
def add_translation_handlers(
|
||||
self,
|
||||
node: type[Element],
|
||||
**kwargs: tuple[Callable | None, Callable | None],
|
||||
**kwargs: tuple[Callable, Callable | None],
|
||||
) -> None:
|
||||
logger.debug('[app] adding translation_handlers: %r, %r', node, kwargs)
|
||||
for builder_name, handlers in kwargs.items():
|
||||
@@ -412,16 +413,18 @@ class SphinxComponentRegistry:
|
||||
def add_html_math_renderer(
|
||||
self,
|
||||
name: str,
|
||||
inline_renderers: tuple[Callable | None, Callable | None] | None,
|
||||
block_renderers: tuple[Callable | None, Callable | None] | None,
|
||||
inline_renderers: tuple[Callable, Callable | None] | None,
|
||||
block_renderers: tuple[Callable, Callable | None] | None,
|
||||
) -> None:
|
||||
logger.debug('[app] adding html_math_renderer: %s, %r, %r',
|
||||
name, inline_renderers, block_renderers)
|
||||
if name in self.html_inline_math_renderers:
|
||||
raise ExtensionError(__('math renderer %s is already registered') % name)
|
||||
|
||||
self.html_inline_math_renderers[name] = inline_renderers
|
||||
self.html_block_math_renderers[name] = block_renderers
|
||||
if inline_renderers is not None:
|
||||
self.html_inline_math_renderers[name] = inline_renderers
|
||||
if block_renderers is not None:
|
||||
self.html_block_math_renderers[name] = block_renderers
|
||||
|
||||
def add_html_theme(self, name: str, theme_path: str) -> None:
|
||||
self.html_themes[name] = theme_path
|
||||
|
@@ -10,7 +10,7 @@ from docutils.parsers.rst.states import Body
|
||||
field_list_item_re = re.compile(Body.patterns['field_marker'])
|
||||
|
||||
|
||||
def separate_metadata(s: str) -> tuple[str, dict[str, str]]:
|
||||
def separate_metadata(s: str | None) -> tuple[str | None, dict[str, str]]:
|
||||
"""Separate docstring into metadata and others."""
|
||||
in_other_element = False
|
||||
metadata: dict[str, str] = {}
|
||||
|
@@ -130,7 +130,7 @@ def getorigbases(obj: Any) -> tuple[Any, ...] | None:
|
||||
return None
|
||||
|
||||
|
||||
def getslots(obj: Any) -> dict | None:
|
||||
def getslots(obj: Any) -> dict[str, Any] | None:
|
||||
"""Get __slots__ attribute of the class as dict.
|
||||
|
||||
Return None if gienv *obj* does not have __slots__.
|
||||
@@ -148,7 +148,7 @@ def getslots(obj: Any) -> dict | None:
|
||||
elif isinstance(__slots__, str):
|
||||
return {__slots__: None}
|
||||
elif isinstance(__slots__, (list, tuple)):
|
||||
return {e: None for e in __slots__}
|
||||
return dict.fromkeys(__slots__)
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
@@ -898,25 +898,29 @@ class HTML5Translator(SphinxTranslator, BaseTranslator):
|
||||
node['classes'].append('field-odd')
|
||||
|
||||
def visit_math(self, node: Element, math_env: str = '') -> None:
|
||||
name = self.builder.math_renderer_name
|
||||
# see validate_math_renderer
|
||||
name: str = self.builder.math_renderer_name # type: ignore[assignment]
|
||||
visit, _ = self.builder.app.registry.html_inline_math_renderers[name]
|
||||
visit(self, node)
|
||||
|
||||
def depart_math(self, node: Element, math_env: str = '') -> None:
|
||||
name = self.builder.math_renderer_name
|
||||
# see validate_math_renderer
|
||||
name: str = self.builder.math_renderer_name # type: ignore[assignment]
|
||||
_, depart = self.builder.app.registry.html_inline_math_renderers[name]
|
||||
if depart: # type: ignore[truthy-function]
|
||||
if depart:
|
||||
depart(self, node)
|
||||
|
||||
def visit_math_block(self, node: Element, math_env: str = '') -> None:
|
||||
name = self.builder.math_renderer_name
|
||||
# see validate_math_renderer
|
||||
name: str = self.builder.math_renderer_name # type: ignore[assignment]
|
||||
visit, _ = self.builder.app.registry.html_block_math_renderers[name]
|
||||
visit(self, node)
|
||||
|
||||
def depart_math_block(self, node: Element, math_env: str = '') -> None:
|
||||
name = self.builder.math_renderer_name
|
||||
# see validate_math_renderer
|
||||
name: str = self.builder.math_renderer_name # type: ignore[assignment]
|
||||
_, depart = self.builder.app.registry.html_block_math_renderers[name]
|
||||
if depart: # type: ignore[truthy-function]
|
||||
if depart:
|
||||
depart(self, node)
|
||||
|
||||
# See Docutils r9413
|
||||
|
@@ -75,12 +75,11 @@ class LaTeXWriter(writers.Writer):
|
||||
))
|
||||
settings_defaults: dict[str, Any] = {}
|
||||
|
||||
output = None
|
||||
theme: Theme
|
||||
|
||||
def __init__(self, builder: LaTeXBuilder) -> None:
|
||||
super().__init__()
|
||||
self.builder = builder
|
||||
self.theme: Theme = None
|
||||
|
||||
def translate(self) -> None:
|
||||
visitor = self.builder.create_translator(self.document, self.builder, self.theme)
|
||||
@@ -110,17 +109,18 @@ class Table:
|
||||
elif 'colorrows' in self.classes:
|
||||
self.styles.append('colorrows')
|
||||
self.colcount = 0
|
||||
self.colspec: str = None
|
||||
self.colsep: str = None
|
||||
self.colspec: str = ''
|
||||
if 'booktabs' in self.styles or 'borderless' in self.styles:
|
||||
self.colsep = ''
|
||||
self.colsep: str | None = ''
|
||||
elif 'standard' in self.styles:
|
||||
self.colsep = '|'
|
||||
else:
|
||||
self.colsep = None
|
||||
self.colwidths: list[int] = []
|
||||
self.has_problematic = False
|
||||
self.has_oldproblematic = False
|
||||
self.has_verbatim = False
|
||||
self.caption: list[str] = None
|
||||
self.caption: list[str] = []
|
||||
self.stubs: list[int] = []
|
||||
|
||||
# current position
|
||||
@@ -169,6 +169,7 @@ class Table:
|
||||
return self.colspec
|
||||
|
||||
_colsep = self.colsep
|
||||
assert _colsep is not None
|
||||
if self.colwidths and 'colwidths-given' in self.classes:
|
||||
total = sum(self.colwidths)
|
||||
colspecs = [r'\X{%d}{%d}' % (width, total) for width in self.colwidths]
|
||||
@@ -660,6 +661,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
def depart_title(self, node: Element) -> None:
|
||||
self.in_title = 0
|
||||
if isinstance(node.parent, nodes.table):
|
||||
assert self.table is not None
|
||||
self.table.caption = self.popbody()
|
||||
else:
|
||||
self.body.append(self.context.pop())
|
||||
@@ -992,6 +994,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
|
||||
def visit_table(self, node: Element) -> None:
|
||||
if len(self.tables) == 1:
|
||||
assert self.table is not None
|
||||
if self.table.get_table_type() == 'longtable':
|
||||
raise UnsupportedError(
|
||||
'%s:%s: longtable does not support nesting a table.' %
|
||||
@@ -1004,26 +1007,28 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
'%s:%s: deeply nested tables are not implemented.' %
|
||||
(self.curfilestack[-1], node.line or ''))
|
||||
|
||||
self.tables.append(Table(node))
|
||||
if self.table.colsep is None:
|
||||
self.table.colsep = '' if (
|
||||
'booktabs' in self.builder.config.latex_table_style or
|
||||
'borderless' in self.builder.config.latex_table_style
|
||||
) else '|'
|
||||
table = Table(node)
|
||||
self.tables.append(table)
|
||||
if table.colsep is None:
|
||||
table.colsep = '|' * (
|
||||
'booktabs' not in self.builder.config.latex_table_style
|
||||
and 'borderless' not in self.builder.config.latex_table_style
|
||||
)
|
||||
if self.next_table_colspec:
|
||||
self.table.colspec = '{%s}' % self.next_table_colspec + CR
|
||||
if '|' in self.table.colspec:
|
||||
self.table.styles.append('vlines')
|
||||
self.table.colsep = '|'
|
||||
table.colspec = '{%s}' % self.next_table_colspec + CR
|
||||
if '|' in table.colspec:
|
||||
table.styles.append('vlines')
|
||||
table.colsep = '|'
|
||||
else:
|
||||
self.table.styles.append('novlines')
|
||||
self.table.colsep = ''
|
||||
table.styles.append('novlines')
|
||||
table.colsep = ''
|
||||
if 'colwidths-given' in node.get('classes', []):
|
||||
logger.info(__('both tabularcolumns and :widths: option are given. '
|
||||
':widths: is ignored.'), location=node)
|
||||
self.next_table_colspec = None
|
||||
|
||||
def depart_table(self, node: Element) -> None:
|
||||
assert self.table is not None
|
||||
labels = self.hypertarget_to(node)
|
||||
table_type = self.table.get_table_type()
|
||||
table = self.render(table_type + '.tex_t',
|
||||
@@ -1035,6 +1040,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
self.tables.pop()
|
||||
|
||||
def visit_colspec(self, node: Element) -> None:
|
||||
assert self.table is not None
|
||||
self.table.colcount += 1
|
||||
if 'colwidth' in node:
|
||||
self.table.colwidths.append(node['colwidth'])
|
||||
@@ -1051,6 +1057,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
pass
|
||||
|
||||
def visit_thead(self, node: Element) -> None:
|
||||
assert self.table is not None
|
||||
# Redirect head output until header is finished.
|
||||
self.pushbody(self.table.header)
|
||||
|
||||
@@ -1060,6 +1067,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
self.popbody()
|
||||
|
||||
def visit_tbody(self, node: Element) -> None:
|
||||
assert self.table is not None
|
||||
# Redirect body output until table is finished.
|
||||
self.pushbody(self.table.body)
|
||||
|
||||
@@ -1069,6 +1077,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
self.popbody()
|
||||
|
||||
def visit_row(self, node: Element) -> None:
|
||||
assert self.table is not None
|
||||
self.table.col = 0
|
||||
_colsep = self.table.colsep
|
||||
# fill columns if the row starts with the bottom of multirow cell
|
||||
@@ -1088,9 +1097,11 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
(cell.width, _colsep, _colsep, cell.cell_id))
|
||||
|
||||
def depart_row(self, node: Element) -> None:
|
||||
assert self.table is not None
|
||||
self.body.append(r'\\' + CR)
|
||||
cells = [self.table.cell(self.table.row, i) for i in range(self.table.colcount)]
|
||||
underlined = [cell.row + cell.height == self.table.row + 1 for cell in cells]
|
||||
underlined = [cell.row + cell.height == self.table.row + 1 # type: ignore[union-attr]
|
||||
for cell in cells]
|
||||
if all(underlined):
|
||||
self.body.append(r'\sphinxhline')
|
||||
else:
|
||||
@@ -1099,7 +1110,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
if underlined[0] is False:
|
||||
i = 1
|
||||
while i < self.table.colcount and underlined[i] is False:
|
||||
if cells[i - 1].cell_id != cells[i].cell_id:
|
||||
if cells[i - 1].cell_id != cells[i].cell_id: # type: ignore[union-attr]
|
||||
self.body.append(r'\sphinxvlinecrossing{%d}' % i)
|
||||
i += 1
|
||||
while i < self.table.colcount:
|
||||
@@ -1109,17 +1120,19 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
i += j
|
||||
i += 1
|
||||
while i < self.table.colcount and underlined[i] is False:
|
||||
if cells[i - 1].cell_id != cells[i].cell_id:
|
||||
if cells[i - 1].cell_id != cells[i].cell_id: # type: ignore[union-attr]
|
||||
self.body.append(r'\sphinxvlinecrossing{%d}' % i)
|
||||
i += 1
|
||||
self.body.append(r'\sphinxfixclines{%d}' % self.table.colcount)
|
||||
self.table.row += 1
|
||||
|
||||
def visit_entry(self, node: Element) -> None:
|
||||
assert self.table is not None
|
||||
if self.table.col > 0:
|
||||
self.body.append('&')
|
||||
self.table.add_cell(node.get('morerows', 0) + 1, node.get('morecols', 0) + 1)
|
||||
cell = self.table.cell()
|
||||
assert cell is not None
|
||||
context = ''
|
||||
_colsep = self.table.colsep
|
||||
if cell.width > 1:
|
||||
@@ -1166,7 +1179,9 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
|
||||
self.body.append(self.context.pop())
|
||||
|
||||
assert self.table is not None
|
||||
cell = self.table.cell()
|
||||
assert cell is not None
|
||||
self.table.col += cell.width
|
||||
_colsep = self.table.colsep
|
||||
|
||||
@@ -1628,7 +1643,7 @@ class LaTeXTranslator(SphinxTranslator):
|
||||
ids = node['ids'][:] # copy to avoid side-effects
|
||||
while has_dup_label(prev):
|
||||
ids.remove(prev['refid']) # type: ignore
|
||||
prev = get_prev_node(prev)
|
||||
prev = get_prev_node(prev) # type: ignore[arg-type]
|
||||
else:
|
||||
ids = iter(node['ids']) # read-only iterator
|
||||
else:
|
||||
|
Reference in New Issue
Block a user