From ae759f40502a0c3f8bdb8484291c542eacd4f63c Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 5 Jun 2022 17:45:07 +0100 Subject: [PATCH 01/19] Assert `readline` setup isn't Windows for static typing --- sphinx/cmd/quickstart.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index 5e9c2b470ed..1509ee7fd92 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -7,11 +7,14 @@ 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 From f65241a706a99352735ea72d4a35e37ab44e600f Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 5 Jun 2022 17:49:58 +0100 Subject: [PATCH 02/19] Add Windows check to get_terminal_width --- sphinx/util/console.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/sphinx/util/console.py b/sphinx/util/console.py index abdbf4219fc..88b208470ce 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 From 3449e9bb1145d03396d76a898c6ab0babdf160a1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 5 Jun 2022 17:56:29 +0100 Subject: [PATCH 03/19] Define ForkProcess on Windows --- sphinx/util/parallel.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index e4bd852b05d..81d24a77bab 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 @@ 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 @@ def __init__(self, nproc: int) -> None: # 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 From 8834ab741ad97785cc90bcf903137226d03d3d82 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 5 Jun 2022 18:44:01 +0100 Subject: [PATCH 04/19] Fix default type for getattr calls --- sphinx/util/logging.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 37fa672afbf..b25b006a6ca 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -381,8 +381,8 @@ def __init__(self, app: "Sphinx") -> None: 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 From e98bac9a5e72ebf520592149542cd4b10b2151dc Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 5 Jun 2022 18:56:28 +0100 Subject: [PATCH 05/19] Make Sphinx.(builder|env|project) non None --- sphinx/application.py | 39 ++++++++++++++++++++++----------------- tests/test_application.py | 33 ++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 18 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index ccd4bf3ac7e..04988a55dfe 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -135,9 +135,6 @@ def __init__(self, srcdir: str, confdir: Optional[str], outdir: str, doctreedir: 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 @@ -251,7 +248,7 @@ def __init__(self, srcdir: str, confdir: Optional[str], outdir: str, doctreedir: # create the builder self.builder = self.create_builder(buildername) # set up the build environment - self._init_env(freshenv) + self.env = self._init_env(freshenv) # set up the builder self._init_builder() @@ -283,20 +280,28 @@ def _init_i18n(self) -> None: 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) + env.find_files(self.config, self.builder) + return env + + def _load_existing_env(self, filename) -> BuildEnvironment: + try: + with progress_message(__('loading pickled environment')): + with open(filename, 'rb') as f: + env = pickle.load(f) + env.setup(self) + except Exception as err: + logger.info(__('failed: %s'), err) + env = self._create_fresh_env() + return env def preload_builder(self, name: str) -> None: self.registry.preload_builder(self, name) @@ -986,7 +991,7 @@ def add_js_file(self, filename: str, priority: int = 500, kwargs['defer'] = 'defer' self.registry.add_js_file(filename, priority=priority, **kwargs) - if hasattr(self.builder, 'add_js_file'): + if hasattr(self, 'builder') and hasattr(self.builder, 'add_js_file'): self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: @@ -1047,7 +1052,7 @@ def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> Non """ logger.debug('[app] adding stylesheet: %r', filename) self.registry.add_css_files(filename, priority=priority, **kwargs) - if hasattr(self.builder, 'add_css_file'): + if hasattr(self, 'builder') and hasattr(self.builder, 'add_css_file'): self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None diff --git a/tests/test_application.py b/tests/test_application.py index 365fff8ea55..90758a939c5 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 From 6616779eefc7a159fb14a8cc5e58800640ad1cf3 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Sun, 5 Jun 2022 19:22:19 +0100 Subject: [PATCH 06/19] Make Builder.env non None --- sphinx/application.py | 23 +++++++++++++++++------ sphinx/builders/__init__.py | 15 +++++++++++++-- sphinx/builders/html/__init__.py | 5 +++-- sphinx/registry.py | 4 ++-- tests/test_environment.py | 6 ++---- tests/test_environment_toctree.py | 2 +- tests/test_markup.py | 3 +-- 7 files changed, 39 insertions(+), 19 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 04988a55dfe..e68e009b1b8 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -245,10 +245,16 @@ def __init__(self, srcdir: str, confdir: Optional[str], outdir: str, doctreedir: # create the project self.project = Project(self.srcdir, self.config.source_suffix) - # create the builder - self.builder = self.create_builder(buildername) + # set up the build environment self.env = self._init_env(freshenv) + + # create the builder + self.builder = self.create_builder(buildername) + + # build environment post-initialisation, after creating the builder + self._post_init_env() + # set up the builder self._init_builder() @@ -289,20 +295,26 @@ def _init_env(self, freshenv: bool) -> BuildEnvironment: def _create_fresh_env(self) -> BuildEnvironment: env = BuildEnvironment(self) - env.find_files(self.config, self.builder) + env._fresh_env_used = True # type: ignore return env - def _load_existing_env(self, filename) -> BuildEnvironment: + 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) + env._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.env._fresh_env_used: # type: ignore # NoQA + self.env.find_files(self.config, self.builder) + del self.env._fresh_env_used # type: ignore # NoQA + def preload_builder(self, name: str) -> None: self.registry.preload_builder(self, name) @@ -311,10 +323,9 @@ def create_builder(self, name: str) -> "Builder": 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) self.builder.init() self.events.emit('builder-inited') diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index d8500e11b42..45bac412158 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.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,13 @@ def __init__(self, app: "Sphinx") -> None: 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) + else: + 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 +113,9 @@ def __init__(self, app: "Sphinx") -> None: 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 b320b0df588..491be8faabd 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -26,6 +26,7 @@ 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 @@ -199,8 +200,8 @@ class StandaloneHTMLBuilder(Builder): imgpath: str = None domain_indices: List[Tuple[str, Type[Index], List[Tuple[str, List[IndexEntry]]], bool]] = [] # NOQA - 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/registry.py b/sphinx/registry.py index 6770abb02a2..88c537a5818 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -153,11 +153,11 @@ def preload_builder(self, app: "Sphinx", name: str) -> None: 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) -> Builder: if name not in self.builders: raise SphinxError(__('Builder name %s not registered') % name) - return self.builders[name](app) + return self.builders[name](app, env) def add_domain(self, domain: Type[Domain], override: bool = False) -> None: logger.debug('[app] adding domain: %r', domain) diff --git a/tests/test_environment.py b/tests/test_environment.py index 7ffca7898e0..c6f6b5aba52 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 588bcac18be..60a9826fda6 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 9e6165a5f84..f15761c5e5d 100644 --- a/tests/test_markup.py +++ b/tests/test_markup.py @@ -106,8 +106,7 @@ def verify(rst, html_expected): 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) From 95105f10b17f3590339f75b55b5c267b19aa3e53 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:02:57 +0100 Subject: [PATCH 07/19] Use ForkProcess without quoting --- sphinx/util/parallel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/util/parallel.py b/sphinx/util/parallel.py index 81d24a77bab..193d2a80dcd 100644 --- a/sphinx/util/parallel.py +++ b/sphinx/util/parallel.py @@ -55,7 +55,7 @@ def __init__(self, nproc: int) -> None: # task arguments self._args: Dict[int, Optional[List[Any]]] = {} # list of subprocesses (both started and waiting) - self._procs: Dict[int, "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 From d62b5767ac62bf9270b9d203a666011baf7546bb Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:07:35 +0100 Subject: [PATCH 08/19] Backwards compat for `create_builder` --- sphinx/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/registry.py b/sphinx/registry.py index 88c537a5818..b4aeedc3f6e 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -153,7 +153,7 @@ def preload_builder(self, app: "Sphinx", name: str) -> None: self.load_extension(app, entry_point.module) - def create_builder(self, app: "Sphinx", name: str, env: BuildEnvironment) -> 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) From 59dc8d15a9b1af08edcac758d333826a273900da Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:09:16 +0100 Subject: [PATCH 09/19] Move `_fresh_env_used` to the `Sphinx` object. --- sphinx/application.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index e68e009b1b8..46af51971fb 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -295,7 +295,7 @@ def _init_env(self, freshenv: bool) -> BuildEnvironment: def _create_fresh_env(self) -> BuildEnvironment: env = BuildEnvironment(self) - env._fresh_env_used = True # type: ignore + self._fresh_env_used = True return env def _load_existing_env(self, filename: str) -> BuildEnvironment: @@ -304,16 +304,16 @@ def _load_existing_env(self, filename: str) -> BuildEnvironment: with open(filename, 'rb') as f: env = pickle.load(f) env.setup(self) - env._fresh_env_used = False + 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.env._fresh_env_used: # type: ignore # NoQA + if self._fresh_env_used: self.env.find_files(self.config, self.builder) - del self.env._fresh_env_used # type: ignore # NoQA + del self._fresh_env_used def preload_builder(self, name: str) -> None: self.registry.preload_builder(self, name) From 35542cd21b2fac2655c2b342b0bcb4923ebf6ae1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:13:26 +0100 Subject: [PATCH 10/19] Line length --- sphinx/registry.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/registry.py b/sphinx/registry.py index b4aeedc3f6e..3a1bde6bb16 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -153,7 +153,8 @@ def preload_builder(self, app: "Sphinx", name: str) -> None: self.load_extension(app, entry_point.module) - def create_builder(self, app: "Sphinx", name: str, env: BuildEnvironment = None) -> 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) From c373bd059dbfd92cff6c2f3c8c8499aa3e797863 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 16:59:05 +0100 Subject: [PATCH 11/19] Use a runtime type alias --- sphinx/builders/html/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 491be8faabd..d8c0f05280b 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -52,6 +52,17 @@ 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: """ @@ -198,7 +209,7 @@ 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, env: BuildEnvironment = None) -> None: super().__init__(app, env) From 08f0a42313c38f7dec7ce913f45c0346397590ce Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 17:04:39 +0100 Subject: [PATCH 12/19] Show MyPy's error codes --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index bc8f14998d5..d6cb45c3ab0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -26,6 +26,7 @@ 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 From c2eedd772cea8206af76a132bbdb0ea97166eafe Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 17:05:22 +0100 Subject: [PATCH 13/19] Specific ignore --- sphinx/application.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 46af51971fb..cd86a39e131 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -1003,7 +1003,7 @@ def add_js_file(self, filename: str, priority: int = 500, self.registry.add_js_file(filename, priority=priority, **kwargs) if hasattr(self, 'builder') and hasattr(self.builder, 'add_js_file'): - self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore + self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore[attr-defined] def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: """Register a stylesheet to include in the HTML output. @@ -1064,7 +1064,7 @@ def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> Non logger.debug('[app] adding stylesheet: %r', filename) self.registry.add_css_files(filename, priority=priority, **kwargs) if hasattr(self, 'builder') and hasattr(self.builder, 'add_css_file'): - self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore + self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore[attr-defined] def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None ) -> None: From 0bf4d55b9cd56e3c3402e9afa732beba6f4d8df9 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 17:08:13 +0100 Subject: [PATCH 14/19] Line length --- sphinx/application.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index cd86a39e131..d75bd7e38d1 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -1003,7 +1003,8 @@ def add_js_file(self, filename: str, priority: int = 500, self.registry.add_js_file(filename, priority=priority, **kwargs) if hasattr(self, 'builder') and hasattr(self.builder, 'add_js_file'): - self.builder.add_js_file(filename, priority=priority, **kwargs) # type: ignore[attr-defined] + self.builder.add_js_file(filename, priority=priority, + **kwargs) # type: ignore[attr-defined] def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> None: """Register a stylesheet to include in the HTML output. @@ -1064,7 +1065,8 @@ def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> Non logger.debug('[app] adding stylesheet: %r', filename) self.registry.add_css_files(filename, priority=priority, **kwargs) if hasattr(self, 'builder') and hasattr(self.builder, 'add_css_file'): - self.builder.add_css_file(filename, priority=priority, **kwargs) # type: ignore[attr-defined] + self.builder.add_css_file(filename, priority=priority, + **kwargs) # type: ignore[attr-defined] def add_stylesheet(self, filename: str, alternate: bool = False, title: str = None ) -> None: From 7377de1f117c319ebe4b8fba30bbd530dd4b4400 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 17:10:59 +0100 Subject: [PATCH 15/19] It seems the ignore must be exactly on the line of the function call... --- sphinx/application.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index d75bd7e38d1..97fccbd4d55 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -1003,8 +1003,8 @@ def add_js_file(self, filename: str, priority: int = 500, self.registry.add_js_file(filename, priority=priority, **kwargs) if hasattr(self, 'builder') and hasattr(self.builder, 'add_js_file'): - self.builder.add_js_file(filename, priority=priority, - **kwargs) # type: ignore[attr-defined] + 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. @@ -1065,8 +1065,8 @@ def add_css_file(self, filename: str, priority: int = 500, **kwargs: Any) -> Non logger.debug('[app] adding stylesheet: %r', filename) self.registry.add_css_files(filename, priority=priority, **kwargs) if hasattr(self, 'builder') and hasattr(self.builder, 'add_css_file'): - self.builder.add_css_file(filename, priority=priority, - **kwargs) # type: ignore[attr-defined] + 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: From b4a92ac7daf4477796d681d55f0d383f25804244 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 18:00:29 +0100 Subject: [PATCH 16/19] Update create_builder to handle custom builders --- sphinx/registry.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/sphinx/registry.py b/sphinx/registry.py index 3a1bde6bb16..9fccf461be2 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -22,7 +22,7 @@ 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 @@ -158,7 +158,15 @@ def create_builder(self, app: "Sphinx", name: str, if name not in self.builders: raise SphinxError(__('Builder name %s not registered') % name) - return self.builders[name](app, env) + 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) + return self.builders[name](app) def add_domain(self, domain: Type[Domain], override: bool = False) -> None: logger.debug('[app] adding domain: %r', domain) From c1ed2fb531de5821a918c9eeb0aa642a45538fc5 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 18:50:41 +0100 Subject: [PATCH 17/19] Add `builder.set_environment` calls --- sphinx/application.py | 2 ++ sphinx/builders/__init__.py | 3 ++- sphinx/registry.py | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 97fccbd4d55..3fa622a6e3e 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -326,6 +326,8 @@ def create_builder(self, name: str) -> "Builder": return self.registry.create_builder(self, name, self.env) def _init_builder(self) -> None: + if not hasattr(self.builder, "env"): + self.builder.set_environment(self.env) self.builder.init() self.events.emit('builder-inited') diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 45bac412158..9705ba89425 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -89,7 +89,8 @@ def __init__(self, app: "Sphinx", env: BuildEnvironment = None) -> None: self.env: BuildEnvironment = env self.env.set_versioning_method(self.versioning_method, self.versioning_compare) - else: + 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 diff --git a/sphinx/registry.py b/sphinx/registry.py index 9fccf461be2..3341f72c3fb 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -166,7 +166,10 @@ def create_builder(self, app: "Sphinx", name: str, 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) - return self.builders[name](app) + builder = self.builders[name](app, env=...) + 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) From 63a803ee86ef8f4d9aa45a3809766ceda406679d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 18:53:44 +0100 Subject: [PATCH 18/19] Ignore intentional type breakage --- sphinx/registry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/registry.py b/sphinx/registry.py index 3341f72c3fb..da892f91bb6 100644 --- a/sphinx/registry.py +++ b/sphinx/registry.py @@ -166,7 +166,7 @@ def create_builder(self, app: "Sphinx", name: str, 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=...) + builder = self.builders[name](app, env=...) # type: ignore[arg-type] if env is not None: builder.set_environment(env) return builder From 465726196ff1598062ace38754e7f2df0d052481 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Mon, 6 Jun 2022 18:56:53 +0100 Subject: [PATCH 19/19] Add `no_implicit_optional` config setting --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index d6cb45c3ab0..b0bfa28d37d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -32,6 +32,7 @@ follow_imports = skip check_untyped_defs = True warn_unused_ignores = True strict_optional = False +no_implicit_optional = True [tool:pytest] filterwarnings =