Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/sphinx-doc/sphinx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Turner <9087854+AA-Turner@users.noreply.github.com>2022-06-16 21:50:01 +0300
committerGitHub <noreply@github.com>2022-06-16 21:50:01 +0300
commit6ef22d261303ec07890305b300135c34952ca327 (patch)
treec126f78d478bdc289f3f796fcd22ccfdcd09a715
parent881f66c5573cec1b3333868effb10cec5c62c7b4 (diff)
Increase static typing strictness (#10530)
-rw-r--r--setup.cfg2
-rw-r--r--sphinx/application.py64
-rw-r--r--sphinx/builders/__init__.py16
-rw-r--r--sphinx/builders/html/__init__.py18
-rw-r--r--sphinx/cmd/quickstart.py5
-rw-r--r--sphinx/registry.py18
-rw-r--r--sphinx/util/console.py5
-rw-r--r--sphinx/util/logging.py4
-rw-r--r--sphinx/util/parallel.py8
-rw-r--r--tests/test_application.py33
-rw-r--r--tests/test_environment.py6
-rw-r--r--tests/test_environment_toctree.py2
-rw-r--r--tests/test_markup.py3
13 files changed, 141 insertions, 43 deletions
diff --git a/setup.cfg b/setup.cfg
index bc8f14998..b0bfa28d3 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -26,11 +26,13 @@ python_version = 3.6
disallow_incomplete_defs = True
show_column_numbers = True
show_error_context = True
+show_error_codes = true
ignore_missing_imports = True
follow_imports = skip
check_untyped_defs = True
warn_unused_ignores = True
strict_optional = False
+no_implicit_optional = True
[tool:pytest]
filterwarnings =
diff --git a/sphinx/application.py b/sphinx/application.py
index f6eff7adc..08c4af5ba 100644
--- a/sphinx/application.py
+++ b/sphinx/application.py
@@ -135,9 +135,6 @@ class Sphinx:
self.phase = BuildPhase.INITIALIZATION
self.verbosity = verbosity
self.extensions: Dict[str, Extension] = {}
- self.builder: Optional[Builder] = None
- self.env: Optional[BuildEnvironment] = None
- self.project: Optional[Project] = None
self.registry = SphinxComponentRegistry()
# validate provided directories
@@ -248,10 +245,16 @@ class Sphinx:
# create the project
self.project = Project(self.srcdir, self.config.source_suffix)
+
+ # set up the build environment
+ self.env = self._init_env(freshenv)
+
# create the builder
self.builder = self.create_builder(buildername)
- # set up the build environment
- self._init_env(freshenv)
+
+ # build environment post-initialisation, after creating the builder
+ self._post_init_env()
+
# set up the builder
self._init_builder()
@@ -283,20 +286,34 @@ class Sphinx:
else:
logger.info(__('not available for built-in messages'))
- def _init_env(self, freshenv: bool) -> None:
+ def _init_env(self, freshenv: bool) -> BuildEnvironment:
filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME)
if freshenv or not os.path.exists(filename):
- self.env = BuildEnvironment(self)
- self.env.find_files(self.config, self.builder)
+ return self._create_fresh_env()
else:
- try:
- with progress_message(__('loading pickled environment')):
- with open(filename, 'rb') as f:
- self.env = pickle.load(f)
- self.env.setup(self)
- except Exception as err:
- logger.info(__('failed: %s'), err)
- self._init_env(freshenv=True)
+ return self._load_existing_env(filename)
+
+ def _create_fresh_env(self) -> BuildEnvironment:
+ env = BuildEnvironment(self)
+ self._fresh_env_used = True
+ return env
+
+ def _load_existing_env(self, filename: str) -> BuildEnvironment:
+ try:
+ with progress_message(__('loading pickled environment')):
+ with open(filename, 'rb') as f:
+ env = pickle.load(f)
+ env.setup(self)
+ self._fresh_env_used = False
+ except Exception as err:
+ logger.info(__('failed: %s'), err)
+ env = self._create_fresh_env()
+ return env
+
+ def _post_init_env(self) -> None:
+ if self._fresh_env_used:
+ self.env.find_files(self.config, self.builder)
+ del self._fresh_env_used
def preload_builder(self, name: str) -> None:
self.registry.preload_builder(self, name)
@@ -306,10 +323,11 @@ class Sphinx:
logger.info(__('No builder selected, using default: html'))
name = 'html'
- return self.registry.create_builder(self, name)
+ return self.registry.create_builder(self, name, self.env)
def _init_builder(self) -> None:
- self.builder.set_environment(self.env)
+ if not hasattr(self.builder, "env"):
+ self.builder.set_environment(self.env)
self.builder.init()
self.events.emit('builder-inited')
@@ -986,8 +1004,9 @@ class Sphinx:
kwargs['defer'] = 'defer'
self.registry.add_js_file(filename, priority=priority, **kwargs)
- if hasattr(self.builder, 'add_js_file'):
- self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore
+ if hasattr(self, 'builder') and hasattr(self.builder, 'add_js_file'):
+ self.builder.add_js_file(filename, # type: ignore[attr-defined]
+ priority=priority, **kwargs)
def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None:
"""Register a stylesheet to include in the HTML output.
@@ -1047,8 +1066,9 @@ class Sphinx:
"""
logger.debug('[app] adding stylesheet: %r', filename)
self.registry.add_css_files(filename, priority=priority, **kwargs)
- if hasattr(self.builder, 'add_css_file'):
- self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore
+ if hasattr(self, 'builder') and hasattr(self.builder, 'add_css_file'):
+ self.builder.add_css_file(filename, # type: ignore[attr-defined]
+ priority=priority, **kwargs)
def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None
) -> None:
diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py
index d8500e11b..9705ba894 100644
--- a/sphinx/builders/__init__.py
+++ b/sphinx/builders/__init__.py
@@ -3,6 +3,7 @@
import codecs
import pickle
import time
+import warnings
from os import path
from typing import (TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Sequence, Set, Tuple,
Type, Union)
@@ -11,6 +12,7 @@ from docutils import nodes
from docutils.nodes import Node
from sphinx.config import Config
+from sphinx.deprecation import RemovedInSphinx70Warning
from sphinx.environment import CONFIG_CHANGED_REASON, CONFIG_OK, BuildEnvironment
from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.errors import SphinxError
@@ -75,7 +77,7 @@ class Builder:
#: The builder supports data URIs or not.
supported_data_uri_images = False
- def __init__(self, app: "Sphinx") -> None:
+ def __init__(self, app: "Sphinx", env: BuildEnvironment = None) -> None:
self.srcdir = app.srcdir
self.confdir = app.confdir
self.outdir = app.outdir
@@ -83,7 +85,14 @@ class Builder:
ensuredir(self.doctreedir)
self.app: Sphinx = app
- self.env: Optional[BuildEnvironment] = None
+ if env is not None:
+ self.env: BuildEnvironment = env
+ self.env.set_versioning_method(self.versioning_method,
+ self.versioning_compare)
+ elif env is not Ellipsis:
+ # ... is passed by SphinxComponentRegistry.create_builder to not show two warnings.
+ warnings.warn("The 'env' argument to Builder will be required from Sphinx 7.",
+ RemovedInSphinx70Warning, stacklevel=2)
self.events: EventManager = app.events
self.config: Config = app.config
self.tags: Tags = app.tags
@@ -105,6 +114,9 @@ class Builder:
def set_environment(self, env: BuildEnvironment) -> None:
"""Store BuildEnvironment object."""
+ warnings.warn("Builder.set_environment is deprecated, pass env to "
+ "'Builder.__init__()' instead.",
+ RemovedInSphinx70Warning, stacklevel=2)
self.env = env
self.env.set_versioning_method(self.versioning_method,
self.versioning_compare)
diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py
index 9404ae458..e023e8194 100644
--- a/sphinx/builders/html/__init__.py
+++ b/sphinx/builders/html/__init__.py
@@ -26,6 +26,7 @@ from sphinx.builders import Builder
from sphinx.config import ENUM, Config
from sphinx.deprecation import RemovedInSphinx70Warning, deprecated_alias
from sphinx.domains import Domain, Index, IndexEntry
+from sphinx.environment import BuildEnvironment
from sphinx.environment.adapters.asset import ImageAdapter
from sphinx.environment.adapters.indexentries import IndexEntries
from sphinx.environment.adapters.toctree import TocTree
@@ -51,6 +52,17 @@ INVENTORY_FILENAME = 'objects.inv'
logger = logging.getLogger(__name__)
return_codes_re = re.compile('[\r\n]+')
+DOMAIN_INDEX_TYPE = Tuple[
+ # Index name (e.g. py-modindex)
+ str,
+ # Index class
+ Type[Index],
+ # list of (heading string, list of index entries) pairs.
+ List[Tuple[str, List[IndexEntry]]],
+ # whether sub-entries should start collapsed
+ bool
+]
+
def get_stable_hash(obj: Any) -> str:
"""
@@ -197,10 +209,10 @@ class StandaloneHTMLBuilder(Builder):
download_support = True # enable download role
imgpath: str = None
- domain_indices: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] = [] # NOQA
+ domain_indices: List[DOMAIN_INDEX_TYPE] = []
- def __init__(self, app: Sphinx) -> None:
- super().__init__(app)
+ def __init__(self, app: Sphinx, env: BuildEnvironment = None) -> None:
+ super().__init__(app, env)
# CSS files
self.css_files: List[Stylesheet] = []
diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py
index 5e9c2b470..1509ee7fd 100644
--- a/sphinx/cmd/quickstart.py
+++ b/sphinx/cmd/quickstart.py
@@ -7,11 +7,14 @@ import sys
import time
from collections import OrderedDict
from os import path
-from typing import Any, Callable, Dict, List, Union
+from typing import TYPE_CHECKING, Any, Callable, Dict, List, Union
# try to import readline, unix specific enhancement
try:
import readline
+ if TYPE_CHECKING and sys.platform == "win32": # always false, for type checking
+ raise ImportError
+
if readline.__doc__ and 'libedit' in readline.__doc__:
readline.parse_and_bind("bind ^I rl_complete")
USE_LIBEDIT = True
diff --git a/sphinx/registry.py b/sphinx/registry.py
index 6770abb02..da892f91b 100644
--- a/sphinx/registry.py
+++ b/sphinx/registry.py
@@ -22,7 +22,7 @@ except ImportError:
from sphinx.builders import Builder
from sphinx.config import Config
-from sphinx.deprecation import RemovedInSphinx60Warning
+from sphinx.deprecation import RemovedInSphinx60Warning, RemovedInSphinx70Warning
from sphinx.domains import Domain, Index, ObjType
from sphinx.domains.std import GenericObject, Target
from sphinx.environment import BuildEnvironment
@@ -153,11 +153,23 @@ class SphinxComponentRegistry:
self.load_extension(app, entry_point.module)
- def create_builder(self, app: "Sphinx", name: str) -> Builder:
+ def create_builder(self, app: "Sphinx", name: str,
+ env: BuildEnvironment = None) -> Builder:
if name not in self.builders:
raise SphinxError(__('Builder name %s not registered') % name)
- return self.builders[name](app)
+ try:
+ return self.builders[name](app, env)
+ except TypeError:
+ warnings.warn(
+ f"The custom builder {name} defines a custom __init__ method without the "
+ f"'env'argument. Report this bug to the developers of your custom builder, "
+ f"this is likely not a issue with Sphinx. The 'env' argument will be required "
+ f"from Sphinx 7.", RemovedInSphinx70Warning, stacklevel=2)
+ builder = self.builders[name](app, env=...) # type: ignore[arg-type]
+ if env is not None:
+ builder.set_environment(env)
+ return builder
def add_domain(self, domain: Type[Domain], override: bool = False) -> None:
logger.debug('[app] adding domain: %r', domain)
diff --git a/sphinx/util/console.py b/sphinx/util/console.py
index abdbf4219..88b208470 100644
--- a/sphinx/util/console.py
+++ b/sphinx/util/console.py
@@ -23,6 +23,9 @@ def terminal_safe(s: str) -> str:
def get_terminal_width() -> int:
"""Borrowed from the py lib."""
+ if sys.platform == "win32":
+ # For static typing, as fcntl & termios never exist on Windows.
+ return int(os.environ.get('COLUMNS', 80)) - 1
try:
import fcntl
import struct
@@ -32,7 +35,7 @@ def get_terminal_width() -> int:
terminal_width = width
except Exception:
# FALLBACK
- terminal_width = int(os.environ.get('COLUMNS', "80")) - 1
+ terminal_width = int(os.environ.get('COLUMNS', 80)) - 1
return terminal_width
diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py
index 37fa672af..b25b006a6 100644
--- a/sphinx/util/logging.py
+++ b/sphinx/util/logging.py
@@ -381,8 +381,8 @@ class WarningSuppressor(logging.Filter):
super().__init__()
def filter(self, record: logging.LogRecord) -> bool:
- type = getattr(record, 'type', None)
- subtype = getattr(record, 'subtype', None)
+ type = getattr(record, 'type', '')
+ subtype = getattr(record, 'subtype', '')
try:
suppress_warnings = self.app.config.suppress_warnings
diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py
index e4bd852b0..193d2a80d 100644
--- a/sphinx/util/parallel.py
+++ b/sphinx/util/parallel.py
@@ -1,6 +1,7 @@
"""Parallel building utilities."""
import os
+import sys
import time
import traceback
from math import sqrt
@@ -16,6 +17,11 @@ from sphinx.util import logging
logger = logging.getLogger(__name__)
+if sys.platform != "win32":
+ ForkProcess = multiprocessing.context.ForkProcess
+else:
+ # For static typing, as ForkProcess doesn't exist on Windows
+ ForkProcess = multiprocessing.process.BaseProcess
# our parallel functionality only works for the forking Process
parallel_available = multiprocessing and os.name == 'posix'
@@ -49,7 +55,7 @@ class ParallelTasks:
# task arguments
self._args: Dict[int, Optional[List[Any]]] = {}
# list of subprocesses (both started and waiting)
- self._procs: Dict[int, multiprocessing.context.ForkProcess] = {}
+ self._procs: Dict[int, ForkProcess] = {}
# list of receiving pipe connections of running subprocesses
self._precvs: Dict[int, Any] = {}
# list of receiving pipe connections of waiting subprocesses
diff --git a/tests/test_application.py b/tests/test_application.py
index 365fff8ea..90758a939 100644
--- a/tests/test_application.py
+++ b/tests/test_application.py
@@ -1,15 +1,46 @@
"""Test the Sphinx class."""
+import shutil
+import sys
+from io import StringIO
+from pathlib import Path
from unittest.mock import Mock
import pytest
from docutils import nodes
+import sphinx.application
from sphinx.errors import ExtensionError
-from sphinx.testing.util import strip_escseq
+from sphinx.testing.path import path
+from sphinx.testing.util import SphinxTestApp, strip_escseq
from sphinx.util import logging
+def test_instantiation(tmp_path_factory, rootdir: str, monkeypatch):
+ # Given
+ src_dir = tmp_path_factory.getbasetemp() / 'root'
+
+ # special support for sphinx/tests
+ if rootdir and not src_dir.exists():
+ shutil.copytree(Path(str(rootdir)) / 'test-root', src_dir)
+
+ monkeypatch.setattr('sphinx.application.abspath', lambda x: x)
+
+ syspath = sys.path[:]
+
+ # When
+ app_ = SphinxTestApp(
+ srcdir=path(src_dir),
+ status=StringIO(),
+ warning=StringIO()
+ )
+ sys.path[:] = syspath
+ app_.cleanup()
+
+ # Then
+ assert isinstance(app_, sphinx.application.Sphinx)
+
+
def test_events(app, status, warning):
def empty():
pass
diff --git a/tests/test_environment.py b/tests/test_environment.py
index 7ffca7898..c6f6b5aba 100644
--- a/tests/test_environment.py
+++ b/tests/test_environment.py
@@ -49,8 +49,7 @@ def test_images(app):
app.build()
tree = app.env.get_doctree('images')
- htmlbuilder = StandaloneHTMLBuilder(app)
- htmlbuilder.set_environment(app.env)
+ htmlbuilder = StandaloneHTMLBuilder(app, app.env)
htmlbuilder.init()
htmlbuilder.imgpath = 'dummy'
htmlbuilder.post_process_images(tree)
@@ -59,8 +58,7 @@ def test_images(app):
assert set(htmlbuilder.images.values()) == \
{'img.png', 'img1.png', 'simg.png', 'svgimg.svg', 'img.foo.png'}
- latexbuilder = LaTeXBuilder(app)
- latexbuilder.set_environment(app.env)
+ latexbuilder = LaTeXBuilder(app, app.env)
latexbuilder.init()
latexbuilder.post_process_images(tree)
assert set(latexbuilder.images.keys()) == \
diff --git a/tests/test_environment_toctree.py b/tests/test_environment_toctree.py
index 588bcac18..60a9826fd 100644
--- a/tests/test_environment_toctree.py
+++ b/tests/test_environment_toctree.py
@@ -156,7 +156,7 @@ def test_get_toc_for(app):
@pytest.mark.test_params(shared_result='test_environment_toctree_basic')
def test_get_toc_for_only(app):
app.build()
- builder = StandaloneHTMLBuilder(app)
+ builder = StandaloneHTMLBuilder(app, app.env)
toctree = TocTree(app.env).get_toc_for('index', builder)
assert_node(toctree,
diff --git a/tests/test_markup.py b/tests/test_markup.py
index 9e6165a5f..f15761c5e 100644
--- a/tests/test_markup.py
+++ b/tests/test_markup.py
@@ -106,8 +106,7 @@ def verify_re_html(app, parse):
def verify_re_latex(app, parse):
def verify(rst, latex_expected):
document = parse(rst)
- app.builder = LaTeXBuilder(app)
- app.builder.set_environment(app.env)
+ app.builder = LaTeXBuilder(app, app.env)
app.builder.init()
theme = app.builder.themes.get('manual')
latex_translator = ForgivingLaTeXTranslator(document, app.builder, theme)