diff --git a/pyproject.toml b/pyproject.toml index 9e3f4df91..12701fe7e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,6 +87,7 @@ lint = [ "sphinx-lint", "docutils-stubs", "types-requests", + "pytest>=6.0", ] test = [ "pytest>=6.0", @@ -133,9 +134,6 @@ exclude = [ files = ["sphinx"] check_untyped_defs = true disallow_incomplete_defs = true -follow_imports = "skip" -ignore_missing_imports = true -no_implicit_optional = true python_version = "3.9" show_column_numbers = true show_error_context = true @@ -152,6 +150,9 @@ enable_error_code = [ "ignore-without-code", "unused-awaitable", ] +disable_error_code = [ + "import-untyped", +] [[tool.mypy.overrides]] module = [ diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 9ebb0cfb9..b7668894a 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -133,7 +133,8 @@ class AutosummaryRenderer: if app.translator: self.env.add_extension("jinja2.ext.i18n") - self.env.install_gettext_translations(app.translator) + # ``install_gettext_translations`` is injected by the ``jinja2.ext.i18n`` extension + self.env.install_gettext_translations(app.translator) # type: ignore[attr-defined] def render(self, template_name: str, context: dict) -> str: """Render a template file.""" diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index 652180015..e8c0c942f 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -8,17 +8,12 @@ from typing import TYPE_CHECKING, Any, Callable from jinja2 import BaseLoader, FileSystemLoader, TemplateNotFound from jinja2.sandbox import SandboxedEnvironment -from jinja2.utils import open_if_exists +from jinja2.utils import open_if_exists, pass_context from sphinx.application import TemplateBridge from sphinx.util import logging from sphinx.util.osutil import mtimes_of_files -try: - from jinja2.utils import pass_context -except ImportError: - from jinja2 import contextfunction as pass_context - if TYPE_CHECKING: from collections.abc import Iterator @@ -194,7 +189,9 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader): self.environment.globals['accesskey'] = pass_context(accesskey) self.environment.globals['idgen'] = idgen if use_i18n: - self.environment.install_gettext_translations(builder.app.translator) + # ``install_gettext_translations`` is injected by the ``jinja2.ext.i18n`` extension + self.environment.install_gettext_translations( # type: ignore[attr-defined] + builder.app.translator) def render(self, template: str, context: dict) -> str: # type: ignore[override] return self.environment.get_template(template).render(context) @@ -218,5 +215,6 @@ class BuiltinTemplateLoader(TemplateBridge, BaseLoader): return loader.get_source(environment, template) except TemplateNotFound: pass - msg = f"{template!r} not found in {self.environment.loader.pathchain}" + msg = (f"{template!r} not found in " + f"{self.environment.loader.pathchain}") # type: ignore[union-attr] raise TemplateNotFound(msg) diff --git a/sphinx/registry.py b/sphinx/registry.py index 1ea6b1a01..62d1e3e5c 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -11,7 +11,7 @@ from typing import TYPE_CHECKING, Any, Callable if sys.version_info >= (3, 10): from importlib.metadata import entry_points else: - from importlib_metadata import entry_points + from importlib_metadata import entry_points # type: ignore[import-not-found] from sphinx.domains import Domain, Index, ObjType from sphinx.domains.std import GenericObject, Target diff --git a/sphinx/search/ja.py b/sphinx/search/ja.py index 18aa12f99..5669155cb 100644 --- a/sphinx/search/ja.py +++ b/sphinx/search/ja.py @@ -13,16 +13,16 @@ from __future__ import annotations import os import re import sys -from typing import TYPE_CHECKING, Any, Dict, List +from typing import Any try: - import MeCab + import MeCab # type: ignore[import-not-found] native_module = True except ImportError: native_module = False try: - import janome.tokenizer + import janome.tokenizer # type: ignore[import-not-found] janome_module = True except ImportError: janome_module = False diff --git a/sphinx/search/zh.py b/sphinx/search/zh.py index b3a0e77f7..e40c9a9fe 100644 --- a/sphinx/search/zh.py +++ b/sphinx/search/zh.py @@ -4,14 +4,13 @@ from __future__ import annotations import os import re -from typing import TYPE_CHECKING, Dict, List import snowballstemmer from sphinx.search import SearchLanguage try: - import jieba + import jieba # type: ignore[import-not-found] JIEBA = True except ImportError: JIEBA = False diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index 3f316b664..eaf76e284 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -82,7 +82,7 @@ def app_params(request: Any, test_params: dict, shared_result: SharedResult, if test_params['shared_result']: if 'srcdir' in kwargs: msg = 'You can not specify shared_result and srcdir in same time.' - raise pytest.Exception(msg) + pytest.fail(msg) kwargs['srcdir'] = test_params['shared_result'] restore = shared_result.restore(test_params['shared_result']) kwargs.update(restore) diff --git a/sphinx/theming.py b/sphinx/theming.py index e95f4c8cc..2a6040e5d 100644 --- a/sphinx/theming.py +++ b/sphinx/theming.py @@ -22,7 +22,7 @@ from sphinx.util.osutil import ensuredir if sys.version_info >= (3, 10): from importlib.metadata import entry_points else: - from importlib_metadata import entry_points + from importlib_metadata import entry_points # type: ignore[import-not-found] if TYPE_CHECKING: from sphinx.application import Sphinx diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 004b5d85f..ae512bd36 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -6,7 +6,7 @@ import os import re from datetime import datetime, timezone from os import path -from typing import TYPE_CHECKING, Callable, NamedTuple +from typing import TYPE_CHECKING, NamedTuple import babel.dates from babel.messages.mofile import write_mo @@ -18,10 +18,41 @@ from sphinx.util import logging from sphinx.util.osutil import SEP, canon_path, relpath if TYPE_CHECKING: + import datetime as dt from collections.abc import Generator + from typing import Protocol, Union + + from babel.core import Locale from sphinx.environment import BuildEnvironment + class DateFormatter(Protocol): + def __call__( # NoQA: E704 + self, + date: dt.date | None = ..., + format: str = ..., + locale: str | Locale | None = ..., + ) -> str: ... + + class TimeFormatter(Protocol): + def __call__( # NoQA: E704 + self, + time: dt.time | dt.datetime | float | None = ..., + format: str = ..., + tzinfo: dt.tzinfo | None = ..., + locale: str | Locale | None = ..., + ) -> str: ... + + class DatetimeFormatter(Protocol): + def __call__( # NoQA: E704 + self, + datetime: dt.date | dt.time | float | None = ..., + format: str = ..., + tzinfo: dt.tzinfo | None = ..., + locale: str | Locale | None = ..., + ) -> str: ... + + Formatter = Union[DateFormatter, TimeFormatter, DatetimeFormatter] logger = logging.getLogger(__name__) @@ -169,7 +200,7 @@ date_format_re = re.compile('(%s)' % '|'.join(date_format_mappings)) def babel_format_date(date: datetime, format: str, locale: str, - formatter: Callable = babel.dates.format_date) -> str: + formatter: Formatter = babel.dates.format_date) -> str: # Check if we have the tzinfo attribute. If not we cannot do any time # related formats. if not hasattr(date, 'tzinfo'): @@ -207,6 +238,7 @@ def format_date( # Check if we have to use a different babel formatter then # format_datetime, because we only want to format a date # or a time. + function: Formatter if token == '%x': function = babel.dates.format_date elif token == '%X': diff --git a/sphinx/util/rst.py b/sphinx/util/rst.py index 1e8fd66c7..9f9c3d7cb 100644 --- a/sphinx/util/rst.py +++ b/sphinx/util/rst.py @@ -54,7 +54,8 @@ def textwidth(text: str, widechars: str = 'WF') -> int: def heading(env: Environment, text: str, level: int = 1) -> str: """Create a heading for *level*.""" assert level <= 3 - width = textwidth(text, WIDECHARS[env.language]) + # ``env.language`` is injected by ``sphinx.util.template.ReSTRenderer`` + width = textwidth(text, WIDECHARS[env.language]) # type: ignore[attr-defined] sectioning_char = SECTIONING_CHARS[level - 1] return f'{text}\n{sectioning_char * width}' diff --git a/sphinx/util/tags.py b/sphinx/util/tags.py index 73e1a8308..5d8d8900a 100644 --- a/sphinx/util/tags.py +++ b/sphinx/util/tags.py @@ -20,8 +20,8 @@ class BooleanParser(Parser): Only allow condition exprs and/or/not operations. """ - def parse_compare(self) -> Node: - node: Node + def parse_compare(self) -> nodes.Expr: + node: nodes.Expr token = self.stream.current if token.type == 'name': if token.value in ('true', 'false', 'True', 'False'): @@ -67,7 +67,7 @@ class Tags: msg = 'chunk after expression' raise ValueError(msg) - def eval_node(node: Node) -> bool: + def eval_node(node: Node | None) -> bool: if isinstance(node, nodes.CondExpr): if eval_node(node.test): return eval_node(node.expr1) diff --git a/sphinx/util/template.py b/sphinx/util/template.py index e379a51cc..0ddc29e17 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -26,7 +26,8 @@ class BaseRenderer: def __init__(self, loader: BaseLoader | None = None) -> None: self.env = SandboxedEnvironment(loader=loader, extensions=['jinja2.ext.i18n']) self.env.filters['repr'] = repr - self.env.install_gettext_translations(get_translator()) + # ``install_gettext_translations`` is injected by the ``jinja2.ext.i18n`` extension + self.env.install_gettext_translations(get_translator()) # type: ignore[attr-defined] def render(self, template_name: str, context: dict[str, Any]) -> str: return self.env.get_template(template_name).render(context) diff --git a/sphinx/versioning.py b/sphinx/versioning.py index 042b97d89..ab1701537 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: from sphinx.application import Sphinx try: - import Levenshtein + import Levenshtein # type: ignore[import-not-found] IS_SPEEDUP = True except ImportError: IS_SPEEDUP = False diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c55a211d5..5a4778e48 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -29,7 +29,7 @@ try: from docutils.utils.roman import toRoman except ImportError: # In Debian/Ubuntu, roman package is provided as roman, not as docutils.utils.roman - from roman import toRoman # type: ignore[no-redef] + from roman import toRoman # type: ignore[no-redef, import-not-found] if TYPE_CHECKING: from docutils.nodes import Element, Node, Text