Enable mypy 'strict optional' for 19 modules (#11422)

Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com>
This commit is contained in:
danieleades 2023-07-23 21:29:04 +01:00 committed by GitHub
parent f4a47f1401
commit 4de540efb6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 217 additions and 145 deletions

View File

@ -309,42 +309,23 @@ disallow_any_generics = true
module = [
"sphinx.builders.html",
"sphinx.builders.latex",
"sphinx.builders.linkcheck",
"sphinx.domains",
"sphinx.domains.c",
"sphinx.domains.cpp",
"sphinx.domains.javascript",
"sphinx.domains.python",
"sphinx.domains.std",
"sphinx.environment",
"sphinx.environment.adapters.toctree",
"sphinx.ext.apidoc",
"sphinx.ext.autodoc",
"sphinx.ext.autodoc.mock",
"sphinx.ext.autodoc.importer",
"sphinx.ext.autodoc.preserve_defaults",
"sphinx.ext.autosummary",
"sphinx.ext.autosummary.generate",
"sphinx.ext.doctest",
"sphinx.ext.graphviz",
"sphinx.ext.inheritance_diagram",
"sphinx.ext.intersphinx",
"sphinx.ext.imgmath",
"sphinx.ext.linkcode",
"sphinx.ext.mathjax",
"sphinx.ext.napoleon",
"sphinx.ext.napoleon.docstring",
"sphinx.pycode.parser",
"sphinx.registry",
"sphinx.testing.util",
"sphinx.transforms.i18n",
"sphinx.transforms.post_transforms.images",
"sphinx.util.cfamily",
"sphinx.util.docfields",
"sphinx.util.docutils",
"sphinx.writers.latex",
"sphinx.writers.text",
"sphinx.writers.xml",
]
strict_optional = false

View File

@ -160,7 +160,10 @@ class BuildInfo:
raise ValueError(__('build info file is broken: %r') % exc) from exc
def __init__(
self, config: Config = None, tags: Tags = None, config_categories: list[str] = [],
self,
config: Config | None = None,
tags: Tags | None = None,
config_categories: list[str] = [],
) -> None:
self.config_hash = ''
self.tags_hash = ''
@ -217,7 +220,7 @@ class StandaloneHTMLBuilder(Builder):
imgpath: str = None
domain_indices: list[DOMAIN_INDEX_TYPE] = []
def __init__(self, app: Sphinx, env: BuildEnvironment = None) -> None:
def __init__(self, app: Sphinx, env: BuildEnvironment | None = None) -> None:
super().__init__(app, env)
# CSS files
@ -1016,7 +1019,7 @@ class StandaloneHTMLBuilder(Builder):
# --------- these are overwritten by the serialization builder
def get_target_uri(self, docname: str, typ: str = None) -> str:
def get_target_uri(self, docname: str, typ: str | None = None) -> str:
return quote(docname) + self.link_suffix
def handle_page(self, pagename: str, addctx: dict, templatename: str = 'page.html',

View File

@ -174,7 +174,7 @@ def _add_uri(app: Sphinx, uri: str, node: nodes.Element,
try:
lineno = get_node_line(node)
except ValueError:
lineno = None
lineno = -1
if uri not in hyperlinks:
hyperlinks[uri] = Hyperlink(uri, docname, app.env.doc2path(docname), lineno)
@ -184,7 +184,7 @@ class Hyperlink(NamedTuple):
uri: str
docname: str
docpath: str
lineno: int | None
lineno: int
class HyperlinkAvailabilityChecker:
@ -374,7 +374,7 @@ class HyperlinkAvailabilityCheckWorker(Thread):
# - Attempt HTTP HEAD before HTTP GET unless page content is required.
# - Follow server-issued HTTP redirects.
# - Respect server-issued HTTP 429 back-offs.
error_message = None
error_message = ''
status_code = -1
response_url = retry_after = ''
for retrieval_method, kwargs in _retrieval_methods(self.check_anchors, anchor):

View File

@ -32,6 +32,7 @@ class XMLBuilder(Builder):
allow_parallel = True
_writer_class: type[XMLWriter] | type[PseudoXMLWriter] = XMLWriter
writer: XMLWriter | PseudoXMLWriter
default_translator_class = XMLTranslator
def init(self) -> None:

View File

@ -226,8 +226,8 @@ class Domain:
for rolename in obj.roles:
self._role2type.setdefault(rolename, []).append(name)
self._type2role[name] = obj.roles[0] if obj.roles else ''
self.objtypes_for_role: Callable[[str], list[str]] = self._role2type.get
self.role_for_objtype: Callable[[str], str] = self._type2role.get
self.objtypes_for_role = self._role2type.get
self.role_for_objtype = self._type2role.get
def setup(self) -> None:
"""Set up domain object."""

View File

@ -2013,7 +2013,9 @@ class ASTFunctionParameter(ASTBase):
self.arg = arg
self.ellipsis = ellipsis
def get_id(self, version: int, objectType: str = None, symbol: Symbol = None) -> str:
def get_id(
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
) -> str:
# this is not part of the normal name mangling in C++
if symbol:
# the anchor will be our parent
@ -3114,8 +3116,8 @@ class ASTType(ASTBase):
def trailingReturn(self) -> ASTType:
return self.decl.trailingReturn
def get_id(self, version: int, objectType: str = None,
symbol: Symbol = None) -> str:
def get_id(self, version: int, objectType: str | None = None,
symbol: Symbol | None = None) -> str:
if version == 1:
res = []
if objectType: # needs the name
@ -3211,7 +3213,9 @@ class ASTTemplateParamConstrainedTypeWithInit(ASTBase):
def isPack(self) -> bool:
return self.type.isPack
def get_id(self, version: int, objectType: str = None, symbol: Symbol = None) -> str:
def get_id(
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
) -> str:
# this is not part of the normal name mangling in C++
assert version >= 2
if symbol:
@ -3250,8 +3254,8 @@ class ASTTypeWithInit(ASTBase):
def isPack(self) -> bool:
return self.type.isPack
def get_id(self, version: int, objectType: str = None,
symbol: Symbol = None) -> str:
def get_id(self, version: int, objectType: str | None = None,
symbol: Symbol | None = None) -> str:
if objectType != 'member':
return self.type.get_id(version, objectType)
if version == 1:
@ -3279,8 +3283,8 @@ class ASTTypeUsing(ASTBase):
self.name = name
self.type = type
def get_id(self, version: int, objectType: str = None,
symbol: Symbol = None) -> str:
def get_id(self, version: int, objectType: str | None = None,
symbol: Symbol | None = None) -> str:
if version == 1:
raise NoOldIdError()
return symbol.get_full_nested_name().get_id(version)
@ -3319,8 +3323,8 @@ class ASTConcept(ASTBase):
def name(self) -> ASTNestedName:
return self.nestedName
def get_id(self, version: int, objectType: str = None,
symbol: Symbol = None) -> str:
def get_id(self, version: int, objectType: str | None = None,
symbol: Symbol | None = None) -> str:
if version == 1:
raise NoOldIdError()
return symbol.get_full_nested_name().get_id(version)
@ -3628,7 +3632,9 @@ class ASTTemplateParamType(ASTTemplateParam):
def get_identifier(self) -> ASTIdentifier:
return self.data.get_identifier()
def get_id(self, version: int, objectType: str = None, symbol: Symbol = None) -> str:
def get_id(
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
) -> str:
# this is not part of the normal name mangling in C++
assert version >= 2
if symbol:
@ -3715,7 +3721,9 @@ class ASTTemplateParamNonType(ASTTemplateParam):
else:
return None
def get_id(self, version: int, objectType: str = None, symbol: Symbol = None) -> str:
def get_id(
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
) -> str:
assert version >= 2
# this is not part of the normal name mangling in C++
if symbol:
@ -3836,7 +3844,9 @@ class ASTTemplateIntroductionParameter(ASTBase):
def get_identifier(self) -> ASTIdentifier:
return self.identifier
def get_id(self, version: int, objectType: str = None, symbol: Symbol = None) -> str:
def get_id(
self, version: int, objectType: str | None = None, symbol: Symbol | None = None,
) -> str:
assert version >= 2
# this is not part of the normal name mangling in C++
if symbol:
@ -4933,7 +4943,7 @@ class Symbol:
Symbol.debug_indent -= 2
def add_name(self, nestedName: ASTNestedName,
templatePrefix: ASTTemplateDeclarationPrefix = None) -> Symbol:
templatePrefix: ASTTemplateDeclarationPrefix | None = None) -> Symbol:
if Symbol.debug_lookup:
Symbol.debug_indent += 1
Symbol.debug_print("add_name:")
@ -6543,7 +6553,7 @@ class DefinitionParser(BaseParser):
header = "Error in declarator or parameters-and-qualifiers"
raise self._make_multi_error(prevErrors, header) from e
def _parse_initializer(self, outer: str = None, allowFallback: bool = True,
def _parse_initializer(self, outer: str | None = None, allowFallback: bool = True,
) -> ASTInitializer:
# initializer # global vars
# -> brace-or-equal-initializer
@ -6592,7 +6602,7 @@ class DefinitionParser(BaseParser):
value = self._parse_expression_fallback(fallbackEnd, parser, allow=allowFallback)
return ASTInitializer(value)
def _parse_type(self, named: bool | str, outer: str = None) -> ASTType:
def _parse_type(self, named: bool | str, outer: str | None = None) -> ASTType:
"""
named=False|'maybe'|True: 'maybe' is e.g., for function objects which
doesn't need to name the arguments
@ -7571,8 +7581,8 @@ class CPPNamespacePopObject(SphinxDirective):
class AliasNode(nodes.Element):
def __init__(self, sig: str, aliasOptions: dict,
env: BuildEnvironment = None,
parentKey: LookupKey = None) -> None:
env: BuildEnvironment | None = None,
parentKey: LookupKey | None = None) -> None:
super().__init__()
self.sig = sig
self.aliasOptions = aliasOptions

View File

@ -143,7 +143,7 @@ class JSObject(ObjectDescription[Tuple[str, str]]):
domain.note_object(fullname, self.objtype, node_id, location=signode)
if 'noindexentry' not in self.options:
indextext = self.get_index_text(mod_name, name_obj)
indextext = self.get_index_text(mod_name, name_obj) # type: ignore[arg-type]
if indextext:
self.indexnode['entries'].append(('single', indextext, node_id, '', None))
@ -438,11 +438,13 @@ class JavaScriptDomain(Domain):
searches.reverse()
newname = None
object_ = None
for search_name in searches:
if search_name in self.objects:
newname = search_name
object_ = self.objects[search_name]
return newname, self.objects.get(newname)
return newname, object_
def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder,
typ: str, target: str, node: pending_xref, contnode: Element,
@ -463,7 +465,7 @@ class JavaScriptDomain(Domain):
name, obj = self.find_obj(env, mod_name, prefix, target, None, 1)
if not obj:
return []
return [('js:' + self.role_for_objtype(obj[2]),
return [('js:' + self.role_for_objtype(obj[2]), # type: ignore[operator]
make_refnode(builder, fromdocname, obj[0], obj[1], contnode, name))]
def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]:

View File

@ -250,6 +250,8 @@ class ReSTDomain(Domain):
typ: str, target: str, node: pending_xref, contnode: Element,
) -> Element | None:
objtypes = self.objtypes_for_role(typ)
if not objtypes:
return None
for objtype in objtypes:
result = self.objects.get((objtype, target))
if result:
@ -266,7 +268,8 @@ class ReSTDomain(Domain):
result = self.objects.get((objtype, target))
if result:
todocname, node_id = result
results.append(('rst:' + self.role_for_objtype(objtype),
results.append(
('rst:' + self.role_for_objtype(objtype), # type: ignore[operator]
make_refnode(builder, fromdocname, todocname, node_id,
contnode, target + ' ' + objtype)))
return results

View File

@ -212,7 +212,7 @@ class TocTree:
if sub_toc_node.get('hidden', False) and not includehidden:
continue
for i, entry in enumerate(
_entries_from_toctree(sub_toc_node, [refdoc] + parents,
_entries_from_toctree(sub_toc_node, [refdoc or ''] + parents,
subtree=True),
start=sub_toc_node.parent.index(sub_toc_node) + 1,
):

View File

@ -117,7 +117,7 @@ class MockFinder(MetaPathFinder):
self.mocked_modules: list[str] = []
def find_spec(self, fullname: str, path: Sequence[bytes | str] | None,
target: ModuleType = None) -> ModuleSpec | None:
target: ModuleType | None = None) -> ModuleSpec | None:
for modname in self.modnames:
# check if fullname is (or is a descendant of) one of our targets
if modname == fullname or fullname.startswith(modname + '.'):

View File

@ -72,6 +72,7 @@ def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
try:
function = get_function_def(obj)
assert function is not None # for mypy
if function.args.defaults or function.args.kw_defaults:
sig = inspect.signature(obj)
defaults = list(function.args.defaults)
@ -90,7 +91,7 @@ def update_defvalue(app: Sphinx, obj: Any, bound_method: bool) -> None:
value = ast_unparse(default)
parameters[i] = param.replace(default=DefaultValue(value))
else:
default = kw_defaults.pop(0)
default = kw_defaults.pop(0) # type: ignore[assignment]
value = get_default_value(lines, default)
if value is None:
value = ast_unparse(default)

View File

@ -192,7 +192,7 @@ class TestGroup:
def __init__(self, name: str) -> None:
self.name = name
self.setup: list[TestCode] = []
self.tests: list[list[TestCode]] = []
self.tests: list[list[TestCode] | tuple[TestCode, None]] = []
self.cleanup: list[TestCode] = []
def add_code(self, code: TestCode, prepend: bool = False) -> None:
@ -206,10 +206,13 @@ class TestGroup:
elif code.type == 'doctest':
self.tests.append([code])
elif code.type == 'testcode':
self.tests.append([code, None])
# "testoutput" may replace the second element
self.tests.append((code, None))
elif code.type == 'testoutput':
if self.tests and len(self.tests[-1]) == 2:
self.tests[-1][1] = code
if self.tests:
latest_test = self.tests[-1]
if len(latest_test) == 2:
self.tests[-1] = [latest_test[0], code]
else:
raise RuntimeError(__('invalid TestCode type'))
@ -233,7 +236,7 @@ class TestCode:
class SphinxDocTestRunner(doctest.DocTestRunner):
def summarize(self, out: Callable, verbose: bool = None, # type: ignore
def summarize(self, out: Callable, verbose: bool | None = None, # type: ignore
) -> tuple[int, int]:
string_io = StringIO()
old_stdout = sys.stdout
@ -339,7 +342,7 @@ Doctest summary
if self.total_failures or self.setup_failures or self.cleanup_failures:
self.app.statuscode = 1
def write(self, build_docnames: Iterable[str], updated_docnames: Sequence[str],
def write(self, build_docnames: Iterable[str] | None, updated_docnames: Sequence[str],
method: str = 'update') -> None:
if build_docnames is None:
build_docnames = sorted(self.env.all_docs)
@ -361,7 +364,7 @@ Doctest summary
return filename
@staticmethod
def get_line_number(node: Node) -> int | None:
def get_line_number(node: Node) -> int:
"""Get the real line number or admit we don't know."""
# TODO: Work out how to store or calculate real (file-relative)
# line numbers for doctest blocks in docstrings.
@ -370,7 +373,7 @@ Doctest summary
# not the file. This is correct where it is set, in
# `docutils.nodes.Node.setup_child`, but Sphinx should report
# relative to the file, not the docstring.
return None
return None # type: ignore[return-value]
if node.line is not None:
# TODO: find the root cause of this off by one error.
return node.line - 1
@ -438,12 +441,12 @@ Doctest summary
group.add_code(code)
if self.config.doctest_global_setup:
code = TestCode(self.config.doctest_global_setup,
'testsetup', filename=None, lineno=0)
'testsetup', filename='<global_setup>', lineno=0)
for group in groups.values():
group.add_code(code, prepend=True)
if self.config.doctest_global_cleanup:
code = TestCode(self.config.doctest_global_cleanup,
'testcleanup', filename=None, lineno=0)
'testcleanup', filename='<global_cleanup>', lineno=0)
for group in groups.values():
group.add_code(code)
if not groups:

View File

@ -298,6 +298,7 @@ def render_dot_html(self: HTML5Translator, node: graphviz, code: str, options: d
self.body.append('<p class="warning">%s</p>' % alt)
self.body.append('</object></div>\n')
else:
assert outfn is not None
with open(outfn + '.map', encoding='utf-8') as mapfile:
imgmap = ClickableMapDefinition(outfn + '.map', mapfile.read(), dot=code)
if imgmap.clickable:

View File

@ -25,6 +25,7 @@ def doctree_read(app: Sphinx, doctree: Node) -> None:
if not callable(env.config.linkcode_resolve):
raise LinkcodeError(
"Function `linkcode_resolve` is not given in conf.py")
assert resolve_target is not None # for mypy
domain_keys = {
'py': ['module', 'fullname'],

View File

@ -377,7 +377,7 @@ def _process_docstring(app: Sphinx, what: str, name: str, obj: Any,
"""
result_lines = lines
docstring: GoogleDocstring = None
docstring: GoogleDocstring
if app.config.napoleon_numpy_docstring:
docstring = NumpyDocstring(result_lines, app.config, app, what, name,
obj, options)
@ -390,7 +390,7 @@ def _process_docstring(app: Sphinx, what: str, name: str, obj: Any,
def _skip_member(app: Sphinx, what: str, name: str, obj: Any,
skip: bool, options: Any) -> bool:
skip: bool, options: Any) -> bool | None:
"""Determine if private and special class members are included in docs.
The following settings in conf.py determine if private and special class

View File

@ -5,7 +5,7 @@ from __future__ import annotations
import locale
from gettext import NullTranslations, translation
from os import path
from typing import Any, Callable
from typing import Any, Callable, Iterable
class _TranslationProxy:
@ -90,7 +90,7 @@ translators: dict[tuple[str, str], NullTranslations] = {}
def init(
locale_dirs: list[str | None],
locale_dirs: Iterable[str | None],
language: str | None,
catalog: str = 'sphinx',
namespace: str = 'general',

View File

@ -145,7 +145,7 @@ class TokenProcessor:
return self.current
def fetch_until(self, condition: Any) -> list[Token]:
def fetch_until(self, condition: Any) -> list[Token | None]:
"""Fetch tokens until specified token appeared.
.. note:: This also handles parenthesis well.
@ -176,7 +176,7 @@ class AfterCommentParser(TokenProcessor):
super().__init__(lines)
self.comment: str | None = None
def fetch_rvalue(self) -> list[Token]:
def fetch_rvalue(self) -> list[Token | None]:
"""Fetch right-hand value of assignment."""
tokens = []
while self.fetch_token():
@ -191,7 +191,7 @@ class AfterCommentParser(TokenProcessor):
tokens += self.fetch_until(DEDENT)
elif self.current == [OP, ';']: # NoQA: SIM114
break
elif self.current.kind not in (OP, NAME, NUMBER, STRING):
elif self.current and self.current.kind not in {OP, NAME, NUMBER, STRING}:
break
return tokens
@ -199,7 +199,7 @@ class AfterCommentParser(TokenProcessor):
def parse(self) -> None:
"""Parse the code and obtain comment after assignment."""
# skip lvalue (or whole of AnnAssign)
while not self.fetch_token().match([OP, '='], NEWLINE, COMMENT):
while (tok := self.fetch_token()) and not tok.match([OP, '='], NEWLINE, COMMENT):
assert self.current
# skip rvalue (if exists)
@ -207,7 +207,7 @@ class AfterCommentParser(TokenProcessor):
self.fetch_rvalue()
if self.current == COMMENT:
self.comment = self.current.value
self.comment = self.current.value # type: ignore[union-attr]
class VariableCommentPicker(ast.NodeVisitor):
@ -502,22 +502,23 @@ class DefinitionFinder(TokenProcessor):
def parse_definition(self, typ: str) -> None:
"""Parse AST of definition."""
name = self.fetch_token()
self.context.append(name.value)
self.context.append(name.value) # type: ignore[union-attr]
funcname = '.'.join(self.context)
if self.decorator:
start_pos = self.decorator.start[0]
self.decorator = None
else:
start_pos = name.start[0]
start_pos = name.start[0] # type: ignore[union-attr]
self.fetch_until([OP, ':'])
if self.fetch_token().match(COMMENT, NEWLINE):
if self.fetch_token().match(COMMENT, NEWLINE): # type: ignore[union-attr]
self.fetch_until(INDENT)
self.indents.append((typ, funcname, start_pos))
else:
# one-liner
self.add_definition(funcname, (typ, start_pos, name.end[0]))
self.add_definition(funcname,
(typ, start_pos, name.end[0])) # type: ignore[union-attr]
self.context.pop()
def finalize_block(self) -> None:
@ -525,11 +526,11 @@ class DefinitionFinder(TokenProcessor):
definition = self.indents.pop()
if definition[0] != 'other':
typ, funcname, start_pos = definition
end_pos = self.current.end[0] - 1
end_pos = self.current.end[0] - 1 # type: ignore[union-attr]
while emptyline_re.match(self.get_line(end_pos)):
end_pos -= 1
self.add_definition(funcname, (typ, start_pos, end_pos))
self.add_definition(funcname, (typ, start_pos, end_pos)) # type: ignore[arg-type]
self.context.pop()

View File

@ -111,6 +111,7 @@ class SphinxTestApp(application.Sphinx):
docutilsconf: str | None = None,
parallel: int = 0,
) -> None:
assert srcdir is not None
self.docutils_conf_path = srcdir / 'docutils.conf'
if docutilsconf is not None:

View File

@ -24,7 +24,7 @@ from sphinx.util.nodes import apply_source_workaround, is_smartquotable
if TYPE_CHECKING:
from sphinx.application import Sphinx
from sphinx.domain.std import StandardDomain
from sphinx.domains.std import StandardDomain
from sphinx.environment import BuildEnvironment
@ -163,7 +163,7 @@ class AutoNumbering(SphinxTransform):
default_priority = 210
def apply(self, **kwargs: Any) -> None:
domain: StandardDomain = self.env.get_domain('std')
domain: StandardDomain = self.env.domains['std']
for node in self.document.findall(nodes.Element):
if (domain.is_enumerable_node(node) and

View File

@ -300,7 +300,7 @@ class _NodeUpdater:
__('inconsistent term references in translated message.' +
' original: {0}, translated: {1}'))
xref_reftarget_map = {}
xref_reftarget_map: dict[tuple[str, str, str] | None, dict[str, Any]] = {}
def get_ref_key(node: addnodes.pending_xref) -> tuple[str, str, str] | None:
case = node["refdomain"], node["reftype"]
@ -393,10 +393,10 @@ class Locale(SphinxTransform):
for _id in node['ids']:
parts = split_term_classifiers(msgstr)
patch = publish_msgstr(
self.app, parts[0], source, node.line, self.config, settings,
self.app, parts[0] or '', source, node.line, self.config, settings,
)
updater.patch = make_glossary_term(
self.env, patch, parts[1], source, node.line, _id, self.document,
self.env, patch, parts[1] or '', source, node.line, _id, self.document,
)
processed = True
@ -497,7 +497,7 @@ class Locale(SphinxTransform):
if 'index' in self.config.gettext_additional_targets:
# Extract and translate messages for index entries.
for node, entries in traverse_translatable_index(self.document):
new_entries: list[tuple[str, str, str, str, str]] = []
new_entries: list[tuple[str, str, str, str, str | None]] = []
for type, msg, tid, main, _key in entries:
msg_parts = split_index_msg(type, msg)
msgstr_parts = []

View File

@ -117,6 +117,7 @@ class DataURIExtractor(BaseImageConverter):
def handle(self, node: nodes.image) -> None:
image = parse_data_uri(node['uri'])
assert image is not None
ext = get_image_extension(image.mimetype)
if ext is None:
logger.warning(__('Unknown image format: %s...'), node['uri'][:32],
@ -140,7 +141,7 @@ class DataURIExtractor(BaseImageConverter):
def get_filename_for(filename: str, mimetype: str) -> str:
basename = os.path.basename(filename)
basename = re.sub(CRITICAL_PATH_CHAR_RE, "_", basename)
return os.path.splitext(basename)[0] + get_image_extension(mimetype)
return os.path.splitext(basename)[0] + (get_image_extension(mimetype) or '')
class ImageConverter(BaseImageConverter):
@ -202,8 +203,12 @@ class ImageConverter(BaseImageConverter):
if not self.available:
return False
else:
rule = self.get_conversion_rule(node)
return bool(rule)
try:
self.get_conversion_rule(node)
except ValueError:
return False
else:
return True
def get_conversion_rule(self, node: nodes.image) -> tuple[str, str]:
for candidate in self.guess_mimetypes(node):
@ -212,7 +217,7 @@ class ImageConverter(BaseImageConverter):
if rule in self.conversion_rules:
return rule
return None
raise ValueError('No conversion rule found')
def is_available(self) -> bool:
"""Return the image converter is available or not."""
@ -222,7 +227,8 @@ class ImageConverter(BaseImageConverter):
if '?' in node['candidates']:
return []
elif '*' in node['candidates']:
return [guess_mimetype(node['uri'])]
guessed = guess_mimetype(node['uri'])
return [guessed] if guessed is not None else []
else:
return node['candidates'].keys()

View File

@ -94,7 +94,8 @@ class ASTBaseBase:
return False
return True
__hash__: Callable[[], int] = None
# Defining __hash__ = None is not strictly needed when __eq__ is defined.
__hash__ = None # type: ignore[assignment]
def clone(self) -> Any:
return deepcopy(self)
@ -346,8 +347,7 @@ class BaseParser:
def matched_text(self) -> str:
if self.last_match is not None:
return self.last_match.group()
else:
return None
return ''
def read_rest(self) -> str:
rv = self.definition[self.pos:]

View File

@ -5,16 +5,18 @@ be domain-specifically transformed to a more appealing presentation.
"""
from __future__ import annotations
import contextlib
from typing import TYPE_CHECKING, Any, List, Tuple, cast
from docutils import nodes
from docutils.nodes import Node
from docutils.nodes import Element, Node
from docutils.parsers.rst.states import Inliner
from sphinx import addnodes
from sphinx.environment import BuildEnvironment
from sphinx.locale import __
from sphinx.util import logging
from sphinx.util.nodes import get_node_line
from sphinx.util.typing import TextlikeNode
if TYPE_CHECKING:
@ -52,8 +54,15 @@ class Field:
is_grouped = False
is_typed = False
def __init__(self, name: str, names: tuple[str, ...] = (), label: str = None,
has_arg: bool = True, rolename: str = None, bodyrolename: str = None) -> None:
def __init__(
self,
name: str,
names: tuple[str, ...] = (),
label: str = '',
has_arg: bool = True,
rolename: str = '',
bodyrolename: str = '',
) -> None:
self.name = name
self.names = names
self.label = label
@ -63,8 +72,8 @@ class Field:
def make_xref(self, rolename: str, domain: str, target: str,
innernode: type[TextlikeNode] = addnodes.literal_emphasis,
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> Node:
contnode: Node | None = None, env: BuildEnvironment | None = None,
inliner: Inliner | None = None, location: Element | None = None) -> Node:
# note: for backwards compatibility env is last, but not optional
assert env is not None
assert (inliner is None) == (location is None), (inliner, location)
@ -83,23 +92,33 @@ class Field:
refnode += contnode or innernode(target, target)
env.get_domain(domain).process_field_xref(refnode)
return refnode
lineno = logging.get_source_line(location)[1]
lineno = -1
if location is not None:
with contextlib.suppress(ValueError):
lineno = get_node_line(location)
ns, messages = role(rolename, target, target, lineno, inliner, {}, [])
return nodes.inline(target, '', *ns)
def make_xrefs(self, rolename: str, domain: str, target: str,
innernode: type[TextlikeNode] = addnodes.literal_emphasis,
contnode: Node = None, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> list[Node]:
contnode: Node | None = None, env: BuildEnvironment | None = None,
inliner: Inliner | None = None, location: Element | None = None,
) -> list[Node]:
return [self.make_xref(rolename, domain, target, innernode, contnode,
env, inliner, location)]
def make_entry(self, fieldarg: str, content: list[Node]) -> tuple[str, list[Node]]:
return (fieldarg, content)
def make_field(self, types: dict[str, list[Node]], domain: str,
item: tuple, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> nodes.field:
def make_field(
self,
types: dict[str, list[Node]],
domain: str,
item: tuple,
env: BuildEnvironment | None = None,
inliner: Inliner | None = None,
location: Element | None = None,
) -> nodes.field:
fieldarg, content = item
fieldname = nodes.field_name('', self.label)
if fieldarg:
@ -135,14 +154,20 @@ class GroupedField(Field):
is_grouped = True
list_type = nodes.bullet_list
def __init__(self, name: str, names: tuple[str, ...] = (), label: str = None,
rolename: str = None, can_collapse: bool = False) -> None:
def __init__(self, name: str, names: tuple[str, ...] = (), label: str = '',
rolename: str = '', can_collapse: bool = False) -> None:
super().__init__(name, names, label, True, rolename)
self.can_collapse = can_collapse
def make_field(self, types: dict[str, list[Node]], domain: str,
items: tuple, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> nodes.field:
def make_field(
self,
types: dict[str, list[Node]],
domain: str,
items: tuple,
env: BuildEnvironment | None = None,
inliner: Inliner | None = None,
location: Element | None = None,
) -> nodes.field:
fieldname = nodes.field_name('', self.label)
listnode = self.list_type()
for fieldarg, content in items:
@ -184,16 +209,29 @@ class TypedField(GroupedField):
"""
is_typed = True
def __init__(self, name: str, names: tuple[str, ...] = (), typenames: tuple[str, ...] = (),
label: str = None, rolename: str = None, typerolename: str = None,
can_collapse: bool = False) -> None:
def __init__(
self,
name: str,
names: tuple[str, ...] = (),
typenames: tuple[str, ...] = (),
label: str = '',
rolename: str = '',
typerolename: str = '',
can_collapse: bool = False,
) -> None:
super().__init__(name, names, label, rolename, can_collapse)
self.typenames = typenames
self.typerolename = typerolename
def make_field(self, types: dict[str, list[Node]], domain: str,
items: tuple, env: BuildEnvironment = None,
inliner: Inliner = None, location: Node = None) -> nodes.field:
def make_field(
self,
types: dict[str, list[Node]],
domain: str,
items: tuple,
env: BuildEnvironment | None = None,
inliner: Inliner | None = None,
location: Element | None = None,
) -> nodes.field:
def handle_item(fieldarg: str, content: str) -> nodes.paragraph:
par = nodes.paragraph()
par.extend(self.make_xrefs(self.rolename, domain, fieldarg,
@ -251,7 +289,7 @@ class DocFieldTransformer:
"""Transform a single field list *node*."""
typemap = self.typemap
entries: list[nodes.field | tuple[Field, Any, Node]] = []
entries: list[nodes.field | tuple[Field, Any, Element]] = []
groupindices: dict[str, int] = {}
types: dict[str, dict] = {}
@ -292,7 +330,7 @@ class DocFieldTransformer:
target = content[0].astext()
xrefs = typed_field.make_xrefs(
typed_field.typerolename,
self.directive.domain,
self.directive.domain or '',
target,
contnode=content[0],
env=self.directive.state.document.settings.env,
@ -362,7 +400,8 @@ class DocFieldTransformer:
fieldtypes = types.get(fieldtype.name, {})
env = self.directive.state.document.settings.env
inliner = self.directive.state.inliner
new_list += fieldtype.make_field(fieldtypes, self.directive.domain, items,
domain = self.directive.domain or ''
new_list += fieldtype.make_field(fieldtypes, domain, items,
env=env, inliner=inliner, location=location)
node.replace_self(new_list)

View File

@ -143,7 +143,7 @@ def patched_get_language() -> Generator[None, None, None]:
"""
from docutils.languages import get_language
def patched_get_language(language_code: str, reporter: Reporter = None) -> Any:
def patched_get_language(language_code: str, reporter: Reporter | None = None) -> Any:
return get_language(language_code)
try:
@ -167,7 +167,7 @@ def patched_rst_get_language() -> Generator[None, None, None]:
"""
from docutils.parsers.rst.languages import get_language
def patched_get_language(language_code: str, reporter: Reporter = None) -> Any:
def patched_get_language(language_code: str, reporter: Reporter | None = None) -> Any:
return get_language(language_code)
try:
@ -378,7 +378,7 @@ def switch_source_input(state: State, content: StringList) -> Generator[None, No
get_source_and_line = state.memo.reporter.get_source_and_line # type: ignore
# replace it by new one
state_machine = StateMachine([], None)
state_machine = StateMachine([], None) # type: ignore[arg-type]
state_machine.input_lines = content
state.memo.reporter.get_source_and_line = state_machine.get_source_and_line # type: ignore # noqa: E501
@ -492,12 +492,12 @@ class SphinxRole:
"""Reference to the :class:`.Config` object."""
return self.env.config
def get_source_info(self, lineno: int = None) -> tuple[str, int]:
def get_source_info(self, lineno: int | None = None) -> tuple[str, int]:
if lineno is None:
lineno = self.lineno
return self.inliner.reporter.get_source_and_line(lineno) # type: ignore
def set_source_info(self, node: Node, lineno: int = None) -> None:
def set_source_info(self, node: Node, lineno: int | None = None) -> None:
node.source, node.line = self.get_source_info(lineno)
def get_location(self) -> str:

View File

@ -4,7 +4,7 @@ from __future__ import annotations
import base64
from os import path
from typing import TYPE_CHECKING, NamedTuple
from typing import TYPE_CHECKING, NamedTuple, overload
import imagesize
@ -51,6 +51,16 @@ def get_image_size(filename: str) -> tuple[int, int] | None:
return None
@overload
def guess_mimetype(filename: PathLike[str] | str, default: str) -> str:
...
@overload
def guess_mimetype(filename: PathLike[str] | str, default: None = None) -> str | None:
...
def guess_mimetype(
filename: PathLike[str] | str = '',
default: str | None = None,

View File

@ -19,7 +19,6 @@ from sphinx.util import logging
if TYPE_CHECKING:
from sphinx.builders import Builder
from sphinx.domain import IndexEntry
from sphinx.environment import BuildEnvironment
from sphinx.util.tags import Tags
@ -300,7 +299,7 @@ def get_prev_node(node: Node) -> Node | None:
def traverse_translatable_index(
doctree: Element,
) -> Iterable[tuple[Element, list[IndexEntry]]]:
) -> Iterable[tuple[Element, list[tuple[str, str, str, str, str | None]]]]:
"""Traverse translatable index node from a document tree."""
matcher = NodeMatcher(addnodes.index, inline=False)
for node in doctree.findall(matcher): # type: addnodes.index

View File

@ -38,6 +38,9 @@ class Cell:
def __hash__(self) -> int:
return hash((self.col, self.row))
def __bool__(self) -> bool:
return self.text != '' and self.col is not None and self.row is not None
def wrap(self, width: int) -> None:
self.wrapped = my_wrap(self.text, width)
@ -88,7 +91,7 @@ class Table:
+--------+--------+
"""
def __init__(self, colwidth: list[int] = None) -> None:
def __init__(self, colwidth: list[int] | None = None) -> None:
self.lines: list[list[Cell]] = []
self.separator = 0
self.colwidth: list[int] = (colwidth if colwidth is not None else [])
@ -140,7 +143,7 @@ class Table:
def _ensure_has_column(self, col: int) -> None:
for line in self.lines:
while len(line) < col:
line.append(None)
line.append(Cell())
def __repr__(self) -> str:
return "\n".join(repr(line) for line in self.lines)
@ -150,6 +153,8 @@ class Table:
``self.colwidth`` or ``self.measured_widths``).
This takes into account cells spanning multiple columns.
"""
if cell.row is None or cell.col is None:
raise ValueError('Cell co-ordinates have not been set')
width = 0
for i in range(self[cell.row, cell.col].colspan):
width += source[cell.col + i]
@ -173,6 +178,8 @@ class Table:
cell.wrap(width=self.cell_width(cell, self.colwidth))
if not cell.wrapped:
continue
if cell.row is None or cell.col is None:
raise ValueError('Cell co-ordinates have not been set')
width = math.ceil(max(column_width(x) for x in cell.wrapped) / cell.colspan)
for col in range(cell.col, cell.col + cell.colspan):
self.measured_widths[col] = max(self.measured_widths[col], width)
@ -358,7 +365,7 @@ class TextWriter(writers.Writer):
settings_spec = ('No options here.', '', ())
settings_defaults: dict[str, Any] = {}
output: str = None
output: str
def __init__(self, builder: TextBuilder) -> None:
super().__init__()
@ -391,7 +398,7 @@ class TextTranslator(SphinxTranslator):
self.list_counter: list[int] = []
self.sectionlevel = 0
self.lineblocklevel = 0
self.table: Table = None
self.table: Table
def add_text(self, text: str) -> None:
self.states[-1].append((-1, text))
@ -400,7 +407,9 @@ class TextTranslator(SphinxTranslator):
self.states.append([])
self.stateindent.append(indent)
def end_state(self, wrap: bool = True, end: list[str] = [''], first: str = None) -> None:
def end_state(
self, wrap: bool = True, end: list[str] | None = [''], first: str | None = None,
) -> None:
content = self.states.pop()
maxindent = sum(self.stateindent)
indent = self.stateindent.pop()
@ -843,17 +852,17 @@ class TextTranslator(SphinxTranslator):
self.stateindent.pop()
self.entry.text = text
self.table.add_cell(self.entry)
self.entry = None
del self.entry
def visit_table(self, node: Element) -> None:
if self.table:
if hasattr(self, 'table'):
raise NotImplementedError('Nested tables are not supported.')
self.new_state(0)
self.table = Table()
def depart_table(self, node: Element) -> None:
self.add_text(str(self.table))
self.table = None
del self.table
self.end_state(wrap=False)
def visit_acks(self, node: Element) -> None:

View File

@ -10,6 +10,8 @@ from sphinx.builders import Builder
class XMLWriter(BaseXMLWriter):
output: str
def __init__(self, builder: Builder) -> None:
super().__init__()
self.builder = builder
@ -34,7 +36,7 @@ class PseudoXMLWriter(BaseXMLWriter):
config_section = 'pseudoxml writer'
config_section_dependencies = ('writers',)
output = None
output: str
"""Final translated form of `document`."""
def __init__(self, builder: Builder) -> None:

View File

@ -125,13 +125,12 @@ def test_reporting_with_autodoc(app, status, warning, capfd):
written = []
app.builder._warn_out = written.append
app.builder.build_all()
lines = '\n'.join(written).replace(os.sep, '/').split('\n')
failures = [l for l in lines if l.startswith('File')]
expected = [
'File "dir/inner.rst", line 1, in default',
'File "dir/bar.py", line ?, in default',
'File "foo.py", line ?, in default',
'File "index.rst", line 4, in default',
]
for location in expected:
assert location in failures
failures = [line.replace(os.sep, '/')
for line in '\n'.join(written).splitlines()
if line.startswith('File')]
assert 'File "dir/inner.rst", line 1, in default' in failures
assert 'File "dir/bar.py", line ?, in default' in failures
assert 'File "foo.py", line ?, in default' in failures
assert 'File "index.rst", line 4, in default' in failures