From de24fd0b76f6d86672a721f8c130642b0be4bcf0 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:06:19 +0100 Subject: [PATCH 01/39] Use ``_StrPath`` in ``sphinx.cmd.make_mode`` --- sphinx/cmd/make_mode.py | 48 ++++++++++++++++++++++++----------------- sphinx/util/osutil.py | 2 +- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/sphinx/cmd/make_mode.py b/sphinx/cmd/make_mode.py index ac22ba99822..00c8744868d 100644 --- a/sphinx/cmd/make_mode.py +++ b/sphinx/cmd/make_mode.py @@ -13,11 +13,11 @@ import subprocess import sys from contextlib import chdir -from os import path from typing import TYPE_CHECKING import sphinx from sphinx.cmd.build import build_main +from sphinx.util._pathlib import _StrPath from sphinx.util.console import blue, bold, color_terminal, nocolor from sphinx.util.osutil import rmtree @@ -57,30 +57,36 @@ class Make: - def __init__(self, *, source_dir: str, build_dir: str, opts: Sequence[str]) -> None: - self.source_dir = source_dir - self.build_dir = build_dir + def __init__( + self, + *, + source_dir: str | os.PathLike[str], + build_dir: str | os.PathLike[str], + opts: Sequence[str], + ) -> None: + self.source_dir = _StrPath(source_dir) + self.build_dir = _StrPath(build_dir) self.opts = [*opts] - def build_dir_join(self, *comps: str) -> str: - return path.join(self.build_dir, *comps) + def build_dir_join(self, *comps: str | os.PathLike[str]) -> _StrPath: + return self.build_dir.joinpath(*comps) def build_clean(self) -> int: - source_dir = path.abspath(self.source_dir) - build_dir = path.abspath(self.build_dir) - if not path.exists(self.build_dir): + source_dir = self.source_dir.resolve() + build_dir = self.build_dir.resolve() + if not self.build_dir.exists(): return 0 - elif not path.isdir(self.build_dir): - print('Error: %r is not a directory!' % self.build_dir) + elif not self.build_dir.is_dir(): + print("Error: '%s' is not a directory!" % self.build_dir) return 1 elif source_dir == build_dir: - print('Error: %r is same as source directory!' % self.build_dir) + print("Error: '%s' is same as source directory!" % self.build_dir) return 1 - elif path.commonpath([source_dir, build_dir]) == build_dir: - print('Error: %r directory contains source directory!' % self.build_dir) + elif source_dir.is_relative_to(build_dir): + print("Error: '%s' directory contains source directory!" % self.build_dir) return 1 - print('Removing everything under %r...' % self.build_dir) - for item in os.listdir(self.build_dir): + print("Removing everything under '%s'..." % self.build_dir) + for item in self.build_dir.iterdir(): rmtree(self.build_dir_join(item)) return 0 @@ -179,7 +185,9 @@ def build_gettext(self) -> int: return 1 return 0 - def run_generic_build(self, builder: str, doctreedir: str | None = None) -> int: + def run_generic_build( + self, builder: str, doctreedir: str | os.PathLike[str] | None = None + ) -> int: # compatibility with old Makefile paper_size = os.getenv('PAPER', '') if paper_size in {'a4', 'letter'}: @@ -191,9 +199,9 @@ def run_generic_build(self, builder: str, doctreedir: str | None = None) -> int: '--builder', builder, '--doctree-dir', - doctreedir, - self.source_dir, - self.build_dir_join(builder), + str(doctreedir), + str(self.source_dir), + str(self.build_dir_join(builder)), ] return build_main(args + self.opts) diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 4e092692bf0..7ec14d446b2 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -236,7 +236,7 @@ def __getattr__(self, name: str) -> Any: return getattr(self._io, name) -def rmtree(path: str) -> None: +def rmtree(path: str | os.PathLike[str], /) -> None: if os.path.isdir(path): shutil.rmtree(path) else: From f0f127f11610797fcef9caa1a6dd8a8a3265ab21 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:11:48 +0100 Subject: [PATCH 02/39] Use ``_StrPath`` in ``sphinx.cmd.build`` --- sphinx/cmd/build.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index 3c3d8e4b4de..db76bc3ad85 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -7,11 +7,10 @@ import contextlib import locale import multiprocessing -import os import pdb # NoQA: T100 import sys import traceback -from os import path +from pathlib import Path from typing import TYPE_CHECKING, Any, TextIO from docutils.utils import SystemMessage @@ -22,6 +21,7 @@ from sphinx.errors import SphinxError, SphinxParallelError from sphinx.locale import __ from sphinx.util._io import TeeStripANSI +from sphinx.util._pathlib import _StrPath from sphinx.util.console import color_terminal, nocolor, red, terminal_safe from sphinx.util.docutils import docutils_namespace, patch_docutils from sphinx.util.exceptions import format_exception_cut_frames, save_traceback @@ -392,10 +392,10 @@ def _parse_confdir(noconfig: bool, confdir: str, sourcedir: str) -> str | None: return confdir -def _parse_doctreedir(doctreedir: str, outputdir: str) -> str: +def _parse_doctreedir(doctreedir: str, outputdir: str) -> _StrPath: if doctreedir: - return doctreedir - return os.path.join(outputdir, '.doctrees') + return _StrPath(doctreedir) + return _StrPath(outputdir, '.doctrees') def _validate_filenames( @@ -431,12 +431,12 @@ def _parse_logging( warnfp = None if warning and warnfile: try: - warnfile = path.abspath(warnfile) - ensuredir(path.dirname(warnfile)) + warn_file = Path(warnfile).resolve() + ensuredir(warn_file.parent) # the caller is responsible for closing this file descriptor - warnfp = open(warnfile, 'w', encoding='utf-8') # NoQA: SIM115 + warnfp = open(warn_file, 'w', encoding='utf-8') # NoQA: SIM115 except Exception as exc: - parser.error(__('cannot open warning file %r: %s') % (warnfile, exc)) + parser.error(__("cannot open warning file '%s': %s") % (warn_file, exc)) warning = TeeStripANSI(warning, warnfp) # type: ignore[assignment] error = warning From 97ddc477650efb590d8245239279c4158dc24bd2 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:14:24 +0100 Subject: [PATCH 03/39] Use ``_StrPath`` in ``sphinx.directives.patches`` --- sphinx/directives/patches.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/sphinx/directives/patches.py b/sphinx/directives/patches.py index 85cff2e6407..ff2989520d8 100644 --- a/sphinx/directives/patches.py +++ b/sphinx/directives/patches.py @@ -1,7 +1,7 @@ from __future__ import annotations import os -from os import path +from pathlib import Path from typing import TYPE_CHECKING, ClassVar, cast from docutils import nodes @@ -16,7 +16,7 @@ from sphinx.util import logging from sphinx.util.docutils import SphinxDirective from sphinx.util.nodes import set_source_info -from sphinx.util.osutil import SEP, os_path, relpath +from sphinx.util.osutil import SEP, relpath if TYPE_CHECKING: from sphinx.application import Sphinx @@ -60,8 +60,8 @@ class CSVTable(tables.CSVTable): # type: ignore[misc] def run(self) -> list[Node]: if 'file' in self.options and self.options['file'].startswith((SEP, os.sep)): env = self.state.document.settings.env - filename = self.options['file'] - if path.exists(filename): + filename = Path(self.options['file']) + if filename.exists(): logger.warning( __( '":file:" option for csv-table directive now recognizes ' @@ -71,9 +71,9 @@ def run(self) -> list[Node]: location=(env.docname, self.lineno), ) else: - abspath = path.join(env.srcdir, os_path(self.options['file'][1:])) - docdir = path.dirname(env.doc2path(env.docname)) - self.options['file'] = relpath(abspath, docdir) + abspath = env.srcdir / self.options['file'][1:] + doc_dir = env.doc2path(env.docname).parent + self.options['file'] = relpath(abspath, doc_dir) return super().run() From 072df8f779def71d51cf43c8fa934cb0fa5e1ef2 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:21:12 +0100 Subject: [PATCH 04/39] Use ``_StrPath`` in ``sphinx.search`` --- sphinx/search/__init__.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/sphinx/search/__init__.py b/sphinx/search/__init__.py index 82081cbe2e9..3f19d3663a0 100644 --- a/sphinx/search/__init__.py +++ b/sphinx/search/__init__.py @@ -10,13 +10,14 @@ import pickle import re from importlib import import_module -from os import path +from pathlib import Path from typing import IO, TYPE_CHECKING, Any from docutils import nodes from docutils.nodes import Element, Node from sphinx import addnodes, package_dir +from sphinx.util._pathlib import _StrPath from sphinx.util.index_entries import split_index_msg if TYPE_CHECKING: @@ -24,6 +25,9 @@ from sphinx.environment import BuildEnvironment +_NON_MINIFIED_JS_PATH = Path(package_dir, 'search', 'non-minified-js') +_MINIFIED_JS_PATH = Path(package_dir, 'search', 'minified-js') + class SearchLanguage: """ @@ -554,12 +558,12 @@ def context_for_searchtool(self) -> dict[str, Any]: 'search_word_splitter_code': js_splitter_code, } - def get_js_stemmer_rawcodes(self) -> list[str]: + def get_js_stemmer_rawcodes(self) -> list[_StrPath]: """Returns a list of non-minified stemmer JS files to copy.""" if self.lang.js_stemmer_rawcode: return [ - path.join(package_dir, 'search', 'non-minified-js', fname) - for fname in ('base-stemmer.js', self.lang.js_stemmer_rawcode) + _StrPath(_NON_MINIFIED_JS_PATH / 'base-stemmer.js'), + _StrPath(_NON_MINIFIED_JS_PATH / self.lang.js_stemmer_rawcode), ] else: return [] @@ -570,15 +574,10 @@ def get_js_stemmer_rawcode(self) -> str | None: def get_js_stemmer_code(self) -> str: """Returns JS code that will be inserted into language_data.js.""" if self.lang.js_stemmer_rawcode: - js_dir = path.join(package_dir, 'search', 'minified-js') - with open( - path.join(js_dir, 'base-stemmer.js'), encoding='utf-8' - ) as js_file: - base_js = js_file.read() - with open( - path.join(js_dir, self.lang.js_stemmer_rawcode), encoding='utf-8' - ) as js_file: - language_js = js_file.read() + base_js_path = _NON_MINIFIED_JS_PATH / 'base-stemmer.js' + language_js_path = _NON_MINIFIED_JS_PATH / self.lang.js_stemmer_rawcode + base_js = base_js_path.read_text(encoding='utf-8') + language_js = language_js_path.read_text(encoding='utf-8') return ( f'{base_js}\n{language_js}\nStemmer = {self.lang.language_name}Stemmer;' ) From 3015ceb54685f067e1422809392998f43ae396ea Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:46:33 +0100 Subject: [PATCH 05/39] Use ``_StrPath`` in ``sphinx.testing.restructuredtext`` --- sphinx/testing/restructuredtext.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/sphinx/testing/restructuredtext.py b/sphinx/testing/restructuredtext.py index 1f89336db4b..620e8483492 100644 --- a/sphinx/testing/restructuredtext.py +++ b/sphinx/testing/restructuredtext.py @@ -1,5 +1,3 @@ -from os import path - from docutils import nodes from docutils.core import publish_doctree @@ -20,7 +18,7 @@ def parse(app: Sphinx, text: str, docname: str = 'index') -> nodes.document: with sphinx_domains(app.env): return publish_doctree( text, - path.join(app.srcdir, docname + '.rst'), + str(app.srcdir / f'{docname}.rst'), reader=reader, parser=parser, settings_overrides={ From 5cf3e6bf9a052188bc95d7e26328f9420228b554 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 25 Oct 2024 23:58:42 +0100 Subject: [PATCH 06/39] Use ``_StrPath`` in ``sphinx.util`` --- sphinx/util/docutils.py | 18 ++++++++---------- sphinx/util/template.py | 25 ++++++++++++++++--------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index a4c1c67098e..4f085bfeee3 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -7,7 +7,7 @@ from collections.abc import Sequence # NoQA: TCH003 from contextlib import contextmanager from copy import copy -from os import path +from pathlib import Path from typing import IO, TYPE_CHECKING, Any, cast import docutils @@ -171,25 +171,23 @@ def patched_get_language( @contextmanager -def using_user_docutils_conf(confdir: str | None) -> Iterator[None]: +def using_user_docutils_conf(confdir: str | os.PathLike[str] | None) -> Iterator[None]: """Let docutils know the location of ``docutils.conf`` for Sphinx.""" try: - docutilsconfig = os.environ.get('DOCUTILSCONFIG', None) + docutils_config = os.environ.get('DOCUTILSCONFIG', None) if confdir: - os.environ['DOCUTILSCONFIG'] = path.join( - path.abspath(confdir), 'docutils.conf' - ) - + docutils_conf_path = Path(confdir, 'docutils.conf').resolve() + os.environ['DOCUTILSCONFIG'] = str(docutils_conf_path) yield finally: - if docutilsconfig is None: + if docutils_config is None: os.environ.pop('DOCUTILSCONFIG', None) else: - os.environ['DOCUTILSCONFIG'] = docutilsconfig + os.environ['DOCUTILSCONFIG'] = docutils_config @contextmanager -def patch_docutils(confdir: str | None = None) -> Iterator[None]: +def patch_docutils(confdir: str | os.PathLike[str] | None = None) -> Iterator[None]: """Patch to docutils temporarily.""" with ( patched_get_language(), diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 6e217d6177f..b428d3cf777 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -4,7 +4,7 @@ import os from functools import partial -from os import path +from pathlib import Path from typing import TYPE_CHECKING, Any from jinja2 import TemplateNotFound @@ -21,6 +21,9 @@ from jinja2.environment import Environment +_TEMPLATES_PATH = Path(package_dir, 'templates') +_LATEX_TEMPLATES_PATH = _TEMPLATES_PATH / 'latex' + class BaseRenderer: def __init__(self, loader: BaseLoader | None = None) -> None: @@ -49,11 +52,12 @@ def __init__(self, search_path: Sequence[str | os.PathLike[str]]) -> None: @classmethod def render_from_file( - cls: type[FileRenderer], filename: str, context: dict[str, Any] + cls: type[FileRenderer], + filename: str | os.PathLike[str], + context: dict[str, Any], ) -> str: - dirname = os.path.dirname(filename) - basename = os.path.basename(filename) - return cls(dirname).render(basename, context) + filename = Path(filename) + return cls((filename.parent,)).render(filename.name, context) class SphinxRenderer(FileRenderer): @@ -61,12 +65,14 @@ def __init__( self, template_path: Sequence[str | os.PathLike[str]] | None = None ) -> None: if template_path is None: - template_path = os.path.join(package_dir, 'templates') + template_path = (_TEMPLATES_PATH,) super().__init__(template_path) @classmethod def render_from_file( - cls: type[FileRenderer], filename: str, context: dict[str, Any] + cls: type[FileRenderer], + filename: str | os.PathLike[str], + context: dict[str, Any], ) -> str: return FileRenderer.render_from_file(filename, context) @@ -78,7 +84,7 @@ def __init__( latex_engine: str | None = None, ) -> None: if template_path is None: - template_path = [os.path.join(package_dir, 'templates', 'latex')] + template_path = (_LATEX_TEMPLATES_PATH,) super().__init__(template_path) # use texescape as escape filter @@ -126,8 +132,9 @@ def __init__( self.loaders = [] self.sysloaders = [] + conf_dir = Path(confdir) for templates_path in templates_paths: - loader = SphinxFileSystemLoader(path.join(confdir, templates_path)) + loader = SphinxFileSystemLoader(conf_dir / templates_path) self.loaders.append(loader) for templates_path in system_templates_paths: From e7fa2420b61f4f1908c99d24499fd62bfba259c3 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 26 Oct 2024 00:07:13 +0100 Subject: [PATCH 07/39] Use ``_StrPath`` in ``sphinx.config`` --- sphinx/config.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/sphinx/config.py b/sphinx/config.py index 8700ed30054..24b0ba2cd9c 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -7,14 +7,14 @@ import types import warnings from contextlib import chdir -from os import getenv, path +from os import getenv +from pathlib import Path from typing import TYPE_CHECKING, Any, Literal, NamedTuple from sphinx.deprecation import RemovedInSphinx90Warning from sphinx.errors import ConfigError, ExtensionError from sphinx.locale import _, __ from sphinx.util import logging -from sphinx.util.osutil import fs_encoding if TYPE_CHECKING: import os @@ -304,8 +304,8 @@ def overrides(self) -> dict[str, Any]: def read(cls: type[Config], confdir: str | os.PathLike[str], overrides: dict | None = None, tags: Tags | None = None) -> Config: """Create a Config object from configuration file.""" - filename = path.join(confdir, CONFIG_FILENAME) - if not path.isfile(filename): + filename = Path(confdir, CONFIG_FILENAME) + if not filename.is_file(): raise ConfigError(__("config directory doesn't contain a conf.py file (%s)") % confdir) namespace = eval_config_file(filename, tags) @@ -510,18 +510,19 @@ def __setstate__(self, state: dict) -> None: self.__dict__.update(state) -def eval_config_file(filename: str, tags: Tags | None) -> dict[str, Any]: +def eval_config_file(filename: str | os.PathLike[str], tags: Tags | None) -> dict[str, Any]: """Evaluate a config file.""" + filename = Path(filename) + namespace: dict[str, Any] = {} - namespace['__file__'] = filename + namespace['__file__'] = str(filename) namespace['tags'] = tags - with chdir(path.dirname(filename)): + with chdir(filename.parent): # during executing config file, current dir is changed to ``confdir``. try: - with open(filename, 'rb') as f: - code = compile(f.read(), filename.encode(fs_encoding), 'exec') - exec(code, namespace) # NoQA: S102 + code = compile(filename.read_bytes(), filename, 'exec') + exec(code, namespace) # NoQA: S102 except SyntaxError as err: msg = __("There is a syntax error in your configuration file: %s\n") raise ConfigError(msg % err) from err From eed19a0a7f1dce9088f6fce8250a0573373d4325 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 26 Oct 2024 00:08:33 +0100 Subject: [PATCH 08/39] Use ``_StrPath`` in ``sphinx.versioning`` --- sphinx/versioning.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sphinx/versioning.py b/sphinx/versioning.py index 506d7b5753d..778c7ab5da0 100644 --- a/sphinx/versioning.py +++ b/sphinx/versioning.py @@ -5,7 +5,6 @@ import pickle from itertools import product, zip_longest from operator import itemgetter -from os import path from typing import TYPE_CHECKING, Any from uuid import uuid4 @@ -160,8 +159,8 @@ def apply(self, **kwargs: Any) -> None: if env.versioning_compare: # get old doctree + filename = env.doctreedir / f'{env.docname}.doctree' try: - filename = path.join(env.doctreedir, env.docname + '.doctree') with open(filename, 'rb') as f: old_doctree = pickle.load(f) except OSError: From 3b5bce906c3a8473ba75c650d03e77ee4fe8e5a0 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 26 Oct 2024 00:12:12 +0100 Subject: [PATCH 09/39] Use ``_StrPath`` in ``sphinx.application`` --- sphinx/application.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 2d650dc231f..5edc86afb71 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -11,7 +11,6 @@ import sys from collections import deque from io import StringIO -from os import path from typing import TYPE_CHECKING, overload from docutils.parsers.rst import Directive, roles @@ -182,11 +181,11 @@ def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[st self.outdir = _StrPath(outdir).resolve() self.doctreedir = _StrPath(doctreedir).resolve() - if not path.isdir(self.srcdir): + if not self.srcdir.is_dir(): raise ApplicationError(__('Cannot find source directory (%s)') % self.srcdir) - if path.exists(self.outdir) and not path.isdir(self.outdir): + if self.outdir.exists() and not self.outdir.is_dir(): raise ApplicationError(__('Output directory (%s) is not a directory') % self.outdir) @@ -258,9 +257,9 @@ def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[st # preload builder module (before init config values) self.preload_builder(buildername) - if not path.isdir(outdir): + if not self.outdir.is_dir(): with progress_message(__('making output directory')): - ensuredir(outdir) + ensuredir(self.outdir) # the config file itself can be an extension if self.config.setup: @@ -327,8 +326,8 @@ def _init_i18n(self) -> None: logger.info(__('not available for built-in messages')) def _init_env(self, freshenv: bool) -> BuildEnvironment: - filename = path.join(self.doctreedir, ENV_PICKLE_FILENAME) - if freshenv or not os.path.exists(filename): + filename = self.doctreedir / ENV_PICKLE_FILENAME + if freshenv or not filename.exists(): return self._create_fresh_env() else: return self._load_existing_env(filename) @@ -339,12 +338,12 @@ def _create_fresh_env(self) -> BuildEnvironment: return env @progress_message(__('loading pickled environment')) - def _load_existing_env(self, filename: str) -> BuildEnvironment: + def _load_existing_env(self, filename: Path) -> BuildEnvironment: try: with open(filename, 'rb') as f: env = pickle.load(f) - env.setup(self) - self._fresh_env_used = False + env.setup(self) + self._fresh_env_used = False except Exception as err: logger.info(__('failed: %s'), err) env = self._create_fresh_env() @@ -383,8 +382,8 @@ def build(self, force_all: bool = False, filenames: list[str] | None = None) -> self.events.emit('build-finished', None) except Exception as err: # delete the saved env to force a fresh build next time - envfile = path.join(self.doctreedir, ENV_PICKLE_FILENAME) - if path.isfile(envfile): + envfile = self.doctreedir / ENV_PICKLE_FILENAME + if envfile.is_file(): os.unlink(envfile) self.events.emit('build-finished', err) raise From 1e968be6ef441cadcf99d8073b3281dbdd60139e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 26 Oct 2024 15:49:14 +0100 Subject: [PATCH 10/39] Minimise installed apt packages --- .github/workflows/builddoc.yml | 2 +- .github/workflows/main.yml | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/builddoc.yml b/.github/workflows/builddoc.yml index f2055e2798f..e3347a7b535 100644 --- a/.github/workflows/builddoc.yml +++ b/.github/workflows/builddoc.yml @@ -27,7 +27,7 @@ jobs: with: python-version: "3" - name: Install graphviz - run: sudo apt-get install graphviz + run: sudo apt-get install --no-install-recommends --yes graphviz - name: Install uv run: > curl --no-progress-meter --location --fail diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 27f67597d46..aa3a27753b2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -54,7 +54,7 @@ jobs: - name: Check Python version run: python --version --version - name: Install graphviz - run: sudo apt-get install graphviz + run: sudo apt-get install --no-install-recommends --yes graphviz - name: Install uv run: > curl --no-progress-meter --location --fail @@ -92,7 +92,7 @@ jobs: - name: Check Python version run: python --version --version - name: Install graphviz - run: sudo apt-get install graphviz + run: sudo apt-get install --no-install-recommends --yes graphviz - name: Install dependencies run: | python -m pip install --upgrade pip @@ -125,7 +125,7 @@ jobs: - name: Check Python version run: python --version --version - name: Install graphviz - run: sudo apt-get install graphviz + run: sudo apt-get install --no-install-recommends --yes graphviz - name: Install dependencies run: | python -m pip install --upgrade pip @@ -158,7 +158,7 @@ jobs: - name: Check Python version run: python --version --version - name: Install graphviz - run: sudo apt-get install graphviz + run: sudo apt-get install --no-install-recommends --yes graphviz - name: Install dependencies run: | python -m pip install --upgrade pip @@ -218,7 +218,7 @@ jobs: - name: Check Python version run: python --version --version - name: Install graphviz - run: sudo apt-get install graphviz + run: sudo apt-get install --no-install-recommends --yes graphviz - name: Install uv run: > curl --no-progress-meter --location --fail @@ -250,7 +250,7 @@ jobs: - name: Check Python version run: python --version --version - name: Install graphviz - run: sudo apt-get install graphviz + run: sudo apt-get install --no-install-recommends --yes graphviz - name: Install uv run: > curl --no-progress-meter --location --fail @@ -310,7 +310,7 @@ jobs: - name: Check Python version run: python --version --version - name: Install graphviz - run: sudo apt-get install graphviz + run: sudo apt-get install --no-install-recommends --yes graphviz - name: Install uv run: > curl --no-progress-meter --location --fail From c3968e9be96d51179e701439fbe7dfa22c8ed204 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 28 Oct 2024 13:31:59 +0000 Subject: [PATCH 11/39] Use attributes of ``env`` rather than ``env.app`` --- sphinx/directives/__init__.py | 4 ++-- sphinx/directives/other.py | 4 ++-- sphinx/domains/cpp/__init__.py | 2 +- sphinx/domains/javascript.py | 2 +- sphinx/domains/python/_object.py | 2 +- sphinx/domains/rst.py | 2 +- sphinx/ext/autodoc/__init__.py | 31 +++++++++++++++++++------------ 7 files changed, 27 insertions(+), 20 deletions(-) diff --git a/sphinx/directives/__init__.py b/sphinx/directives/__init__.py index 218bc6d5431..181c6f81a07 100644 --- a/sphinx/directives/__init__.py +++ b/sphinx/directives/__init__.py @@ -267,7 +267,7 @@ def run(self) -> list[Node]: finally: # Private attributes for ToC generation. Will be modified or removed # without notice. - if self.env.app.config.toc_object_entries: + if self.env.config.toc_object_entries: signode['_toc_parts'] = self._object_hierarchy_parts(signode) signode['_toc_name'] = self._toc_entry_name(signode) else: @@ -288,7 +288,7 @@ def run(self) -> list[Node]: content_node = addnodes.desc_content('', *content_children) node.append(content_node) self.transform_content(content_node) - self.env.app.emit( + self.env.events.emit( 'object-description-transform', self.domain, self.objtype, content_node ) DocFieldTransformer(self).transform_all(content_node) diff --git a/sphinx/directives/other.py b/sphinx/directives/other.py index e297ceb4b89..32fd3111a65 100644 --- a/sphinx/directives/other.py +++ b/sphinx/directives/other.py @@ -412,7 +412,7 @@ def _insert_input(include_lines: list[str], source: str) -> None: # Emit the "include-read" event arg = [text] - self.env.app.events.emit('include-read', path, docname, arg) + self.env.events.emit('include-read', path, docname, arg) text = arg[0] # Split back into lines and reattach the two marker lines @@ -424,7 +424,7 @@ def _insert_input(include_lines: list[str], source: str) -> None: return StateMachine.insert_input(self.state_machine, include_lines, source) # Only enable this patch if there are listeners for 'include-read'. - if self.env.app.events.listeners.get('include-read'): + if self.env.events.listeners.get('include-read'): # See https://github.com/python/mypy/issues/2427 for details on the mypy issue self.state_machine.insert_input = _insert_input diff --git a/sphinx/domains/cpp/__init__.py b/sphinx/domains/cpp/__init__.py index 45183611106..5e7942dd65b 100644 --- a/sphinx/domains/cpp/__init__.py +++ b/sphinx/domains/cpp/__init__.py @@ -403,7 +403,7 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: if not sig_node.get('_toc_parts'): return '' - config = self.env.app.config + config = self.env.config objtype = sig_node.parent.get('objtype') if config.add_function_parentheses and objtype in {'function', 'method'}: parens = '()' diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index ec81375a6da..7ad37987b38 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -230,7 +230,7 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: if not sig_node.get('_toc_parts'): return '' - config = self.env.app.config + config = self.env.config objtype = sig_node.parent.get('objtype') if config.add_function_parentheses and objtype in {'function', 'method'}: parens = '()' diff --git a/sphinx/domains/python/_object.py b/sphinx/domains/python/_object.py index ceb7f8bf83b..3e9049a1a27 100644 --- a/sphinx/domains/python/_object.py +++ b/sphinx/domains/python/_object.py @@ -411,7 +411,7 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: if not sig_node.get('_toc_parts'): return '' - config = self.env.app.config + config = self.env.config objtype = sig_node.parent.get('objtype') if config.add_function_parentheses and objtype in {'function', 'method'}: parens = '()' diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 9eec281f3e6..f2ada04e1fe 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -75,7 +75,7 @@ def _toc_entry_name(self, sig_node: desc_signature) -> str: if not sig_node.get('_toc_parts'): return '' - config = self.env.app.config + config = self.env.config objtype = sig_node.parent.get('objtype') *parents, name = sig_node['_toc_parts'] if objtype == 'directive:option': diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index c5d2b0b248b..60c31e2542e 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -584,9 +584,14 @@ def process_doc(self, docstrings: list[list[str]]) -> Iterator[str]: for docstringlines in docstrings: if self.env.app: # let extensions preprocess docstrings - self.env.app.emit('autodoc-process-docstring', - self.objtype, self.fullname, self.object, - self.options, docstringlines) + self.env.events.emit( + 'autodoc-process-docstring', + self.objtype, + self.fullname, + self.object, + self.options, + docstringlines, + ) if docstringlines and docstringlines[-1]: # append a blank line to the end of the docstring @@ -793,7 +798,7 @@ def is_filtered_inherited_member(name: str, obj: Any) -> bool: # should be skipped if self.env.app: # let extensions preprocess docstrings - skip_user = self.env.app.emit_firstresult( + skip_user = self.env.events.emit_firstresult( 'autodoc-skip-member', self.objtype, membername, member, not keep, self.options) if skip_user is not None: @@ -1325,7 +1330,7 @@ def format_args(self, **kwargs: Any) -> str: kwargs.setdefault('unqualified_typehints', True) try: - self.env.app.emit('autodoc-before-process-signature', self.object, False) + self.env.events.emit('autodoc-before-process-signature', self.object, False) sig = inspect.signature(self.object, type_aliases=self.config.autodoc_type_aliases) args = stringify_signature(sig, **kwargs) except TypeError as exc: @@ -1564,7 +1569,7 @@ def get_user_defined_function_or_method(obj: Any, attr: str) -> Any: call = None if call is not None: - self.env.app.emit('autodoc-before-process-signature', call, True) + self.env.events.emit('autodoc-before-process-signature', call, True) try: sig = inspect.signature(call, bound_method=True, type_aliases=self.config.autodoc_type_aliases) @@ -1580,7 +1585,7 @@ def get_user_defined_function_or_method(obj: Any, attr: str) -> Any: new = None if new is not None: - self.env.app.emit('autodoc-before-process-signature', new, True) + self.env.events.emit('autodoc-before-process-signature', new, True) try: sig = inspect.signature(new, bound_method=True, type_aliases=self.config.autodoc_type_aliases) @@ -1591,7 +1596,7 @@ def get_user_defined_function_or_method(obj: Any, attr: str) -> Any: # Finally, we should have at least __init__ implemented init = get_user_defined_function_or_method(self.object, '__init__') if init is not None: - self.env.app.emit('autodoc-before-process-signature', init, True) + self.env.events.emit('autodoc-before-process-signature', init, True) try: sig = inspect.signature(init, bound_method=True, type_aliases=self.config.autodoc_type_aliases) @@ -1603,7 +1608,7 @@ def get_user_defined_function_or_method(obj: Any, attr: str) -> Any: # handle it. # We don't know the exact method that inspect.signature will read # the signature from, so just pass the object itself to our hook. - self.env.app.emit('autodoc-before-process-signature', self.object, False) + self.env.events.emit('autodoc-before-process-signature', self.object, False) try: sig = inspect.signature(self.object, bound_method=False, type_aliases=self.config.autodoc_type_aliases) @@ -2198,11 +2203,13 @@ def format_args(self, **kwargs: Any) -> str: args = '()' else: if inspect.isstaticmethod(self.object, cls=self.parent, name=self.object_name): - self.env.app.emit('autodoc-before-process-signature', self.object, False) + self.env.events.emit( + 'autodoc-before-process-signature', self.object, False + ) sig = inspect.signature(self.object, bound_method=False, type_aliases=self.config.autodoc_type_aliases) else: - self.env.app.emit('autodoc-before-process-signature', self.object, True) + self.env.events.emit('autodoc-before-process-signature', self.object, True) sig = inspect.signature(self.object, bound_method=True, type_aliases=self.config.autodoc_type_aliases) args = stringify_signature(sig, **kwargs) @@ -2785,7 +2792,7 @@ def format_args(self, **kwargs: Any) -> str: return '' # update the annotations of the property getter - self.env.app.emit('autodoc-before-process-signature', func, False) + self.env.events.emit('autodoc-before-process-signature', func, False) # correctly format the arguments for a property return super().format_args(**kwargs) From c6b4114cde98c28c9b53b7caab7a9191c99e7afd Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 2 Nov 2024 21:26:57 +0000 Subject: [PATCH 12/39] Make registry use explicit when creating _DomainsContainer --- sphinx/domains/_domains_container.py | 7 +++++-- sphinx/environment/__init__.py | 8 ++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/sphinx/domains/_domains_container.py b/sphinx/domains/_domains_container.py index b8e389c79e4..5578a92575b 100644 --- a/sphinx/domains/_domains_container.py +++ b/sphinx/domains/_domains_container.py @@ -22,6 +22,7 @@ from sphinx.environment import BuildEnvironment from sphinx.ext.duration import DurationDomain from sphinx.ext.todo import TodoDomain + from sphinx.registry import SphinxComponentRegistry class _DomainsContainer: @@ -71,8 +72,10 @@ class _DomainsContainer: }) @classmethod - def _from_environment(cls, env: BuildEnvironment, /) -> Self: - create_domains = env.app.registry.create_domains + def _from_environment( + cls, env: BuildEnvironment, /, *, registry: SphinxComponentRegistry + ) -> Self: + create_domains = registry.create_domains # Initialise domains if domains := {domain.name: domain for domain in create_domains(env)}: return cls(**domains) # type: ignore[arg-type] diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 34ef4cc8066..8ecacf257ce 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -220,7 +220,9 @@ def __init__(self, app: Sphinx) -> None: self._search_index_objnames: dict[int, tuple[str, str, str]] = {} # all the registered domains, set by the application - self.domains: _DomainsContainer = _DomainsContainer._from_environment(self) + self.domains: _DomainsContainer = _DomainsContainer._from_environment( + self, registry=app.registry + ) # set up environment self.setup(app) @@ -259,7 +261,9 @@ def setup(self, app: Sphinx) -> None: # initialise domains if self.domains is None: # if we are unpickling an environment, we need to recreate the domains - self.domains = _DomainsContainer._from_environment(self) + self.domains = _DomainsContainer._from_environment( + self, registry=app.registry + ) # setup domains (must do after all initialization) self.domains._setup() From 9eff55711be572d4e6f22aa571b16467b8a75371 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 2 Nov 2024 21:30:07 +0000 Subject: [PATCH 13/39] Use ``env.events`` in ``sphinx.ext.todo`` --- sphinx/ext/todo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/ext/todo.py b/sphinx/ext/todo.py index 836dc8b864c..0625621b6db 100644 --- a/sphinx/ext/todo.py +++ b/sphinx/ext/todo.py @@ -97,7 +97,7 @@ def process_doc(self, env: BuildEnvironment, docname: str, document: nodes.document) -> None: todos = self.todos.setdefault(docname, []) for todo in document.findall(todo_node): - env.app.emit('todo-defined', todo) + env.events.emit('todo-defined', todo) todos.append(todo) if env.config.todo_emit_warnings: From dedb21e63851cd08e71ce2399a97c828afaa16dd Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 2 Nov 2024 21:39:18 +0000 Subject: [PATCH 14/39] Resolve _StrPath warnings in the tests --- tests/test_environment/test_environment.py | 5 +++-- tests/test_util/test_util_i18n.py | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/tests/test_environment/test_environment.py b/tests/test_environment/test_environment.py index 64487b8c3f6..10e98584342 100644 --- a/tests/test_environment/test_environment.py +++ b/tests/test_environment/test_environment.py @@ -41,11 +41,12 @@ def test_config_status(make_app, app_params): # incremental build (config entry changed) app3 = make_app(*args, confoverrides={'root_doc': 'indexx'}, **kwargs) fname = app3.srcdir / 'index.rst' + other_fname = app3.srcdir / 'indexx.rst' assert fname.is_file() - shutil.move(fname, fname[:-4] + 'x.rst') + shutil.move(fname, other_fname) assert app3.env.config_status == CONFIG_CHANGED app3.build() - shutil.move(fname[:-4] + 'x.rst', fname) + shutil.move(other_fname, fname) output = strip_colors(app3.status.getvalue()) assert 'The configuration has changed' in output assert "[config changed ('master_doc')] 1 added," in output diff --git a/tests/test_util/test_util_i18n.py b/tests/test_util/test_util_i18n.py index 973b054a1d8..d5ee52fb1f8 100644 --- a/tests/test_util/test_util_i18n.py +++ b/tests/test_util/test_util_i18n.py @@ -19,16 +19,16 @@ def test_catalog_info_for_file_and_path(): cat = i18n.CatalogInfo('path', 'domain', 'utf-8') assert cat.po_file == 'domain.po' assert cat.mo_file == 'domain.mo' - assert cat.po_path == str(Path('path', 'domain.po')) - assert cat.mo_path == str(Path('path', 'domain.mo')) + assert cat.po_path == Path('path', 'domain.po') + assert cat.mo_path == Path('path', 'domain.mo') def test_catalog_info_for_sub_domain_file_and_path(): cat = i18n.CatalogInfo('path', 'sub/domain', 'utf-8') assert cat.po_file == 'sub/domain.po' assert cat.mo_file == 'sub/domain.mo' - assert cat.po_path == str(Path('path', 'sub', 'domain.po')) - assert cat.mo_path == str(Path('path', 'sub', 'domain.mo')) + assert cat.po_path == Path('path', 'sub', 'domain.po') + assert cat.mo_path == Path('path', 'sub', 'domain.mo') def test_catalog_outdated(tmp_path): @@ -178,8 +178,8 @@ def test_CatalogRepository(tmp_path): # for language xx repo = i18n.CatalogRepository(tmp_path, ['loc1', 'loc2'], 'xx', 'utf-8') assert list(repo.locale_dirs) == [ - str(tmp_path / 'loc1'), - str(tmp_path / 'loc2'), + tmp_path / 'loc1', + tmp_path / 'loc2', ] assert all(isinstance(c, i18n.CatalogInfo) for c in repo.catalogs) assert sorted(c.domain for c in repo.catalogs) == [ From 7a3ba905404747fe9ccab5652ef4da591904581a Mon Sep 17 00:00:00 2001 From: James Addison <55152140+jayaddison@users.noreply.github.com> Date: Sat, 2 Nov 2024 21:50:33 +0000 Subject: [PATCH 15/39] Add James Addison to AUTHORS.rst (#13080) --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 5c463beed8e..3c5de42f249 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -52,6 +52,7 @@ Contributors * Hugo van Kemenade -- support FORCE_COLOR and NO_COLOR * Ian Lee -- quickstart improvements * Jacob Mason -- websupport library (GSOC project) +* James Addison -- linkcheck and HTML search improvements * Jeppe Pihl -- literalinclude improvements * Joel Wurtz -- cellspanning support in LaTeX * John Waltman -- Texinfo builder From 867be99098ccb13ca77b9571f798463244aa0db2 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 2 Nov 2024 22:47:28 +0000 Subject: [PATCH 16/39] Narrow types for resolve_xref and resolve_any_xref --- sphinx/domains/__init__.py | 4 +-- sphinx/domains/c/__init__.py | 15 ++++---- sphinx/domains/citation.py | 4 +-- sphinx/domains/cpp/__init__.py | 15 ++++---- sphinx/domains/javascript.py | 4 +-- sphinx/domains/math.py | 4 +-- sphinx/domains/python/__init__.py | 8 ++--- sphinx/domains/rst.py | 7 ++-- sphinx/domains/std/__init__.py | 34 ++++++++++--------- sphinx/transforms/post_transforms/__init__.py | 2 +- 10 files changed, 53 insertions(+), 44 deletions(-) diff --git a/sphinx/domains/__init__.py b/sphinx/domains/__init__.py index 12a80e0d8d4..3f21078c6c5 100644 --- a/sphinx/domains/__init__.py +++ b/sphinx/domains/__init__.py @@ -223,7 +223,7 @@ def process_field_xref(self, pnode: pending_xref) -> None: def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element, - ) -> Element | None: + ) -> nodes.reference | None: """Resolve the pending_xref *node* with the given *typ* and *target*. This method should return a new node, to replace the xref node, @@ -241,7 +241,7 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, - ) -> list[tuple[str, Element]]: + ) -> list[tuple[str, nodes.reference]]: """Resolve the pending_xref *node* with the given *target*. The reference comes from an "any" or similar role, which means that we diff --git a/sphinx/domains/c/__init__.py b/sphinx/domains/c/__init__.py index e82912c167e..23729f09166 100644 --- a/sphinx/domains/c/__init__.py +++ b/sphinx/domains/c/__init__.py @@ -771,9 +771,10 @@ def merge_domaindata(self, docnames: Set[str], otherdata: dict[str, Any]) -> Non ourObjects[fullname] = (fn, id_, objtype) # no need to warn on duplicates, the symbol merge already does that - def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder, - typ: str, target: str, node: pending_xref, - contnode: Element) -> tuple[Element | None, str | None]: + def _resolve_xref_inner( + self, env: BuildEnvironment, fromdocname: str, builder: Builder, + typ: str, target: str, node: pending_xref, contnode: Element + ) -> tuple[nodes.reference, str] | tuple[None, None]: parser = DefinitionParser(target, location=node, config=env.config) try: name = parser.parse_xref_object() @@ -810,13 +811,13 @@ def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, - contnode: Element) -> Element | None: + contnode: Element) -> nodes.reference | None: return self._resolve_xref_inner(env, fromdocname, builder, typ, target, node, contnode)[0] def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, - ) -> list[tuple[str, Element]]: + ) -> list[tuple[str, nodes.reference]]: with logging.suppress_logging(): retnode, objtype = self._resolve_xref_inner(env, fromdocname, builder, 'any', target, node, contnode) @@ -844,7 +845,9 @@ def setup(app: Sphinx) -> ExtensionMetadata: app.add_config_value("c_id_attributes", [], 'env', types={list, tuple}) app.add_config_value("c_paren_attributes", [], 'env', types={list, tuple}) app.add_config_value("c_extra_keywords", _macroKeywords, 'env', types={set, list}) - app.add_config_value("c_maximum_signature_line_length", None, 'env', types={int, None}) + app.add_config_value( + "c_maximum_signature_line_length", None, 'env', types={int, type(None)} + ) app.add_post_transform(AliasTransform) return { diff --git a/sphinx/domains/citation.py b/sphinx/domains/citation.py index 58d55774f48..0bac6040c46 100644 --- a/sphinx/domains/citation.py +++ b/sphinx/domains/citation.py @@ -86,7 +86,7 @@ def check_consistency(self) -> None: def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element, - ) -> Element | None: + ) -> nodes.reference | None: docname, labelid, lineno = self.citations.get(target, ('', '', 0)) if not docname: return None @@ -96,7 +96,7 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, - ) -> list[tuple[str, Element]]: + ) -> list[tuple[str, nodes.reference]]: refnode = self.resolve_xref(env, fromdocname, builder, 'ref', target, node, contnode) if refnode is None: return [] diff --git a/sphinx/domains/cpp/__init__.py b/sphinx/domains/cpp/__init__.py index 5e7942dd65b..b038a2efd2d 100644 --- a/sphinx/domains/cpp/__init__.py +++ b/sphinx/domains/cpp/__init__.py @@ -962,9 +962,10 @@ def merge_domaindata(self, docnames: Set[str], otherdata: dict[str, Any]) -> Non logger.debug("\tresult end") logger.debug("merge_domaindata end") - def _resolve_xref_inner(self, env: BuildEnvironment, fromdocname: str, builder: Builder, - typ: str, target: str, node: pending_xref, - contnode: Element) -> tuple[Element | None, str | None]: + def _resolve_xref_inner( + self, env: BuildEnvironment, fromdocname: str, builder: Builder, + typ: str, target: str, node: pending_xref, contnode: Element + ) -> tuple[nodes.reference, str] | tuple[None, None]: # add parens again for those that could be functions if typ in {'any', 'func'}: target += '()' @@ -1112,13 +1113,13 @@ def checkType() -> bool: def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element, - ) -> Element | None: + ) -> nodes.reference | None: return self._resolve_xref_inner(env, fromdocname, builder, typ, target, node, contnode)[0] def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, - ) -> list[tuple[str, Element]]: + ) -> list[tuple[str, nodes.reference]]: with logging.suppress_logging(): retnode, objtype = self._resolve_xref_inner(env, fromdocname, builder, 'any', target, node, contnode) @@ -1162,7 +1163,9 @@ def setup(app: Sphinx) -> ExtensionMetadata: app.add_config_value("cpp_index_common_prefix", [], 'env') app.add_config_value("cpp_id_attributes", [], 'env', types={list, tuple}) app.add_config_value("cpp_paren_attributes", [], 'env', types={list, tuple}) - app.add_config_value("cpp_maximum_signature_line_length", None, 'env', types={int, None}) + app.add_config_value( + "cpp_maximum_signature_line_length", None, 'env', types={int, type(None)} + ) app.add_post_transform(AliasTransform) # debug stuff diff --git a/sphinx/domains/javascript.py b/sphinx/domains/javascript.py index 7ad37987b38..ca9b78b53b1 100644 --- a/sphinx/domains/javascript.py +++ b/sphinx/domains/javascript.py @@ -466,7 +466,7 @@ def find_obj( def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element, - ) -> Element | None: + ) -> nodes.reference | None: mod_name = node.get('js:module') prefix = node.get('js:object') searchorder = 1 if node.hasattr('refspecific') else 0 @@ -477,7 +477,7 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, - ) -> list[tuple[str, Element]]: + ) -> list[tuple[str, nodes.reference]]: mod_name = node.get('js:module') prefix = node.get('js:object') name, obj = self.find_obj(env, mod_name, prefix, target, None, 1) diff --git a/sphinx/domains/math.py b/sphinx/domains/math.py index 19e050739db..e59776d7a07 100644 --- a/sphinx/domains/math.py +++ b/sphinx/domains/math.py @@ -95,7 +95,7 @@ def merge_domaindata(self, docnames: Set[str], otherdata: dict[str, Any]) -> Non def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element, - ) -> Element | None: + ) -> nodes.reference | None: assert typ in {'eq', 'numref'} result = self.equations.get(target) if result: @@ -126,7 +126,7 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, - ) -> list[tuple[str, Element]]: + ) -> list[tuple[str, nodes.reference]]: refnode = self.resolve_xref(env, fromdocname, builder, 'eq', target, node, contnode) if refnode is None: return [] diff --git a/sphinx/domains/python/__init__.py b/sphinx/domains/python/__init__.py index eaa3b158d38..a1285bd3f21 100644 --- a/sphinx/domains/python/__init__.py +++ b/sphinx/domains/python/__init__.py @@ -828,7 +828,7 @@ def find_obj(self, env: BuildEnvironment, modname: str, classname: str, def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, type: str, target: str, node: pending_xref, contnode: Element, - ) -> Element | None: + ) -> nodes.reference | None: modname = node.get('py:module') clsname = node.get('py:class') searchmode = 1 if node.hasattr('refspecific') else 0 @@ -875,10 +875,10 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, - ) -> list[tuple[str, Element]]: + ) -> list[tuple[str, nodes.reference]]: modname = node.get('py:module') clsname = node.get('py:class') - results: list[tuple[str, Element]] = [] + results: list[tuple[str, nodes.reference]] = [] # always search in "refspecific" mode with the :any: role matches = self.find_obj(env, modname, clsname, target, None, 1) @@ -910,7 +910,7 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Bui return results def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, - contnode: Node) -> Element: + contnode: Node) -> nodes.reference: # get additional info for modules module: ModuleEntry = self.modules[name] title_parts = [name] diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index f2ada04e1fe..7dbb7815883 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -18,6 +18,7 @@ if TYPE_CHECKING: from collections.abc import Iterator, Set + from docutils import nodes from docutils.nodes import Element from sphinx.addnodes import desc_signature, pending_xref @@ -262,7 +263,7 @@ def merge_domaindata(self, docnames: Set[str], otherdata: dict[str, Any]) -> Non def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element, - ) -> Element | None: + ) -> nodes.reference | None: objtypes = self.objtypes_for_role(typ) if not objtypes: return None @@ -276,8 +277,8 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, contnode: Element, - ) -> list[tuple[str, Element]]: - results: list[tuple[str, Element]] = [] + ) -> list[tuple[str, nodes.reference]]: + results: list[tuple[str, nodes.reference]] = [] for objtype in self.object_types: result = self.objects.get((objtype, target)) if result: diff --git a/sphinx/domains/std/__init__.py b/sphinx/domains/std/__init__.py index 470a1c05428..af467c0d8cc 100644 --- a/sphinx/domains/std/__init__.py +++ b/sphinx/domains/std/__init__.py @@ -845,10 +845,11 @@ def add_program_option(self, program: str | None, name: str, self.progoptions[program, name] = (docname, labelid) def build_reference_node(self, fromdocname: str, builder: Builder, docname: str, - labelid: str, sectname: str, rolename: str, **options: Any, - ) -> Element: - nodeclass = options.pop('nodeclass', nodes.reference) - newnode = nodeclass('', '', internal=True, **options) + labelid: str, sectname: str, rolename: str, *, + node_class: type[nodes.reference] = nodes.reference, + **options: Any, + ) -> nodes.reference: + newnode = node_class('', '', internal=True, **options) innernode = nodes.inline(sectname, sectname) if innernode.get('classes') is not None: innernode['classes'].append('std') @@ -871,11 +872,11 @@ def build_reference_node(self, fromdocname: str, builder: Builder, docname: str, def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, contnode: Element, - ) -> Element | None: + ) -> nodes.reference | None: if typ == 'ref': resolver = self._resolve_ref_xref elif typ == 'numref': - resolver = self._resolve_numref_xref + resolver = self._resolve_numref_xref # type: ignore[assignment] elif typ == 'keyword': resolver = self._resolve_keyword_xref elif typ == 'doc': @@ -891,7 +892,7 @@ def resolve_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder def _resolve_ref_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, node: pending_xref, - contnode: Element) -> Element | None: + contnode: Element) -> nodes.reference | None: if node['refexplicit']: # reference to anonymous label; the reference uses # the supplied link caption @@ -909,7 +910,8 @@ def _resolve_ref_xref(self, env: BuildEnvironment, fromdocname: str, def _resolve_numref_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, - node: pending_xref, contnode: Element) -> Element | None: + node: pending_xref, contnode: Element + ) -> nodes.reference | Element | None: if target in self.labels: docname, labelid, figname = self.labels.get(target, ('', '', '')) else: @@ -968,12 +970,12 @@ def _resolve_numref_xref(self, env: BuildEnvironment, fromdocname: str, return self.build_reference_node(fromdocname, builder, docname, labelid, newtitle, 'numref', - nodeclass=addnodes.number_reference, + node_class=addnodes.number_reference, title=title) def _resolve_keyword_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, - node: pending_xref, contnode: Element) -> Element | None: + node: pending_xref, contnode: Element) -> nodes.reference | None: # keywords are oddballs: they are referenced by named labels docname, labelid, _ = self.labels.get(target, ('', '', '')) if not docname: @@ -983,7 +985,7 @@ def _resolve_keyword_xref(self, env: BuildEnvironment, fromdocname: str, def _resolve_doc_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, - node: pending_xref, contnode: Element) -> Element | None: + node: pending_xref, contnode: Element) -> nodes.reference | None: # directly reference to document by source name; can be absolute or relative refdoc = node.get('refdoc', fromdocname) docname = docname_join(refdoc, node['reftarget']) @@ -1000,7 +1002,7 @@ def _resolve_doc_xref(self, env: BuildEnvironment, fromdocname: str, def _resolve_option_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, - node: pending_xref, contnode: Element) -> Element | None: + node: pending_xref, contnode: Element) -> nodes.reference | None: progname = node.get('std:program') target = target.strip() docname, labelid = self.progoptions.get((progname, target), ('', '')) @@ -1033,7 +1035,7 @@ def _resolve_option_xref(self, env: BuildEnvironment, fromdocname: str, def _resolve_term_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, - node: pending_xref, contnode: Element) -> Element | None: + node: pending_xref, contnode: Element) -> nodes.reference | None: result = self._resolve_obj_xref(env, fromdocname, builder, typ, target, node, contnode) if result: @@ -1048,7 +1050,7 @@ def _resolve_term_xref(self, env: BuildEnvironment, fromdocname: str, def _resolve_obj_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, typ: str, target: str, - node: pending_xref, contnode: Element) -> Element | None: + node: pending_xref, contnode: Element) -> nodes.reference | None: objtypes = self.objtypes_for_role(typ) or [] for objtype in objtypes: if (objtype, target) in self.objects: @@ -1063,8 +1065,8 @@ def _resolve_obj_xref(self, env: BuildEnvironment, fromdocname: str, def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, builder: Builder, target: str, node: pending_xref, - contnode: Element) -> list[tuple[str, Element]]: - results: list[tuple[str, Element]] = [] + contnode: Element) -> list[tuple[str, nodes.reference]]: + results: list[tuple[str, nodes.reference]] = [] ltarget = target.lower() # :ref: lowercases its target automatically for role in ('ref', 'option'): # do not try "keyword" res = self.resolve_xref(env, fromdocname, builder, role, diff --git a/sphinx/transforms/post_transforms/__init__.py b/sphinx/transforms/post_transforms/__init__.py index e642a95b134..cb590b77a10 100644 --- a/sphinx/transforms/post_transforms/__init__.py +++ b/sphinx/transforms/post_transforms/__init__.py @@ -139,7 +139,7 @@ def resolve_anyref( """Resolve reference generated by the "any" role.""" stddomain = self.env.domains.standard_domain target = node['reftarget'] - results: list[tuple[str, Element]] = [] + results: list[tuple[str, nodes.reference]] = [] # first, try resolving as :doc: doc_ref = stddomain.resolve_xref( self.env, refdoc, self.app.builder, 'doc', target, node, contnode From db1a190c588b38c638676886fc4e1eadc2549549 Mon Sep 17 00:00:00 2001 From: Rafael Fontenelle Date: Sat, 2 Nov 2024 20:47:24 -0300 Subject: [PATCH 17/39] Use default locale_dirs value (#13079) --- doc/conf.py | 1 - 1 file changed, 1 deletion(-) diff --git a/doc/conf.py b/doc/conf.py index e7976f59a87..9c8aa877075 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -173,7 +173,6 @@ } # Sphinx document translation with sphinx gettext feature uses these settings: -locale_dirs = ['locale/'] gettext_compact = False nitpick_ignore = { From 1094556afb5740d6378c1ae2742af2f0bf17873b Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 3 Nov 2024 02:50:31 +0000 Subject: [PATCH 18/39] Bump Ruff to 0.7.2 --- pyproject.toml | 2 +- sphinx/_cli/__init__.py | 2 +- sphinx/builders/__init__.py | 2 +- sphinx/builders/latex/__init__.py | 5 +- sphinx/builders/manpage.py | 7 +-- sphinx/cmd/build.py | 8 ++- sphinx/cmd/make_mode.py | 2 +- sphinx/cmd/quickstart.py | 2 +- sphinx/domains/c/__init__.py | 2 +- sphinx/domains/c/_parser.py | 2 +- sphinx/domains/cpp/__init__.py | 2 +- sphinx/domains/cpp/_parser.py | 2 +- sphinx/domains/python/__init__.py | 6 +-- sphinx/domains/rst.py | 8 +-- sphinx/domains/std/__init__.py | 10 ++-- sphinx/environment/adapters/toctree.py | 2 +- sphinx/ext/autodoc/importer.py | 2 +- sphinx/ext/intersphinx/_load.py | 2 +- sphinx/ext/intersphinx/_resolve.py | 4 +- sphinx/ext/viewcode.py | 4 +- sphinx/testing/path.py | 2 +- sphinx/testing/util.py | 12 ++--- sphinx/util/docfields.py | 2 +- sphinx/util/docutils.py | 4 +- sphinx/writers/html5.py | 8 +-- sphinx/writers/latex.py | 4 +- sphinx/writers/manpage.py | 2 +- sphinx/writers/texinfo.py | 17 +++--- tests/test_builders/test_build_html.py | 7 +-- .../test_builders/test_build_html_5_output.py | 3 +- tests/test_builders/test_build_latex.py | 36 +++++-------- tests/test_builders/test_build_text.py | 22 +------- tests/test_config/test_config.py | 9 ++-- tests/test_directives/test_directive_code.py | 7 +-- tests/test_directives/test_directive_only.py | 12 ++--- tests/test_domains/test_domain_c.py | 6 +-- tests/test_domains/test_domain_cpp.py | 11 ++-- tests/test_domains/test_domain_py_pyobject.py | 5 +- tests/test_extensions/test_ext_apidoc.py | 12 ++--- tests/test_extensions/test_ext_autodoc.py | 12 ++--- tests/test_extensions/test_ext_autosummary.py | 27 +++------- tests/test_extensions/test_ext_graphviz.py | 7 +-- .../test_ext_napoleon_docstring.py | 6 +-- tests/test_intl/test_intl.py | 54 +++++++++---------- tests/test_markup/test_markup.py | 5 +- tests/test_pycode/test_pycode_parser.py | 5 +- tests/test_search.py | 6 +-- .../test_transforms_reorder_nodes.py | 9 +--- 48 files changed, 150 insertions(+), 238 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9c46ba94694..4eb5269ed2e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,7 +81,7 @@ docs = [ ] lint = [ "flake8>=6.0", - "ruff==0.7.0", + "ruff==0.7.2", "mypy==1.13.0", "sphinx-lint>=0.9", "types-colorama==0.4.15.20240311", diff --git a/sphinx/_cli/__init__.py b/sphinx/_cli/__init__.py index 3160f08373c..270d9210e33 100644 --- a/sphinx/_cli/__init__.py +++ b/sphinx/_cli/__init__.py @@ -170,7 +170,7 @@ def _format_metavar( def error(self, message: str) -> NoReturn: sys.stderr.write( __( - '{0}: error: {1}\n' "Run '{0} --help' for information" # NoQA: COM812 + "{0}: error: {1}\nRun '{0} --help' for information" # NoQA: COM812 ).format(self.prog, message) ) raise SystemExit(2) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index 21b73f873a0..bf2f3f86080 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -203,7 +203,7 @@ def post_process_images(self, doctree: Node) -> None: image_uri = images.get_original_image_uri(node['uri']) if mimetypes: logger.warning( - __('a suitable image for %s builder not found: ' '%s (%s)'), + __('a suitable image for %s builder not found: %s (%s)'), self.name, mimetypes, image_uri, diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index db7adc5b0d7..19ea60e1290 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -166,10 +166,7 @@ def init_document_data(self) -> None: docname = entry[0] if docname not in self.env.all_docs: logger.warning( - __( - '"latex_documents" config value references unknown ' - 'document %s' - ), + __('"latex_documents" config value references unknown document %s'), docname, ) continue diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index 2e24486d174..4535776b9cb 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -44,10 +44,7 @@ class ManualPageBuilder(Builder): def init(self) -> None: if not self.config.man_pages: logger.warning( - __( - 'no "man_pages" config value found; no manual pages ' - 'will be written' - ) + __('no "man_pages" config value found; no manual pages will be written') ) def get_outdated_docs(self) -> str | list[str]: @@ -73,7 +70,7 @@ def write_documents(self, _docnames: Set[str]) -> None: docname, name, description, authors, section = info if docname not in self.env.all_docs: logger.warning( - __('"man_pages" config value references unknown ' 'document %s'), + __('"man_pages" config value references unknown document %s'), docname, ) continue diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index db76bc3ad85..37f3f26c453 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -217,14 +217,14 @@ def get_parser() -> argparse.ArgumentParser: '-a', action='store_true', dest='force_all', - help=__('write all files (default: only write new and ' 'changed files)'), + help=__('write all files (default: only write new and changed files)'), ) group.add_argument( '--fresh-env', '-E', action='store_true', dest='freshenv', - help=__("don't use a saved environment, always read " 'all files'), + help=__("don't use a saved environment, always read all files"), ) group = parser.add_argument_group(__('path options')) @@ -243,9 +243,7 @@ def get_parser() -> argparse.ArgumentParser: '-c', metavar='PATH', dest='confdir', - help=__( - 'directory for the configuration file (conf.py) ' '(default: SOURCE_DIR)' - ), + help=__('directory for the configuration file (conf.py) (default: SOURCE_DIR)'), ) group = parser.add_argument_group('build configuration options') diff --git a/sphinx/cmd/make_mode.py b/sphinx/cmd/make_mode.py index 00c8744868d..5966b628a3e 100644 --- a/sphinx/cmd/make_mode.py +++ b/sphinx/cmd/make_mode.py @@ -49,7 +49,7 @@ ( '', 'doctest', - 'to run all doctests embedded in the documentation ' '(if enabled)', + 'to run all doctests embedded in the documentation (if enabled)', ), ('', 'coverage', 'to run coverage check of the documentation (if enabled)'), ('', 'clean', 'to remove everything in the build directory'), diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index 1176dc14b40..18d06c39f1d 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -507,7 +507,7 @@ def write_file(fpath: str, content: str, newline: str | None = None) -> None: end='', ) if d['makefile'] or d['batchfile']: - print(__('Use the Makefile to build the docs, like so:\n' ' make builder')) + print(__('Use the Makefile to build the docs, like so:\n make builder')) else: print( __( diff --git a/sphinx/domains/c/__init__.py b/sphinx/domains/c/__init__.py index 23729f09166..28d77227eee 100644 --- a/sphinx/domains/c/__init__.py +++ b/sphinx/domains/c/__init__.py @@ -837,7 +837,7 @@ def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]: objectType = symbol.declaration.objectType docname = symbol.docname newestId = symbol.declaration.get_newest_id() - yield (name, dispname, objectType, docname, newestId, 1) + yield name, dispname, objectType, docname, newestId, 1 def setup(app: Sphinx) -> ExtensionMetadata: diff --git a/sphinx/domains/c/_parser.py b/sphinx/domains/c/_parser.py index 2eeb4e7ef47..d6afb24ade0 100644 --- a/sphinx/domains/c/_parser.py +++ b/sphinx/domains/c/_parser.py @@ -465,7 +465,7 @@ def _parse_expression_fallback( brackets = {'(': ')', '{': '}', '[': ']'} symbols: list[str] = [] while not self.eof: - if (len(symbols) == 0 and self.current_char in end): + if len(symbols) == 0 and self.current_char in end: break if self.current_char in brackets: symbols.append(brackets[self.current_char]) diff --git a/sphinx/domains/cpp/__init__.py b/sphinx/domains/cpp/__init__.py index b038a2efd2d..743aa0d1018 100644 --- a/sphinx/domains/cpp/__init__.py +++ b/sphinx/domains/cpp/__init__.py @@ -1142,7 +1142,7 @@ def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]: objectType = symbol.declaration.objectType docname = symbol.docname newestId = symbol.declaration.get_newest_id() - yield (name, dispname, objectType, docname, newestId, 1) + yield name, dispname, objectType, docname, newestId, 1 def get_full_qualified_name(self, node: Element) -> str | None: target = node.get('reftarget', None) diff --git a/sphinx/domains/cpp/_parser.py b/sphinx/domains/cpp/_parser.py index 5eedd078d9b..09f9e69e0cd 100644 --- a/sphinx/domains/cpp/_parser.py +++ b/sphinx/domains/cpp/_parser.py @@ -795,7 +795,7 @@ def _parse_expression_fallback(self, end: list[str], brackets = {'(': ')', '{': '}', '[': ']', '<': '>'} symbols: list[str] = [] while not self.eof: - if (len(symbols) == 0 and self.current_char in end): + if len(symbols) == 0 and self.current_char in end: break if self.current_char in brackets: symbols.append(brackets[self.current_char]) diff --git a/sphinx/domains/python/__init__.py b/sphinx/domains/python/__init__.py index a1285bd3f21..24dfc2a5756 100644 --- a/sphinx/domains/python/__init__.py +++ b/sphinx/domains/python/__init__.py @@ -927,14 +927,14 @@ def _make_module_refnode(self, builder: Builder, fromdocname: str, name: str, def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]: for modname, mod in self.modules.items(): - yield (modname, modname, 'module', mod.docname, mod.node_id, 0) + yield modname, modname, 'module', mod.docname, mod.node_id, 0 for refname, obj in self.objects.items(): if obj.objtype != 'module': # modules are already handled if obj.aliased: # aliased names are not full-text searchable. - yield (refname, refname, obj.objtype, obj.docname, obj.node_id, -1) + yield refname, refname, obj.objtype, obj.docname, obj.node_id, -1 else: - yield (refname, refname, obj.objtype, obj.docname, obj.node_id, 1) + yield refname, refname, obj.objtype, obj.docname, obj.node_id, 1 def get_full_qualified_name(self, node: Element) -> str | None: modname = node.get('py:module') diff --git a/sphinx/domains/rst.py b/sphinx/domains/rst.py index 7dbb7815883..4bed18e0c9b 100644 --- a/sphinx/domains/rst.py +++ b/sphinx/domains/rst.py @@ -99,15 +99,15 @@ def parse_directive(d: str) -> tuple[str, str]: dir = d.strip() if not dir.startswith('.'): # Assume it is a directive without syntax - return (dir, '') + return dir, '' m = dir_sig_re.match(dir) if not m: - return (dir, '') + return dir, '' parsed_dir, parsed_args = m.groups() if parsed_args.strip(): - return (parsed_dir.strip(), ' ' + parsed_args.strip()) + return parsed_dir.strip(), ' ' + parsed_args.strip() else: - return (parsed_dir.strip(), '') + return parsed_dir.strip(), '' class ReSTDirective(ReSTMarkup): diff --git a/sphinx/domains/std/__init__.py b/sphinx/domains/std/__init__.py index af467c0d8cc..0e214c92a5b 100644 --- a/sphinx/domains/std/__init__.py +++ b/sphinx/domains/std/__init__.py @@ -1089,23 +1089,23 @@ def resolve_any_xref(self, env: BuildEnvironment, fromdocname: str, def get_objects(self) -> Iterator[tuple[str, str, str, str, str, int]]: # handle the special 'doc' reference here for doc in self.env.all_docs: - yield (doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1) + yield doc, clean_astext(self.env.titles[doc]), 'doc', doc, '', -1 for (prog, option), info in self.progoptions.items(): if prog: fullname = f'{prog}.{option}' - yield (fullname, fullname, 'cmdoption', info[0], info[1], 1) + yield fullname, fullname, 'cmdoption', info[0], info[1], 1 else: - yield (option, option, 'cmdoption', info[0], info[1], 1) + yield option, option, 'cmdoption', info[0], info[1], 1 for (type, name), info in self.objects.items(): yield (name, name, type, info[0], info[1], self.object_types[type].attrs['searchprio']) for name, (docname, labelid, sectionname) in self.labels.items(): - yield (name, sectionname, 'label', docname, labelid, -1) + yield name, sectionname, 'label', docname, labelid, -1 # add anonymous-only labels as well non_anon_labels = set(self.labels) for name, (docname, labelid) in self.anonlabels.items(): if name not in non_anon_labels: - yield (name, name, 'label', docname, labelid, -1) + yield name, name, 'label', docname, labelid, -1 def get_type_name(self, type: ObjType, primary: bool = False) -> str: # never prepend "Default" diff --git a/sphinx/environment/adapters/toctree.py b/sphinx/environment/adapters/toctree.py index edd873b5f44..3079c7dc543 100644 --- a/sphinx/environment/adapters/toctree.py +++ b/sphinx/environment/adapters/toctree.py @@ -315,7 +315,7 @@ def _toctree_entry( else: if ref in parents: logger.warning( - __('circular toctree references ' 'detected, ignoring: %s <- %s'), + __('circular toctree references detected, ignoring: %s <- %s'), ref, ' <- '.join(parents), location=ref, diff --git a/sphinx/ext/autodoc/importer.py b/sphinx/ext/autodoc/importer.py index 4311d428870..ceefdcd9f5f 100644 --- a/sphinx/ext/autodoc/importer.py +++ b/sphinx/ext/autodoc/importer.py @@ -67,7 +67,7 @@ def should_ignore(name: str, value: Any) -> bool: def query(name: str, defining_class: type) -> tuple[str, type, Any] | None: value = attrgetter(enum_class, name, sentinel) if value is not sentinel: - return (name, defining_class, value) + return name, defining_class, value return None # attributes defined on a parent type, possibly shadowed later by diff --git a/sphinx/ext/intersphinx/_load.py b/sphinx/ext/intersphinx/_load.py index 27b11673465..dde2e34e1c2 100644 --- a/sphinx/ext/intersphinx/_load.py +++ b/sphinx/ext/intersphinx/_load.py @@ -272,7 +272,7 @@ def _fetch_inventory_group( else: issues = '\n'.join(f[0] % f[1:] for f in failures) LOGGER.warning( - __('failed to reach any of the inventories ' 'with the following issues:') + __('failed to reach any of the inventories with the following issues:') + '\n' + issues ) diff --git a/sphinx/ext/intersphinx/_resolve.py b/sphinx/ext/intersphinx/_resolve.py index 9387d1e1096..be279b8c350 100644 --- a/sphinx/ext/intersphinx/_resolve.py +++ b/sphinx/ext/intersphinx/_resolve.py @@ -516,9 +516,9 @@ def get_role_name(self, name: str) -> tuple[str, str] | None: return None if domain and self.is_existent_role(domain, role): - return (domain, role) + return domain, role elif self.is_existent_role('std', role): - return ('std', role) + return 'std', role else: return None diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 9991cf5d426..91ba1f049ed 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -311,7 +311,7 @@ def collect_pages(app: Sphinx) -> Iterator[tuple[str, dict[str, Any], str]]: 'body': (_('

Source code for %s

') % modname + '\n'.join(lines)), } - yield (pagename, context, 'page.html') + yield pagename, context, 'page.html' if not modnames: return @@ -339,7 +339,7 @@ def collect_pages(app: Sphinx) -> Iterator[tuple[str, dict[str, Any], str]]: ''.join(html)), } - yield (posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html') + yield posixpath.join(OUTPUT_DIRNAME, 'index'), context, 'page.html' def setup(app: Sphinx) -> ExtensionMetadata: diff --git a/sphinx/testing/path.py b/sphinx/testing/path.py index 9792dcb7479..b469588ea6d 100644 --- a/sphinx/testing/path.py +++ b/sphinx/testing/path.py @@ -13,7 +13,7 @@ from collections.abc import Callable warnings.warn( - "'sphinx.testing.path' is deprecated. " "Use 'os.path' or 'pathlib' instead.", + "'sphinx.testing.path' is deprecated. Use 'os.path' or 'pathlib' instead.", RemovedInSphinx90Warning, stacklevel=2, ) diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 4d221133ffb..d95ffb46bc3 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -40,9 +40,9 @@ def assert_node(node: Node, cls: Any = None, xpath: str = '', **kwargs: Any) -> assert ( isinstance(node, nodes.Element) ), f'The node{xpath} does not have any children' # fmt: skip - assert ( - len(node) == 1 - ), f'The node{xpath} has {len(node)} child nodes, not one' + assert len(node) == 1, ( + f'The node{xpath} has {len(node)} child nodes, not one' + ) assert_node(node[0], cls[1:], xpath=xpath + '[0]', **kwargs) elif isinstance(cls, tuple): assert ( @@ -71,9 +71,9 @@ def assert_node(node: Node, cls: Any = None, xpath: str = '', **kwargs: Any) -> if (key := key.replace('_', '-')) not in node: msg = f'The node{xpath} does not have {key!r} attribute: {node!r}' raise AssertionError(msg) - assert ( - node[key] == value - ), f'The node{xpath}[{key}] is not {value!r}: {node[key]!r}' + assert node[key] == value, ( + f'The node{xpath}[{key}] is not {value!r}: {node[key]!r}' + ) # keep this to restrict the API usage and to have a correct return type diff --git a/sphinx/util/docfields.py b/sphinx/util/docfields.py index bb8142570f0..33fda6f5dcd 100644 --- a/sphinx/util/docfields.py +++ b/sphinx/util/docfields.py @@ -132,7 +132,7 @@ def make_xrefs( ] def make_entry(self, fieldarg: str, content: list[Node]) -> tuple[str, list[Node]]: - return (fieldarg, content) + return fieldarg, content def make_field( self, diff --git a/sphinx/util/docutils.py b/sphinx/util/docutils.py index 4f085bfeee3..30c87595f31 100644 --- a/sphinx/util/docutils.py +++ b/sphinx/util/docutils.py @@ -716,7 +716,7 @@ def dispatch_visit(self, node: Node) -> None: 3. ``self.unknown_visit()`` """ for node_class in node.__class__.__mro__: - method = getattr(self, 'visit_%s' % (node_class.__name__), None) + method = getattr(self, 'visit_%s' % node_class.__name__, None) if method: method(node) break @@ -733,7 +733,7 @@ def dispatch_departure(self, node: Node) -> None: 3. ``self.unknown_departure()`` """ for node_class in node.__class__.__mro__: - method = getattr(self, 'depart_%s' % (node_class.__name__), None) + method = getattr(self, 'depart_%s' % node_class.__name__, None) if method: method(node) break diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index e002788fc97..b6e342ac119 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -327,9 +327,9 @@ def visit_reference(self, node: Element) -> None: atts['href'] = self.cloak_mailto(atts['href']) self.in_mailto = True else: - assert ( - 'refid' in node - ), 'References must have "refuri" or "refid" attribute.' + assert 'refid' in node, ( + 'References must have "refuri" or "refid" attribute.' + ) atts['href'] = '#' + node['refid'] if not isinstance(node.parent, nodes.TextElement): assert len(node) == 1 and isinstance(node[0], nodes.image) # NoQA: PT018 @@ -379,7 +379,7 @@ def get_secnumber(self, node: Element) -> tuple[int, ...] | None: if isinstance(node.parent, nodes.section): if self.builder.name == 'singlehtml': docname = self.docnames[-1] - anchorname = f"{docname}/#{node.parent['ids'][0]}" + anchorname = f'{docname}/#{node.parent["ids"][0]}' if anchorname not in self.builder.secnumbers: # try first heading which has no anchor anchorname = f'{docname}/' diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index c98135efa7f..a9fbd0161c0 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -2452,7 +2452,7 @@ def visit_math(self, node: Element) -> None: def visit_math_block(self, node: Element) -> None: if node.get('label'): - label = f"equation:{node['docname']}:{node['label']}" + label = f'equation:{node["docname"]}:{node["label"]}' else: label = None @@ -2469,7 +2469,7 @@ def visit_math_block(self, node: Element) -> None: raise nodes.SkipNode def visit_math_reference(self, node: Element) -> None: - label = f"equation:{node['docname']}:{node['target']}" + label = f'equation:{node["docname"]}:{node["target"]}' eqref_format = self.config.math_eqref_format if eqref_format: try: diff --git a/sphinx/writers/manpage.py b/sphinx/writers/manpage.py index 5bfc23481c0..7b7db13961f 100644 --- a/sphinx/writers/manpage.py +++ b/sphinx/writers/manpage.py @@ -117,7 +117,7 @@ def header(self) -> str: ' "%(date)s" "%(version)s" "%(manual_group)s"\n' ) if self._docinfo['subtitle']: - tmpl += '.SH NAME\n' '%(title)s \\- %(subtitle)s\n' + tmpl += '.SH NAME\n%(title)s \\- %(subtitle)s\n' return tmpl % self._docinfo def visit_start_of_file(self, node: Element) -> None: diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 997561b16fd..426b0288390 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -250,9 +250,10 @@ def init_settings(self) -> None: '(%s)' % elements['filename'], self.escape_arg(self.settings.texinfo_dir_description), ) - elements['direntry'] = ( - '@dircategory %s\n' '@direntry\n' '%s' '@end direntry\n' - ) % (self.escape_id(self.settings.texinfo_dir_category), entry) + elements['direntry'] = '@dircategory %s\n@direntry\n%s@end direntry\n' % ( + self.escape_id(self.settings.texinfo_dir_category), + entry, + ) elements['copying'] = COPYING % elements # allow the user to override them all elements.update(self.settings.texinfo_elements) @@ -448,10 +449,10 @@ def _add_detailed_menu(name: str) -> None: for subentry in entries: _add_detailed_menu(subentry) - self.body.append('\n@detailmenu\n' ' --- The Detailed Node Listing ---\n') + self.body.append('\n@detailmenu\n --- The Detailed Node Listing ---\n') for entry in entries: _add_detailed_menu(entry) - self.body.append('\n@end detailmenu\n' '@end menu\n') + self.body.append('\n@end detailmenu\n@end menu\n') def tex_image_length(self, width_str: str) -> str: match = re.match(r'(\d*\.?\d*)\s*(\S*)', width_str) @@ -1119,7 +1120,7 @@ def _visit_named_admonition(self, node: Element) -> None: def depart_admonition(self, node: Element) -> None: self.ensure_eol() - self.body.append('@end quotation\n' '@end cartouche\n') + self.body.append('@end quotation\n@end cartouche\n') visit_attention = _visit_named_admonition depart_attention = depart_admonition @@ -1236,7 +1237,7 @@ def visit_image(self, node: Element) -> None: width = self.tex_image_length(node.get('width', '')) height = self.tex_image_length(node.get('height', '')) alt = self.escape_arg(node.get('alt', '')) - filename = f"{self.elements['filename'][:-5]}-figures/{name}" # type: ignore[index] + filename = f'{self.elements["filename"][:-5]}-figures/{name}' # type: ignore[index] self.body.append(f'\n@image{{{filename},{width},{height},{alt},{ext[1:]}}}\n') def depart_image(self, node: Element) -> None: @@ -1280,7 +1281,7 @@ def visit_substitution_definition(self, node: Element) -> None: def visit_system_message(self, node: Element) -> None: self.body.append( - '\n@verbatim\n' '\n' '@end verbatim\n' % node.astext() + '\n@verbatim\n\n@end verbatim\n' % node.astext() ) raise nodes.SkipNode diff --git a/tests/test_builders/test_build_html.py b/tests/test_builders/test_build_html.py index 1e82e70eaa1..097f895d7c8 100644 --- a/tests/test_builders/test_build_html.py +++ b/tests/test_builders/test_build_html.py @@ -300,9 +300,7 @@ def test_html_raw_directive(app): [ (".//link[@href='_static/persistent.css'][@rel='stylesheet']", '', True), ( - ".//link[@href='_static/default.css']" - "[@rel='stylesheet']" - "[@title='Default']", + ".//link[@href='_static/default.css'][@rel='stylesheet'][@title='Default']", '', True, ), @@ -338,8 +336,7 @@ def test_html_raw_directive(app): True, ), ( - ".//link[@href='_static/more_alternate2.css']" - "[@rel='alternate stylesheet']", + ".//link[@href='_static/more_alternate2.css'][@rel='alternate stylesheet']", '', True, ), diff --git a/tests/test_builders/test_build_html_5_output.py b/tests/test_builders/test_build_html_5_output.py index a0cc15aeffa..a618fae1e45 100644 --- a/tests/test_builders/test_build_html_5_output.py +++ b/tests/test_builders/test_build_html_5_output.py @@ -162,8 +162,7 @@ def checker(nodes: Iterable[Element]) -> Literal[True]: ), ( 'markup.html', - ".//a[@href='#with']" - "[@class='reference internal']/code/span[@class='pre']", + ".//a[@href='#with'][@class='reference internal']/code/span[@class='pre']", '^with$', ), ( diff --git a/tests/test_builders/test_build_latex.py b/tests/test_builders/test_build_latex.py index b6207bae60c..d95c5e4c286 100644 --- a/tests/test_builders/test_build_latex.py +++ b/tests/test_builders/test_build_latex.py @@ -389,8 +389,7 @@ def test_numref(app): print(app.status.getvalue()) print(app.warning.getvalue()) assert ( - '\\hyperref[\\detokenize{index:fig1}]' - '{Fig.\\@ \\ref{\\detokenize{index:fig1}}}' + '\\hyperref[\\detokenize{index:fig1}]{Fig.\\@ \\ref{\\detokenize{index:fig1}}}' ) in result assert ( '\\hyperref[\\detokenize{baz:fig22}]{Figure\\ref{\\detokenize{baz:fig22}}}' @@ -400,8 +399,7 @@ def test_numref(app): '{Table \\ref{\\detokenize{index:table-1}}}' ) in result assert ( - '\\hyperref[\\detokenize{baz:table22}]' - '{Table:\\ref{\\detokenize{baz:table22}}}' + '\\hyperref[\\detokenize{baz:table22}]{Table:\\ref{\\detokenize{baz:table22}}}' ) in result assert ( '\\hyperref[\\detokenize{index:code-1}]' @@ -462,8 +460,7 @@ def test_numref_with_prefix1(app): assert '\\ref{\\detokenize{index:code-1}}' in result assert '\\ref{\\detokenize{baz:code22}}' in result assert ( - '\\hyperref[\\detokenize{index:fig1}]' - '{Figure:\\ref{\\detokenize{index:fig1}}}' + '\\hyperref[\\detokenize{index:fig1}]{Figure:\\ref{\\detokenize{index:fig1}}}' ) in result assert ( '\\hyperref[\\detokenize{baz:fig22}]{Figure\\ref{\\detokenize{baz:fig22}}}' @@ -473,8 +470,7 @@ def test_numref_with_prefix1(app): '{Tab\\_\\ref{\\detokenize{index:table-1}}}' ) in result assert ( - '\\hyperref[\\detokenize{baz:table22}]' - '{Table:\\ref{\\detokenize{baz:table22}}}' + '\\hyperref[\\detokenize{baz:table22}]{Table:\\ref{\\detokenize{baz:table22}}}' ) in result assert ( '\\hyperref[\\detokenize{index:code-1}]' @@ -540,8 +536,7 @@ def test_numref_with_prefix2(app): '{Tab\\_\\ref{\\detokenize{index:table-1}}:}' ) in result assert ( - '\\hyperref[\\detokenize{baz:table22}]' - '{Table:\\ref{\\detokenize{baz:table22}}}' + '\\hyperref[\\detokenize{baz:table22}]{Table:\\ref{\\detokenize{baz:table22}}}' ) in result assert ( '\\hyperref[\\detokenize{index:code-1}]{Code\\sphinxhyphen{}\\ref{\\detokenize{index:code-1}} ' @@ -552,8 +547,7 @@ def test_numref_with_prefix2(app): '{Code\\sphinxhyphen{}\\ref{\\detokenize{baz:code22}}}' ) in result assert ( - '\\hyperref[\\detokenize{foo:foo}]' - '{SECTION\\_\\ref{\\detokenize{foo:foo}}\\_}' + '\\hyperref[\\detokenize{foo:foo}]{SECTION\\_\\ref{\\detokenize{foo:foo}}\\_}' ) in result assert ( '\\hyperref[\\detokenize{bar:bar-a}]' @@ -590,8 +584,7 @@ def test_numref_with_language_ja(app): print(app.status.getvalue()) print(app.warning.getvalue()) assert ( - '\\hyperref[\\detokenize{index:fig1}]' - '{\u56f3 \\ref{\\detokenize{index:fig1}}}' + '\\hyperref[\\detokenize{index:fig1}]{\u56f3 \\ref{\\detokenize{index:fig1}}}' ) in result assert ( '\\hyperref[\\detokenize{baz:fig22}]{Figure\\ref{\\detokenize{baz:fig22}}}' @@ -601,8 +594,7 @@ def test_numref_with_language_ja(app): '{\u8868 \\ref{\\detokenize{index:table-1}}}' ) in result assert ( - '\\hyperref[\\detokenize{baz:table22}]' - '{Table:\\ref{\\detokenize{baz:table22}}}' + '\\hyperref[\\detokenize{baz:table22}]{Table:\\ref{\\detokenize{baz:table22}}}' ) in result assert ( '\\hyperref[\\detokenize{index:code-1}]' @@ -937,8 +929,7 @@ def test_footnote(app): 'numbered\n%\n\\end{footnote}' ) in result assert ( - '\\begin{footnote}[2]\\sphinxAtStartFootnote\nauto numbered\n%\n' - '\\end{footnote}' + '\\begin{footnote}[2]\\sphinxAtStartFootnote\nauto numbered\n%\n\\end{footnote}' ) in result assert ( '\\begin{footnote}[3]\\sphinxAtStartFootnote\nnamed\n%\n\\end{footnote}' @@ -1880,8 +1871,7 @@ def test_latex_nested_enumerated_list(app): result = (app.outdir / 'projectnamenotset.tex').read_text(encoding='utf8') assert ( - '\\sphinxsetlistlabels{\\arabic}{enumi}{enumii}{}{.}%\n' - '\\setcounter{enumi}{4}\n' + '\\sphinxsetlistlabels{\\arabic}{enumi}{enumii}{}{.}%\n\\setcounter{enumi}{4}\n' ) in result assert ( '\\sphinxsetlistlabels{\\alph}{enumii}{enumiii}{}{.}%\n' @@ -2201,9 +2191,9 @@ def test_duplicated_labels_before_module(app): ): tex_label_name = 'index:' + rst_label_name.replace('_', '-') tex_label_code = r'\phantomsection\label{\detokenize{%s}}' % tex_label_name - assert ( - content.count(tex_label_code) == 1 - ), f'duplicated label: {tex_label_name!r}' + assert content.count(tex_label_code) == 1, ( + f'duplicated label: {tex_label_name!r}' + ) tested_labels.add(tex_label_code) # ensure that we did not forget any label to check diff --git a/tests/test_builders/test_build_text.py b/tests/test_builders/test_build_text.py index 8007ea23157..2023f6f9a53 100644 --- a/tests/test_builders/test_build_text.py +++ b/tests/test_builders/test_build_text.py @@ -44,14 +44,7 @@ def test_lineblock(app): # regression test for #1109: need empty line after line block app.build() result = (app.outdir / 'lineblock.txt').read_text(encoding='utf8') - expect = ( - '* one\n' - '\n' - ' line-block 1\n' - ' line-block 2\n' - '\n' - 'followed paragraph.\n' - ) + expect = '* one\n\n line-block 1\n line-block 2\n\nfollowed paragraph.\n' assert result == expect @@ -265,16 +258,5 @@ def test_secnums(app): assert lines[5] == '' assert lines[6] == ' * Sub Bb' doc2 = (app.outdir / 'doc2.txt').read_text(encoding='utf8') - expect = ( - 'Section B\n' - '*********\n' - '\n' - '\n' - 'Sub Ba\n' - '======\n' - '\n' - '\n' - 'Sub Bb\n' - '======\n' - ) + expect = 'Section B\n*********\n\n\nSub Ba\n======\n\n\nSub Bb\n======\n' assert doc2 == expect diff --git a/tests/test_config/test_config.py b/tests/test_config/test_config.py index f1d6c12a0fd..78b35b83ab1 100644 --- a/tests/test_config/test_config.py +++ b/tests/test_config/test_config.py @@ -463,16 +463,15 @@ def test_config_eol(logger, tmp_path): ) def test_builtin_conf(app): warnings = app.warning.getvalue() - assert ( - 'root_doc' - ) in warnings, 'override on builtin "root_doc" should raise a type warning' + assert 'root_doc' in warnings, ( + 'override on builtin "root_doc" should raise a type warning' + ) assert 'language' not in warnings, ( 'explicitly permitted override on builtin "language" should NOT raise ' 'a type warning' ) assert 'primary_domain' not in warnings, ( - 'override to None on builtin "primary_domain" should NOT raise a type ' - 'warning' + 'override to None on builtin "primary_domain" should NOT raise a type warning' ) diff --git a/tests/test_directives/test_directive_code.py b/tests/test_directives/test_directive_code.py index dacf8a3b334..0b81b65c1d8 100644 --- a/tests/test_directives/test_directive_code.py +++ b/tests/test_directives/test_directive_code.py @@ -536,12 +536,7 @@ def test_literalinclude_pydecorators(app): assert actual == expect actual = literal_include[2].text - expect = ( - '@function_decorator\n' - '@other_decorator()\n' - 'def the_function():\n' - ' pass\n' - ) + expect = '@function_decorator\n@other_decorator()\ndef the_function():\n pass\n' assert actual == expect diff --git a/tests/test_directives/test_directive_only.py b/tests/test_directives/test_directive_only.py index de9230c04da..297f304dfdb 100644 --- a/tests/test_directives/test_directive_only.py +++ b/tests/test_directives/test_directive_only.py @@ -28,9 +28,9 @@ def testsects(prefix, sects, indent=0): assert prefix == parent_num, f'Section out of place: {title!r}' for i, subsect in enumerate(sects[1]): num = subsect[0].split()[0] - assert re.match( - '[0-9]+[.0-9]*[.]', num - ), f'Unnumbered section: {subsect[0]!r}' + assert re.match('[0-9]+[.0-9]*[.]', num), ( + f'Unnumbered section: {subsect[0]!r}' + ) testsects(prefix + str(i + 1) + '.', subsect, indent + 4) app.build(filenames=[app.srcdir / 'only.rst']) @@ -41,6 +41,6 @@ def testsects(prefix, sects, indent=0): for i, s in enumerate(parts): testsects(str(i + 1) + '.', s, 4) actual_headings = '\n'.join(p[0] for p in parts) - assert ( - len(parts) == 4 - ), f'Expected 4 document level headings, got:\n{actual_headings}' + assert len(parts) == 4, ( + f'Expected 4 document level headings, got:\n{actual_headings}' + ) diff --git a/tests/test_domains/test_domain_c.py b/tests/test_domains/test_domain_c.py index 81451f29845..d90bcd0b7b5 100644 --- a/tests/test_domains/test_domain_c.py +++ b/tests/test_domains/test_domain_c.py @@ -923,11 +923,7 @@ def test_domain_c_parse_cvar(app): @pytest.mark.sphinx('html', testroot='root') def test_domain_c_parse_no_index_entry(app): - text = ( - '.. c:function:: void f()\n' - '.. c:function:: void g()\n' - ' :no-index-entry:\n' - ) + text = '.. c:function:: void f()\n.. c:function:: void g()\n :no-index-entry:\n' doctree = restructuredtext.parse(app, text) assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) assert_node( diff --git a/tests/test_domains/test_domain_cpp.py b/tests/test_domains/test_domain_cpp.py index 6a7a7778d8c..a177991a535 100644 --- a/tests/test_domains/test_domain_cpp.py +++ b/tests/test_domains/test_domain_cpp.py @@ -159,7 +159,7 @@ def make_id_v2(): id1 = make_id_v1() id2 = make_id_v2() - input = f"void f({type_.replace(' ', ' ')} arg)" + input = f'void f({type_.replace(" ", " ")} arg)' output = f'void f({type_} arg)' check('function', input, {1: id1, 2: id2}, output=output) @@ -167,7 +167,7 @@ def make_id_v2(): # try permutations of all components tcs = type_.split() for p in itertools.permutations(tcs): - input = f"void f({' '.join(p)} arg)" + input = f'void f({" ".join(p)} arg)' check('function', input, {1: id1, 2: id2}) @@ -1347,8 +1347,7 @@ def test_domain_cpp_ast_template_args(): # from breathe#218 check( 'function', - 'template ' - 'void allow(F *f, typename func::type tt)', + 'template void allow(F *f, typename func::type tt)', { 2: 'I0E5allowP1FN4funcI1F1BXG != 1EE4typeE', 3: 'I0E5allowP1FN4funcI1F1BXne1GL1EEE4typeE', @@ -1906,9 +1905,7 @@ def test_domain_cpp_build_intersphinx(tmp_path, app): @pytest.mark.sphinx('html', testroot='root') def test_domain_cpp_parse_no_index_entry(app): text = ( - '.. cpp:function:: void f()\n' - '.. cpp:function:: void g()\n' - ' :no-index-entry:\n' + '.. cpp:function:: void f()\n.. cpp:function:: void g()\n :no-index-entry:\n' ) doctree = restructuredtext.parse(app, text) assert_node(doctree, (addnodes.index, desc, addnodes.index, desc)) diff --git a/tests/test_domains/test_domain_py_pyobject.py b/tests/test_domains/test_domain_py_pyobject.py index 8691e0e08cc..67d91d731e4 100644 --- a/tests/test_domains/test_domain_py_pyobject.py +++ b/tests/test_domains/test_domain_py_pyobject.py @@ -165,10 +165,7 @@ def test_pydata_with_union_type_operator(app): @pytest.mark.sphinx('html', testroot='root') def test_pyobject_prefix(app): text = ( - '.. py:class:: Foo\n' - '\n' - ' .. py:method:: Foo.say\n' - ' .. py:method:: FooBar.say' + '.. py:class:: Foo\n\n .. py:method:: Foo.say\n .. py:method:: FooBar.say' ) doctree = restructuredtext.parse(app, text) assert_node( diff --git a/tests/test_extensions/test_ext_apidoc.py b/tests/test_extensions/test_ext_apidoc.py index 3886617c742..7e4edbdfce8 100644 --- a/tests/test_extensions/test_ext_apidoc.py +++ b/tests/test_extensions/test_ext_apidoc.py @@ -372,9 +372,9 @@ def test_toc_all_references_should_exist_pep420_enabled(apidoc): missing_files.append(filename) all_missing = ', '.join(missing_files) - assert ( - len(missing_files) == 0 - ), f'File(s) referenced in TOC not found: {all_missing}\nTOC:\n{toc}' + assert len(missing_files) == 0, ( + f'File(s) referenced in TOC not found: {all_missing}\nTOC:\n{toc}' + ) @pytest.mark.apidoc( @@ -403,9 +403,9 @@ def test_toc_all_references_should_exist_pep420_disabled(apidoc): missing_files.append(filename) all_missing = ', '.join(missing_files) - assert ( - len(missing_files) == 0 - ), f'File(s) referenced in TOC not found: {all_missing}\nTOC:\n{toc}' + assert len(missing_files) == 0, ( + f'File(s) referenced in TOC not found: {all_missing}\nTOC:\n{toc}' + ) def extract_toc(path): diff --git a/tests/test_extensions/test_ext_autodoc.py b/tests/test_extensions/test_ext_autodoc.py index f2fb0c8ec11..dbe0fbe1d71 100644 --- a/tests/test_extensions/test_ext_autodoc.py +++ b/tests/test_extensions/test_ext_autodoc.py @@ -1558,9 +1558,9 @@ def entry( def preamble_lookup( self, doc: str, *, indent: int = 0, **options: Any ) -> list[str]: - assert ( - doc - ), f'enumeration class {self.target!r} should have an explicit docstring' + assert doc, ( + f'enumeration class {self.target!r} should have an explicit docstring' + ) args = self._preamble_args(functional_constructor=False) return self._preamble(doc=doc, args=args, indent=indent, **options) @@ -1568,9 +1568,9 @@ def preamble_lookup( def preamble_constructor( self, doc: str, *, indent: int = 0, **options: Any ) -> list[str]: - assert ( - doc - ), f'enumeration class {self.target!r} should have an explicit docstring' + assert doc, ( + f'enumeration class {self.target!r} should have an explicit docstring' + ) args = self._preamble_args(functional_constructor=True) return self._preamble(doc=doc, args=args, indent=indent, **options) diff --git a/tests/test_extensions/test_ext_autosummary.py b/tests/test_extensions/test_ext_autosummary.py index 81b13860278..5a96afdd3e3 100644 --- a/tests/test_extensions/test_ext_autosummary.py +++ b/tests/test_extensions/test_ext_autosummary.py @@ -192,9 +192,9 @@ def handler(app, what, name, obj, options, lines): 'C.C2': 'This is a nested inner class docstring', } for key, expected in expected_values.items(): - assert ( - autosummary_items[key][2] == expected - ), f'Summary for {key} was {autosummary_items[key]!r} - expected {expected!r}' + assert autosummary_items[key][2] == expected, ( + f'Summary for {key} was {autosummary_items[key]!r} - expected {expected!r}' + ) # check an item in detail assert 'func' in autosummary_items @@ -566,11 +566,7 @@ def test_autosummary_generate(app): Foo = path.read_text(encoding='utf8') assert '.. automethod:: __init__' in Foo assert ( - ' .. autosummary::\n' - ' \n' - ' ~Foo.__init__\n' - ' ~Foo.bar\n' - ' \n' + ' .. autosummary::\n \n ~Foo.__init__\n ~Foo.bar\n \n' ) in Foo assert ( ' .. autosummary::\n' @@ -591,9 +587,7 @@ def test_autosummary_generate(app): path = app.srcdir / 'generated' / 'autosummary_dummy_module.Foo.value.rst' Foo_value = path.read_text(encoding='utf8') assert ( - '.. currentmodule:: autosummary_dummy_module\n' - '\n' - '.. autoattribute:: Foo.value' + '.. currentmodule:: autosummary_dummy_module\n\n.. autoattribute:: Foo.value' ) in Foo_value path = app.srcdir / 'generated' / 'autosummary_dummy_module.qux.rst' @@ -820,17 +814,10 @@ def test_autosummary_module_all(app): ).read_text(encoding='utf8') assert ' .. autosummary::\n \n PublicBar\n \n' in module assert ( - ' .. autosummary::\n' - ' \n' - ' public_foo\n' - ' public_baz\n' - ' \n' + ' .. autosummary::\n \n public_foo\n public_baz\n \n' ) in module assert ( - '.. autosummary::\n' - ' :toctree:\n' - ' :recursive:\n\n' - ' extra_dummy_module\n' + '.. autosummary::\n :toctree:\n :recursive:\n\n extra_dummy_module\n' ) in module finally: sys.modules.pop('autosummary_dummy_package_all', None) diff --git a/tests/test_extensions/test_ext_graphviz.py b/tests/test_extensions/test_ext_graphviz.py index 929dcfa63a0..4be01caf023 100644 --- a/tests/test_extensions/test_ext_graphviz.py +++ b/tests/test_extensions/test_ext_graphviz.py @@ -168,12 +168,7 @@ def test_graphviz_parse_mapfile(): assert cmap.generate_clickable_map() == '' # normal graph - code = ( - 'digraph {\n' - ' foo [href="https://www.google.com/"];\n' - ' foo -> bar;\n' - '}\n' - ) + code = 'digraph {\n foo [href="https://www.google.com/"];\n foo -> bar;\n}\n' content = ( '\n' ':1: ' 'WARNING: Inline literal start-string without end-string. \\[docutils\\]\n' ) - assert re.search( - warning_expr, warnings - ), f'{warning_expr!r} did not match {warnings!r}' + assert re.search(warning_expr, warnings), ( + f'{warning_expr!r} did not match {warnings!r}' + ) @sphinx_intl @@ -196,21 +196,21 @@ def test_text_inconsistency_warnings(app): 'translated': "\\['`I18N WITH REFS INCONSISTENCY`_'\\]", } ) - assert re.search( - expected_warning_expr, warnings - ), f'{expected_warning_expr!r} did not match {warnings!r}' + assert re.search(expected_warning_expr, warnings), ( + f'{expected_warning_expr!r} did not match {warnings!r}' + ) expected_citation_ref_warning_expr = '.*/refs_inconsistency.txt:\\d+: WARNING: Citation \\[ref2\\] is not referenced.' - assert re.search( - expected_citation_ref_warning_expr, warnings - ), f'{expected_citation_ref_warning_expr!r} did not match {warnings!r}' + assert re.search(expected_citation_ref_warning_expr, warnings), ( + f'{expected_citation_ref_warning_expr!r} did not match {warnings!r}' + ) expected_citation_warning_expr = ( '.*/refs_inconsistency.txt:\\d+: WARNING: citation not found: ref3' ) - assert re.search( - expected_citation_warning_expr, warnings - ), f'{expected_citation_warning_expr!r} did not match {warnings!r}' + assert re.search(expected_citation_warning_expr, warnings), ( + f'{expected_citation_warning_expr!r} did not match {warnings!r}' + ) @sphinx_intl @@ -261,9 +261,9 @@ def test_text_literalblock_warnings(app): expected_warning_expr = ( '.*/literalblock.txt:\\d+: WARNING: Literal block expected; none found.' ) - assert re.search( - expected_warning_expr, warnings - ), f'{expected_warning_expr!r} did not match {warnings!r}' + assert re.search(expected_warning_expr, warnings), ( + f'{expected_warning_expr!r} did not match {warnings!r}' + ) @sphinx_intl @@ -347,16 +347,16 @@ def test_text_glossary_term_inconsistencies(app): " original: \\[':term:`Some term`', ':term:`Some other term`'\\]," " translated: \\[':term:`SOME NEW TERM`'\\] \\[i18n.inconsistent_references\\]\n" ) - assert re.search( - expected_warning_expr, warnings - ), f'{expected_warning_expr!r} did not match {warnings!r}' + assert re.search(expected_warning_expr, warnings), ( + f'{expected_warning_expr!r} did not match {warnings!r}' + ) expected_warning_expr = ( '.*/glossary_terms_inconsistency.txt:\\d+::1: ' "WARNING: term not in glossary: 'TERM NOT IN GLOSSARY'" ) - assert re.search( - expected_warning_expr, warnings - ), f'{expected_warning_expr!r} did not match {warnings!r}' + assert re.search(expected_warning_expr, warnings), ( + f'{expected_warning_expr!r} did not match {warnings!r}' + ) @sphinx_intl @@ -1051,9 +1051,9 @@ def wrap_nest(parenttag, childtag, keyword): wrap_nest('li', 'ul', 'SEE'), ] for expr in expected_exprs: - assert re.search( - expr, result, re.MULTILINE - ), f'{expr!r} did not match {result!r}' + assert re.search(expr, result, re.MULTILINE), ( + f'{expr!r} did not match {result!r}' + ) @sphinx_intl @@ -1185,9 +1185,9 @@ def test_xml_footnotes(app): warnings = getwarning(app.warning) warning_expr = '.*/footnote.xml:\\d*: SEVERE: Duplicate ID: ".*".\n' - assert not re.search( - warning_expr, warnings - ), f'{warning_expr!r} did match {warnings!r}' + assert not re.search(warning_expr, warnings), ( + f'{warning_expr!r} did match {warnings!r}' + ) @sphinx_intl diff --git a/tests/test_markup/test_markup.py b/tests/test_markup/test_markup.py index 1a8a074151c..9eb0b83fa89 100644 --- a/tests/test_markup/test_markup.py +++ b/tests/test_markup/test_markup.py @@ -403,10 +403,7 @@ def get(name): 'verify', ':kbd:`-`', '

-

', - ( - '\\sphinxAtStartPar\n' - '\\sphinxkeyboard{\\sphinxupquote{\\sphinxhyphen{}}}' - ), + '\\sphinxAtStartPar\n\\sphinxkeyboard{\\sphinxupquote{\\sphinxhyphen{}}}', ), ( # kbd role diff --git a/tests/test_pycode/test_pycode_parser.py b/tests/test_pycode/test_pycode_parser.py index c12ac4d9935..7883a8de3cf 100644 --- a/tests/test_pycode/test_pycode_parser.py +++ b/tests/test_pycode/test_pycode_parser.py @@ -31,10 +31,7 @@ def test_comment_picker_basic(): def test_comment_picker_location(): # multiple "before" comments source = ( - '#: comment before assignment1\n' - '#:\n' - '#: comment before assignment2\n' - 'a = 1 + 1\n' + '#: comment before assignment1\n#:\n#: comment before assignment2\na = 1 + 1\n' ) parser = Parser(source) parser.parse() diff --git a/tests/test_search.py b/tests/test_search.py index f74755b3b5b..600f66cb9f6 100644 --- a/tests/test_search.py +++ b/tests/test_search.py @@ -454,9 +454,9 @@ def assert_is_sorted( elif isinstance(item, list): if not is_title_tuple_type(item) and path not in lists_not_to_sort: # sort nulls last; http://stackoverflow.com/questions/19868767/ - assert item == sorted( - item, key=lambda x: (x is None, x) - ), f'{err_path} is not sorted' + assert item == sorted(item, key=lambda x: (x is None, x)), ( + f'{err_path} is not sorted' + ) for i, child in enumerate(item): assert_is_sorted(child, f'{path}[{i}]') diff --git a/tests/test_transforms/test_transforms_reorder_nodes.py b/tests/test_transforms/test_transforms_reorder_nodes.py index 5540755523a..22fdcf742cf 100644 --- a/tests/test_transforms/test_transforms_reorder_nodes.py +++ b/tests/test_transforms/test_transforms_reorder_nodes.py @@ -10,14 +10,7 @@ @pytest.mark.sphinx('html', testroot='root') def test_transforms_reorder_consecutive_target_and_index_nodes_preserve_order(app): - text = ( - '.. index:: abc\n' - '.. index:: def\n' - '.. index:: ghi\n' - '.. index:: jkl\n' - '\n' - 'text\n' - ) + text = '.. index:: abc\n.. index:: def\n.. index:: ghi\n.. index:: jkl\n\ntext\n' doctree = restructuredtext.parse(app, text) assert_node( doctree, From a6559d496bd8bd3eef6c07916d37fdf376d8af7b Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 3 Nov 2024 03:14:57 +0000 Subject: [PATCH 19/39] Use pathlib in examples --- doc/development/html_themes/index.rst | 4 ++-- sphinx/locale/__init__.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/development/html_themes/index.rst b/doc/development/html_themes/index.rst index bc75beb7ba7..ad2820495ac 100644 --- a/doc/development/html_themes/index.rst +++ b/doc/development/html_themes/index.rst @@ -203,10 +203,10 @@ using the :meth:`~sphinx.application.Sphinx.add_html_theme` API: .. code-block:: python # your_theme_package.py - from os import path + from pathlib import Path def setup(app): - app.add_html_theme('name_of_theme', path.abspath(path.dirname(__file__))) + app.add_html_theme('name_of_theme', Path(__file__).resolve().parent) If your theme package contains two or more themes, please call ``add_html_theme()`` twice or more. diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index d1a70d05b36..7165c6af4d6 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -184,7 +184,7 @@ def get_translation(catalog: str, namespace: str = 'general') -> Callable[[str], The extension can use this API to translate the messages on the extension:: - import os + from pathlib import Path from sphinx.locale import get_translation MESSAGE_CATALOG_NAME = 'myextension' # name of *.pot, *.po and *.mo files @@ -193,8 +193,8 @@ def get_translation(catalog: str, namespace: str = 'general') -> Callable[[str], def setup(app): - package_dir = os.path.abspath(os.path.dirname(__file__)) - locale_dir = os.path.join(package_dir, 'locales') + package_dir = Path(__file__).resolve().parent + locale_dir = package_dir / 'locales' app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir) With this code, sphinx searches a message catalog from From 801c60a87384a2ad9e28f85ae9bc884ec988e375 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:26:17 +0000 Subject: [PATCH 20/39] Consistently calculate the current file's directory --- doc/extdev/i18n.rst | 2 +- doc/tutorial/describing-code.rst | 4 ++-- sphinx/locale/__init__.py | 2 +- tests/conftest.py | 2 +- tests/roots/test-util-copyasset_overwrite/myext.py | 2 +- tests/utils.py | 2 +- utils/babel_runner.py | 2 +- utils/bump_version.py | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/doc/extdev/i18n.rst b/doc/extdev/i18n.rst index 3c476820fbd..8542ae684b6 100644 --- a/doc/extdev/i18n.rst +++ b/doc/extdev/i18n.rst @@ -51,7 +51,7 @@ In practice, you have to: :caption: src/__init__.py def setup(app): - package_dir = Path(__file__).parent.resolve() + package_dir = Path(__file__).resolve().parent locale_dir = package_dir / 'locales' app.add_message_catalog(MESSAGE_CATALOG_NAME, locale_dir) diff --git a/doc/tutorial/describing-code.rst b/doc/tutorial/describing-code.rst index e8c6a804fd2..32108df4de8 100644 --- a/doc/tutorial/describing-code.rst +++ b/doc/tutorial/describing-code.rst @@ -145,9 +145,9 @@ at the beginning of ``conf.py``: # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. - import pathlib import sys - sys.path.insert(0, pathlib.Path(__file__).parents[2].resolve().as_posix()) + from pathlib import Path + sys.path.insert(0, str(Path(__file__).resolve().parents[2])) .. note:: diff --git a/sphinx/locale/__init__.py b/sphinx/locale/__init__.py index 7165c6af4d6..4379ea10367 100644 --- a/sphinx/locale/__init__.py +++ b/sphinx/locale/__init__.py @@ -141,7 +141,7 @@ def init( return translator, has_translation -_LOCALE_DIR = Path(__file__).parent.resolve() +_LOCALE_DIR = Path(__file__).resolve().parent def init_console( diff --git a/tests/conftest.py b/tests/conftest.py index 8501825a025..b8c8befad32 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -44,7 +44,7 @@ def _init_console( @pytest.fixture(scope='session') def rootdir() -> Path: - return Path(__file__).parent.resolve() / 'roots' + return Path(__file__).resolve().parent / 'roots' def pytest_report_header(config: pytest.Config) -> str: diff --git a/tests/roots/test-util-copyasset_overwrite/myext.py b/tests/roots/test-util-copyasset_overwrite/myext.py index 6c2ad23395b..5ef9e69e645 100644 --- a/tests/roots/test-util-copyasset_overwrite/myext.py +++ b/tests/roots/test-util-copyasset_overwrite/myext.py @@ -9,7 +9,7 @@ def _copy_asset_overwrite_hook(app): assert css.read_text(encoding='utf-8') == '/* html_static_path */\n', 'invalid default text' # warning generated by here copy_asset( - Path(__file__).parent.joinpath('myext_static', 'custom-styles.css'), + Path(__file__).resolve().parent.joinpath('myext_static', 'custom-styles.css'), app.outdir / '_static', ) # This demonstrates that no overwriting occurs diff --git a/tests/utils.py b/tests/utils.py index ba4640aa33a..9ebe62ec2e6 100644 --- a/tests/utils.py +++ b/tests/utils.py @@ -22,7 +22,7 @@ # Generated with: # $ openssl req -new -x509 -days 3650 -nodes -out cert.pem \ # -keyout cert.pem -addext "subjectAltName = DNS:localhost" -TESTS_ROOT: Final[Path] = Path(__file__).parent +TESTS_ROOT: Final[Path] = Path(__file__).resolve().parent CERT_FILE: Final[str] = str(TESTS_ROOT / 'certs' / 'cert.pem') diff --git a/utils/babel_runner.py b/utils/babel_runner.py index 970d6f3bfca..8c27f20e3e8 100644 --- a/utils/babel_runner.py +++ b/utils/babel_runner.py @@ -33,7 +33,7 @@ from jinja2.ext import babel_extract as extract_jinja2 IS_CI = 'CI' in environ -ROOT = Path(__file__).parent.parent.resolve() +ROOT = Path(__file__).resolve().parent.parent TEX_DELIMITERS = { 'variable_start_string': '<%=', 'variable_end_string': '%>', diff --git a/utils/bump_version.py b/utils/bump_version.py index 81824fc0a1c..7894ac9f85e 100755 --- a/utils/bump_version.py +++ b/utils/bump_version.py @@ -14,7 +14,7 @@ if TYPE_CHECKING: from collections.abc import Iterator, Sequence -script_dir = Path(__file__).parent +script_dir = Path(__file__).resolve().parent package_dir = script_dir.parent From 7801bd77b8ebf0bddfedc00f0d61cea56f3f4e2f Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 3 Nov 2024 17:38:10 +0000 Subject: [PATCH 21/39] Make ``os.path`` imports absolute --- sphinx/builders/__init__.py | 20 +++---- sphinx/builders/_epub_base.py | 44 ++++++++-------- sphinx/builders/changes.py | 16 +++--- sphinx/builders/epub3.py | 6 +-- sphinx/builders/html/__init__.py | 6 +-- sphinx/builders/latex/__init__.py | 22 ++++---- sphinx/builders/latex/theming.py | 10 ++-- sphinx/builders/linkcheck.py | 10 ++-- sphinx/builders/manpage.py | 7 +-- sphinx/builders/texinfo.py | 7 +-- sphinx/builders/text.py | 8 +-- sphinx/builders/xml.py | 8 +-- sphinx/cmd/quickstart.py | 52 ++++++++++--------- sphinx/environment/__init__.py | 20 +++---- sphinx/environment/collectors/asset.py | 8 +-- sphinx/environment/collectors/dependencies.py | 6 +-- sphinx/ext/apidoc.py | 14 ++--- sphinx/ext/autosummary/__init__.py | 4 +- sphinx/ext/autosummary/generate.py | 4 +- sphinx/ext/coverage.py | 12 ++--- sphinx/ext/doctest.py | 4 +- sphinx/ext/graphviz.py | 18 +++---- sphinx/ext/imgmath.py | 38 +++++++------- sphinx/ext/inheritance_diagram.py | 4 +- sphinx/ext/intersphinx/_load.py | 10 ++-- sphinx/ext/viewcode.py | 4 +- sphinx/jinja2glue.py | 6 +-- sphinx/pycode/__init__.py | 8 +-- sphinx/transforms/i18n.py | 4 +- sphinx/util/i18n.py | 14 ++--- sphinx/util/images.py | 6 +-- sphinx/util/osutil.py | 8 +-- sphinx/writers/latex.py | 10 ++-- sphinx/writers/texinfo.py | 4 +- 34 files changed, 215 insertions(+), 207 deletions(-) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index bf2f3f86080..f1733184243 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -3,11 +3,11 @@ from __future__ import annotations import codecs +import os.path import pickle import re import time from contextlib import nullcontext -from os import path from typing import TYPE_CHECKING, Any, Literal, final from docutils import nodes @@ -232,7 +232,7 @@ def compile_catalogs(self, catalogs: set[CatalogInfo], message: str) -> None: return def cat2relpath(cat: CatalogInfo) -> str: - return relpath(cat.mo_path, self.env.srcdir).replace(path.sep, SEP) + return relpath(cat.mo_path, self.env.srcdir).replace(os.path.sep, SEP) logger.info(bold(__('building [mo]: ')) + message) for catalog in status_iterator( @@ -259,7 +259,7 @@ def compile_all_catalogs(self) -> None: def compile_specific_catalogs(self, specified_files: list[str]) -> None: def to_domain(fpath: str) -> str | None: - docname = self.env.path2doc(path.abspath(fpath)) + docname = self.env.path2doc(os.path.abspath(fpath)) if docname: return docname_to_domain(docname, self.config.gettext_compact) else: @@ -306,9 +306,9 @@ def build_specific(self, filenames: list[str]) -> None: docnames: list[str] = [] for filename in filenames: - filename = path.normpath(path.abspath(filename)) + filename = os.path.normpath(os.path.abspath(filename)) - if not path.isfile(filename): + if not os.path.isfile(filename): logger.warning( __('file %r given on command line does not exist, '), filename ) @@ -399,7 +399,7 @@ def build( with ( progress_message(__('pickling environment')), - open(path.join(self.doctreedir, ENV_PICKLE_FILENAME), 'wb') as f, + open(os.path.join(self.doctreedir, ENV_PICKLE_FILENAME), 'wb') as f, ): pickle.dump(self.env, f, pickle.HIGHEST_PROTOCOL) @@ -607,8 +607,8 @@ def read_doc(self, docname: str, *, _cache: bool = True) -> None: self.env.prepare_settings(docname) # Add confdir/docutils.conf to dependencies list if exists - docutilsconf = path.join(self.confdir, 'docutils.conf') - if path.isfile(docutilsconf): + docutilsconf = os.path.join(self.confdir, 'docutils.conf') + if os.path.isfile(docutilsconf): self.env.note_dependency(docutilsconf) filename = str(self.env.doc2path(docname)) @@ -659,8 +659,8 @@ def write_doctree( doctree.settings.env = None doctree.settings.record_dependencies = None - doctree_filename = path.join(self.doctreedir, docname + '.doctree') - ensuredir(path.dirname(doctree_filename)) + doctree_filename = os.path.join(self.doctreedir, docname + '.doctree') + ensuredir(os.path.dirname(doctree_filename)) with open(doctree_filename, 'wb') as f: pickle.dump(doctree, f, pickle.HIGHEST_PROTOCOL) diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index ffab73634ae..32fb0e892c6 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -4,9 +4,9 @@ import html import os +import os.path import re import time -from os import path from typing import TYPE_CHECKING, Any, NamedTuple from urllib.parse import quote from zipfile import ZIP_DEFLATED, ZIP_STORED, ZipFile @@ -235,7 +235,7 @@ def get_toc(self) -> None: self.config.root_doc, self, prune_toctrees=False, includehidden=True ) self.refnodes = self.get_refnodes(doctree, []) - master_dir = path.dirname(self.config.root_doc) + master_dir = os.path.dirname(self.config.root_doc) if master_dir: master_dir += '/' # XXX or os.sep? for item in self.refnodes: @@ -409,7 +409,7 @@ def fix_genindex(self, tree: list[tuple[str, list[tuple[str, Any]]]]) -> None: def is_vector_graphics(self, filename: str) -> bool: """Does the filename extension indicate a vector graphic format?""" - ext = path.splitext(filename)[-1] + ext = os.path.splitext(filename)[-1] return ext in VECTOR_GRAPHICS_EXTENSIONS def copy_image_files_pil(self) -> None: @@ -417,7 +417,7 @@ def copy_image_files_pil(self) -> None: The method tries to read and write the files with Pillow, converting the format and resizing the image if necessary/possible. """ - ensuredir(path.join(self.outdir, self.imagedir)) + ensuredir(os.path.join(self.outdir, self.imagedir)) for src in status_iterator( self.images, __('copying images... '), @@ -427,12 +427,12 @@ def copy_image_files_pil(self) -> None: ): dest = self.images[src] try: - img = Image.open(path.join(self.srcdir, src)) + img = Image.open(os.path.join(self.srcdir, src)) except OSError: if not self.is_vector_graphics(src): logger.warning( __('cannot read image file %r: copying it instead'), - path.join(self.srcdir, src), + os.path.join(self.srcdir, src), ) try: copyfile( @@ -443,7 +443,7 @@ def copy_image_files_pil(self) -> None: except OSError as err: logger.warning( __('cannot copy image file %r: %s'), - path.join(self.srcdir, src), + os.path.join(self.srcdir, src), err, ) continue @@ -459,11 +459,11 @@ def copy_image_files_pil(self) -> None: nh = round((height * nw) / width) img = img.resize((nw, nh), Image.BICUBIC) try: - img.save(path.join(self.outdir, self.imagedir, dest)) + img.save(os.path.join(self.outdir, self.imagedir, dest)) except OSError as err: logger.warning( __('cannot write image file %r: %s'), - path.join(self.srcdir, src), + os.path.join(self.srcdir, src), err, ) @@ -511,7 +511,7 @@ def build_mimetype(self) -> None: """Write the metainfo file mimetype.""" logger.info(__('writing mimetype file...')) copyfile( - path.join(self.template_dir, 'mimetype'), + os.path.join(self.template_dir, 'mimetype'), self.outdir / 'mimetype', force=True, ) @@ -522,7 +522,7 @@ def build_container(self, outname: str = 'META-INF/container.xml') -> None: outdir = self.outdir / 'META-INF' ensuredir(outdir) copyfile( - path.join(self.template_dir, 'container.xml'), + os.path.join(self.template_dir, 'container.xml'), outdir / 'container.xml', force=True, ) @@ -578,10 +578,10 @@ def build_content(self) -> None: for root, dirs, files in os.walk(self.outdir): dirs.sort() for fn in sorted(files): - filename = relpath(path.join(root, fn), self.outdir) + filename = relpath(os.path.join(root, fn), self.outdir) if filename in self.ignored_files: continue - ext = path.splitext(filename)[-1] + ext = os.path.splitext(filename)[-1] if ext not in self.media_types: # we always have JS and potentially OpenSearch files, don't # always warn about them @@ -636,7 +636,7 @@ def build_content(self) -> None: spine = Spine(html.escape(self.make_id(self.coverpage_name)), True) metadata['spines'].insert(0, spine) if self.coverpage_name not in self.files: - ext = path.splitext(self.coverpage_name)[-1] + ext = os.path.splitext(self.coverpage_name)[-1] self.files.append(self.coverpage_name) item = ManifestItem( html.escape(self.coverpage_name), @@ -645,7 +645,9 @@ def build_content(self) -> None: ) metadata['manifest_items'].append(item) ctx = {'image': html.escape(image), 'title': self.config.project} - self.handle_page(path.splitext(self.coverpage_name)[0], ctx, html_tmpl) + self.handle_page( + os.path.splitext(self.coverpage_name)[0], ctx, html_tmpl + ) spinefiles.add(self.coverpage_name) auto_add_cover = True @@ -681,7 +683,7 @@ def build_content(self) -> None: # write the project file copy_asset_file( - path.join(self.template_dir, 'content.opf.jinja'), + os.path.join(self.template_dir, 'content.opf.jinja'), self.outdir, context=metadata, force=True, @@ -775,7 +777,7 @@ def build_toc(self) -> None: level = max(item['level'] for item in self.refnodes) level = min(level, self.config.epub_tocdepth) copy_asset_file( - path.join(self.template_dir, 'toc.ncx.jinja'), + os.path.join(self.template_dir, 'toc.ncx.jinja'), self.outdir, context=self.toc_metadata(level, navpoints), force=True, @@ -789,10 +791,10 @@ def build_epub(self) -> None: """ outname = self.config.epub_basename + '.epub' logger.info(__('writing %s file...'), outname) - epub_filename = path.join(self.outdir, outname) + epub_filename = os.path.join(self.outdir, outname) with ZipFile(epub_filename, 'w', ZIP_DEFLATED) as epub: - epub.write(path.join(self.outdir, 'mimetype'), 'mimetype', ZIP_STORED) + epub.write(os.path.join(self.outdir, 'mimetype'), 'mimetype', ZIP_STORED) for filename in ('META-INF/container.xml', 'content.opf', 'toc.ncx'): - epub.write(path.join(self.outdir, filename), filename, ZIP_DEFLATED) + epub.write(os.path.join(self.outdir, filename), filename, ZIP_DEFLATED) for filename in self.files: - epub.write(path.join(self.outdir, filename), filename, ZIP_DEFLATED) + epub.write(os.path.join(self.outdir, filename), filename, ZIP_DEFLATED) diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index cd40faf3ab7..918ed3bcaee 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -3,7 +3,7 @@ from __future__ import annotations import html -from os import path +import os.path from typing import TYPE_CHECKING from sphinx import package_dir @@ -108,9 +108,9 @@ def write_documents(self, _docnames: Set[str]) -> None: 'show_copyright': self.config.html_show_copyright, 'show_sphinx': self.config.html_show_sphinx, } - with open(path.join(self.outdir, 'index.html'), 'w', encoding='utf8') as f: + with open(os.path.join(self.outdir, 'index.html'), 'w', encoding='utf8') as f: f.write(self.templates.render('changes/frameset.html', ctx)) - with open(path.join(self.outdir, 'changes.html'), 'w', encoding='utf8') as f: + with open(os.path.join(self.outdir, 'changes.html'), 'w', encoding='utf8') as f: f.write(self.templates.render('changes/versionchanges.html', ctx)) hltext = [ @@ -140,8 +140,8 @@ def hl(no: int, line: str) -> str: __('could not read %r for changelog creation'), docname ) continue - targetfn = path.join(self.outdir, 'rst', os_path(docname)) + '.html' - ensuredir(path.dirname(targetfn)) + targetfn = os.path.join(self.outdir, 'rst', os_path(docname)) + '.html' + ensuredir(os.path.dirname(targetfn)) with open(targetfn, 'w', encoding='utf-8') as f: text = ''.join(hl(i + 1, line) for (i, line) in enumerate(lines)) ctx = { @@ -153,14 +153,16 @@ def hl(no: int, line: str) -> str: 'theme_' + key: val for (key, val) in self.theme.get_options({}).items() } copy_asset_file( - path.join(package_dir, 'themes', 'default', 'static', 'default.css.jinja'), + os.path.join( + package_dir, 'themes', 'default', 'static', 'default.css.jinja' + ), self.outdir, context=themectx, renderer=self.templates, force=True, ) copy_asset_file( - path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'), + os.path.join(package_dir, 'themes', 'basic', 'static', 'basic.css'), self.outdir / 'basic.css', force=True, ) diff --git a/sphinx/builders/epub3.py b/sphinx/builders/epub3.py index cbd50bd229e..44b2cb10e16 100644 --- a/sphinx/builders/epub3.py +++ b/sphinx/builders/epub3.py @@ -7,9 +7,9 @@ import html import os +import os.path import re import time -from os import path from typing import TYPE_CHECKING, Any, NamedTuple from sphinx import package_dir @@ -83,7 +83,7 @@ class Epub3Builder(_epub_base.EpubBuilder): epilog = __('The ePub file is in %(outdir)s.') supported_remote_images = False - template_dir = path.join(package_dir, 'templates', 'epub3') + template_dir = os.path.join(package_dir, 'templates', 'epub3') doctype = DOCTYPE html_tag = HTML_TAG use_meta_charset = True @@ -199,7 +199,7 @@ def build_navigation_doc(self) -> None: refnodes = self.refnodes navlist = self.build_navlist(refnodes) copy_asset_file( - path.join(self.template_dir, 'nav.xhtml.jinja'), + os.path.join(self.template_dir, 'nav.xhtml.jinja'), self.outdir, context=self.navigation_doc_metadata(navlist), force=True, diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index eacb333fbe9..0dd25fbb644 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -5,12 +5,12 @@ import contextlib import html import os +import os.path import posixpath import re import shutil import sys import warnings -from os import path from pathlib import Path from typing import TYPE_CHECKING, Any from urllib.parse import quote @@ -496,9 +496,9 @@ def prepare_writing(self, docnames: Set[str]) -> None: favicon = self.config.html_favicon or '' if not is_url(logo): - logo = path.basename(logo) + logo = os.path.basename(logo) if not is_url(favicon): - favicon = path.basename(favicon) + favicon = os.path.basename(favicon) self.relations = self.env.collect_relations() diff --git a/sphinx/builders/latex/__init__.py b/sphinx/builders/latex/__init__.py index 19ea60e1290..09cffaaf92c 100644 --- a/sphinx/builders/latex/__init__.py +++ b/sphinx/builders/latex/__init__.py @@ -3,8 +3,8 @@ from __future__ import annotations import os +import os.path import warnings -from os import path from typing import TYPE_CHECKING, Any from docutils.frontend import OptionParser @@ -199,7 +199,7 @@ def init_context(self) -> None: self.context['date'] = format_date(today_fmt, language=self.config.language) if self.config.latex_logo: - self.context['logofilename'] = path.basename(self.config.latex_logo) + self.context['logofilename'] = os.path.basename(self.config.latex_logo) # for compatibilities self.context['indexname'] = _('Index') @@ -276,7 +276,7 @@ def init_multilingual(self) -> None: def write_stylesheet(self) -> None: highlighter = highlighting.PygmentsBridge('latex', self.config.pygments_style) - stylesheet = path.join(self.outdir, 'sphinxhighlight.sty') + stylesheet = os.path.join(self.outdir, 'sphinxhighlight.sty') with open(stylesheet, 'w', encoding='utf-8') as f: f.write('\\NeedsTeXFormat{LaTeX2e}[1995/12/01]\n') f.write( @@ -317,7 +317,7 @@ def write_documents(self, _docnames: Set[str]) -> None: if len(entry) > 5: toctree_only = entry[5] destination = SphinxFileOutput( - destination_path=path.join(self.outdir, targetname), + destination_path=os.path.join(self.outdir, targetname), encoding='utf-8', overwrite_if_changed=True, ) @@ -443,11 +443,11 @@ def copy_support_files(self) -> None: 'xindy_lang_option': xindy_lang_option, 'xindy_cyrillic': xindy_cyrillic, } - staticdirname = path.join(package_dir, 'texinputs') + staticdirname = os.path.join(package_dir, 'texinputs') for filename in os.listdir(staticdirname): if not filename.startswith('.'): copy_asset_file( - path.join(staticdirname, filename), + os.path.join(staticdirname, filename), self.outdir, context=context, force=True, @@ -455,9 +455,9 @@ def copy_support_files(self) -> None: # use pre-1.6.x Makefile for make latexpdf on Windows if os.name == 'nt': - staticdirname = path.join(package_dir, 'texinputs_win') + staticdirname = os.path.join(package_dir, 'texinputs_win') copy_asset_file( - path.join(staticdirname, 'Makefile.jinja'), + os.path.join(staticdirname, 'Makefile.jinja'), self.outdir, context=context, force=True, @@ -495,11 +495,11 @@ def copy_image_files(self) -> None: except Exception as err: logger.warning( __('cannot copy image file %r: %s'), - path.join(self.srcdir, src), + os.path.join(self.srcdir, src), err, ) if self.config.latex_logo: - if not path.isfile(path.join(self.confdir, self.config.latex_logo)): + if not os.path.isfile(os.path.join(self.confdir, self.config.latex_logo)): raise SphinxError( __('logo file %r does not exist') % self.config.latex_logo ) @@ -522,7 +522,7 @@ def write_message_catalog(self) -> None: if self.context['babel'] or self.context['polyglossia']: context['addtocaptions'] = r'\addto\captions%s' % self.babel.get_language() - filename = path.join( + filename = os.path.join( package_dir, 'templates', 'latex', 'sphinxmessages.sty.jinja' ) copy_asset_file( diff --git a/sphinx/builders/latex/theming.py b/sphinx/builders/latex/theming.py index 50f03652fc8..de0540127df 100644 --- a/sphinx/builders/latex/theming.py +++ b/sphinx/builders/latex/theming.py @@ -3,7 +3,7 @@ from __future__ import annotations import configparser -from os import path +import os.path from typing import TYPE_CHECKING from sphinx.errors import ThemeError @@ -77,7 +77,7 @@ class UserTheme(Theme): def __init__(self, name: str, filename: str) -> None: super().__init__(name) self.config = configparser.RawConfigParser() - self.config.read(path.join(filename), encoding='utf-8') + self.config.read(os.path.join(filename), encoding='utf-8') for key in self.REQUIRED_CONFIG_KEYS: try: @@ -104,7 +104,7 @@ class ThemeFactory: def __init__(self, app: Sphinx) -> None: self.themes: dict[str, Theme] = {} self.theme_paths = [ - path.join(app.srcdir, p) for p in app.config.latex_theme_path + os.path.join(app.srcdir, p) for p in app.config.latex_theme_path ] self.config = app.config self.load_builtin_themes(app.config) @@ -127,8 +127,8 @@ def get(self, name: str) -> Theme: def find_user_theme(self, name: str) -> Theme | None: """Find a theme named as *name* from latex_theme_path.""" for theme_path in self.theme_paths: - config_path = path.join(theme_path, name, 'theme.conf') - if path.isfile(config_path): + config_path = os.path.join(theme_path, name, 'theme.conf') + if os.path.isfile(config_path): try: return UserTheme(name, config_path) except ThemeError as exc: diff --git a/sphinx/builders/linkcheck.py b/sphinx/builders/linkcheck.py index fcf994e8e03..82fc0acc4bc 100644 --- a/sphinx/builders/linkcheck.py +++ b/sphinx/builders/linkcheck.py @@ -4,12 +4,12 @@ import contextlib import json +import os.path import re import socket import time from enum import StrEnum from html.parser import HTMLParser -from os import path from queue import PriorityQueue, Queue from threading import Thread from typing import TYPE_CHECKING, NamedTuple, cast @@ -85,8 +85,8 @@ def finish(self) -> None: checker = HyperlinkAvailabilityChecker(self.config) logger.info('') - output_text = path.join(self.outdir, 'output.txt') - output_json = path.join(self.outdir, 'output.json') + output_text = os.path.join(self.outdir, 'output.txt') + output_json = os.path.join(self.outdir, 'output.json') with ( open(output_text, 'w', encoding='utf-8') as self.txt_outfile, open(output_json, 'w', encoding='utf-8') as self.json_outfile, @@ -450,8 +450,8 @@ def _check(self, docname: str, uri: str, hyperlink: Hyperlink) -> _URIProperties # Non-supported URI schemes (ex. ftp) return _Status.UNCHECKED, '', 0 - src_dir = path.dirname(hyperlink.docpath) - if path.exists(path.join(src_dir, uri)): + src_dir = os.path.dirname(hyperlink.docpath) + if os.path.exists(os.path.join(src_dir, uri)): return _Status.WORKING, '', 0 return _Status.BROKEN, '', 0 diff --git a/sphinx/builders/manpage.py b/sphinx/builders/manpage.py index 4535776b9cb..98135d9801c 100644 --- a/sphinx/builders/manpage.py +++ b/sphinx/builders/manpage.py @@ -2,8 +2,8 @@ from __future__ import annotations +import os.path import warnings -from os import path from typing import TYPE_CHECKING, Any from docutils.frontend import OptionParser @@ -87,14 +87,15 @@ def write_documents(self, _docnames: Set[str]) -> None: if self.config.man_make_section_directory: dirname = 'man%s' % section - ensuredir(path.join(self.outdir, dirname)) + ensuredir(os.path.join(self.outdir, dirname)) targetname = f'{dirname}/{name}.{section}' else: targetname = f'{name}.{section}' logger.info(darkgreen(targetname) + ' { ') destination = FileOutput( - destination_path=path.join(self.outdir, targetname), encoding='utf-8' + destination_path=os.path.join(self.outdir, targetname), + encoding='utf-8', ) tree = self.env.get_doctree(docname) diff --git a/sphinx/builders/texinfo.py b/sphinx/builders/texinfo.py index 2d428bb736b..7143e68c4f0 100644 --- a/sphinx/builders/texinfo.py +++ b/sphinx/builders/texinfo.py @@ -3,8 +3,8 @@ from __future__ import annotations import os +import os.path import warnings -from os import path from typing import TYPE_CHECKING, Any from docutils import nodes @@ -109,7 +109,8 @@ def write_documents(self, _docnames: Set[str]) -> None: if len(entry) > 7: toctree_only = entry[7] destination = FileOutput( - destination_path=path.join(self.outdir, targetname), encoding='utf-8' + destination_path=os.path.join(self.outdir, targetname), + encoding='utf-8', ) with progress_message(__('processing %s') % targetname, nonl=False): appendices = self.config.texinfo_appendices or [] @@ -216,7 +217,7 @@ def copy_image_files(self, targetname: str) -> None: except Exception as err: logger.warning( __('cannot copy image file %r: %s'), - path.join(self.srcdir, src), + os.path.join(self.srcdir, src), err, ) diff --git a/sphinx/builders/text.py b/sphinx/builders/text.py index 243e790124f..03e22d2c73e 100644 --- a/sphinx/builders/text.py +++ b/sphinx/builders/text.py @@ -2,7 +2,7 @@ from __future__ import annotations -from os import path +import os.path from typing import TYPE_CHECKING from docutils.io import StringOutput @@ -48,7 +48,7 @@ def get_outdated_docs(self) -> Iterator[str]: if docname not in self.env.all_docs: yield docname continue - targetname = path.join(self.outdir, docname + self.out_suffix) + targetname = os.path.join(self.outdir, docname + self.out_suffix) try: targetmtime = _last_modified_time(targetname) except Exception: @@ -72,8 +72,8 @@ def write_doc(self, docname: str, doctree: nodes.document) -> None: self.secnumbers = self.env.toc_secnumbers.get(docname, {}) destination = StringOutput(encoding='utf-8') self.writer.write(doctree, destination) - outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix) - ensuredir(path.dirname(outfilename)) + outfilename = os.path.join(self.outdir, os_path(docname) + self.out_suffix) + ensuredir(os.path.dirname(outfilename)) try: with open(outfilename, 'w', encoding='utf-8') as f: f.write(self.writer.output) diff --git a/sphinx/builders/xml.py b/sphinx/builders/xml.py index dbb78772a00..173f1ee5c53 100644 --- a/sphinx/builders/xml.py +++ b/sphinx/builders/xml.py @@ -2,7 +2,7 @@ from __future__ import annotations -from os import path +import os.path from typing import TYPE_CHECKING from docutils import nodes @@ -52,7 +52,7 @@ def get_outdated_docs(self) -> Iterator[str]: if docname not in self.env.all_docs: yield docname continue - targetname = path.join(self.outdir, docname + self.out_suffix) + targetname = os.path.join(self.outdir, docname + self.out_suffix) try: targetmtime = _last_modified_time(targetname) except Exception: @@ -88,8 +88,8 @@ def write_doc(self, docname: str, doctree: nodes.document) -> None: value[i] = list(val) destination = StringOutput(encoding='utf-8') self.writer.write(doctree, destination) - outfilename = path.join(self.outdir, os_path(docname) + self.out_suffix) - ensuredir(path.dirname(outfilename)) + outfilename = os.path.join(self.outdir, os_path(docname) + self.out_suffix) + ensuredir(os.path.dirname(outfilename)) try: with open(outfilename, 'w', encoding='utf-8') as f: f.write(self.writer.output) diff --git a/sphinx/cmd/quickstart.py b/sphinx/cmd/quickstart.py index 18d06c39f1d..214391ecf22 100644 --- a/sphinx/cmd/quickstart.py +++ b/sphinx/cmd/quickstart.py @@ -5,9 +5,9 @@ import argparse import locale import os +import os.path import sys import time -from os import path from typing import TYPE_CHECKING, Any # try to import readline, unix specific enhancement @@ -89,8 +89,8 @@ class ValidationError(Exception): def is_path(x: str) -> str: - x = path.expanduser(x) - if not path.isdir(x): + x = os.path.expanduser(x) + if not os.path.isdir(x): raise ValidationError(__('Please enter a valid path name.')) return x @@ -179,12 +179,14 @@ def _has_custom_template(self, template_name: str) -> bool: Note: Please don't use this function from extensions. It will be removed in the future without deprecation period. """ - template = path.join(self.templatedir, path.basename(template_name)) - return bool(self.templatedir) and path.exists(template) + template = os.path.join(self.templatedir, os.path.basename(template_name)) + return bool(self.templatedir) and os.path.exists(template) def render(self, template_name: str, context: dict[str, Any]) -> str: if self._has_custom_template(template_name): - custom_template = path.join(self.templatedir, path.basename(template_name)) + custom_template = os.path.join( + self.templatedir, os.path.basename(template_name) + ) return self.render_from_file(custom_template, context) else: return super().render(template_name, context) @@ -228,8 +230,8 @@ def ask_user(d: dict[str, Any]) -> None: print(__('Enter the root path for documentation.')) d['path'] = do_prompt(__('Root path for the documentation'), '.', is_path) - while path.isfile(path.join(d['path'], 'conf.py')) or path.isfile( - path.join(d['path'], 'source', 'conf.py') + while os.path.isfile(os.path.join(d['path'], 'conf.py')) or os.path.isfile( + os.path.join(d['path'], 'source', 'conf.py') ): print() print( @@ -341,8 +343,8 @@ def ask_user(d: dict[str, Any]) -> None: ) while ( - path.isfile(path.join(d['path'], d['master'] + d['suffix'])) - or path.isfile(path.join(d['path'], 'source', d['master'] + d['suffix'])) + os.path.isfile(os.path.join(d['path'], d['master'] + d['suffix'])) + or os.path.isfile(os.path.join(d['path'], 'source', d['master'] + d['suffix'])) ): # fmt: skip print() print( @@ -424,14 +426,14 @@ def generate( d['path'] = os.path.abspath(d['path']) ensuredir(d['path']) - srcdir = path.join(d['path'], 'source') if d['sep'] else d['path'] + srcdir = os.path.join(d['path'], 'source') if d['sep'] else d['path'] ensuredir(srcdir) if d['sep']: - builddir = path.join(d['path'], 'build') + builddir = os.path.join(d['path'], 'build') d['exclude_patterns'] = '' else: - builddir = path.join(srcdir, d['dot'] + 'build') + builddir = os.path.join(srcdir, d['dot'] + 'build') exclude_patterns = map( repr, [ @@ -442,11 +444,11 @@ def generate( ) d['exclude_patterns'] = ', '.join(exclude_patterns) ensuredir(builddir) - ensuredir(path.join(srcdir, d['dot'] + 'templates')) - ensuredir(path.join(srcdir, d['dot'] + 'static')) + ensuredir(os.path.join(srcdir, d['dot'] + 'templates')) + ensuredir(os.path.join(srcdir, d['dot'] + 'static')) def write_file(fpath: str, content: str, newline: str | None = None) -> None: - if overwrite or not path.isfile(fpath): + if overwrite or not os.path.isfile(fpath): if 'quiet' not in d: print(__('Creating file %s.') % fpath) with open(fpath, 'w', encoding='utf-8', newline=newline) as f: @@ -456,16 +458,16 @@ def write_file(fpath: str, content: str, newline: str | None = None) -> None: print(__('File %s already exists, skipping.') % fpath) conf_path = os.path.join(templatedir, 'conf.py.jinja') if templatedir else None - if not conf_path or not path.isfile(conf_path): + if not conf_path or not os.path.isfile(conf_path): conf_path = os.path.join( package_dir, 'templates', 'quickstart', 'conf.py.jinja' ) with open(conf_path, encoding='utf-8') as f: conf_text = f.read() - write_file(path.join(srcdir, 'conf.py'), template.render_string(conf_text, d)) + write_file(os.path.join(srcdir, 'conf.py'), template.render_string(conf_text, d)) - masterfile = path.join(srcdir, d['master'] + d['suffix']) + masterfile = os.path.join(srcdir, d['master'] + d['suffix']) if template._has_custom_template('quickstart/master_doc.rst.jinja'): write_file(masterfile, template.render('quickstart/master_doc.rst.jinja', d)) else: @@ -479,7 +481,7 @@ def write_file(fpath: str, content: str, newline: str | None = None) -> None: d['rbuilddir'] = 'build' if d['sep'] else d['dot'] + 'build' # use binary mode, to avoid writing \r\n on Windows write_file( - path.join(d['path'], 'Makefile'), + os.path.join(d['path'], 'Makefile'), template.render(makefile_template, d), '\n', ) @@ -488,7 +490,7 @@ def write_file(fpath: str, content: str, newline: str | None = None) -> None: d['rsrcdir'] = 'source' if d['sep'] else '.' d['rbuilddir'] = 'build' if d['sep'] else d['dot'] + 'build' write_file( - path.join(d['path'], 'make.bat'), + os.path.join(d['path'], 'make.bat'), template.render(batchfile_template, d), '\r\n', ) @@ -527,9 +529,9 @@ def write_file(fpath: str, content: str, newline: str | None = None) -> None: def valid_dir(d: dict[str, Any]) -> bool: dir = d['path'] - if not path.exists(dir): + if not os.path.exists(dir): return True - if not path.isdir(dir): + if not os.path.isdir(dir): return False if {'Makefile', 'make.bat'} & set(os.listdir(dir)): @@ -537,9 +539,9 @@ def valid_dir(d: dict[str, Any]) -> bool: if d['sep']: dir = os.path.join('source', dir) - if not path.exists(dir): + if not os.path.exists(dir): return True - if not path.isdir(dir): + if not os.path.isdir(dir): return False reserved_names = [ diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 8ecacf257ce..68d0249b151 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -4,10 +4,10 @@ import functools import os +import os.path import pickle from collections import defaultdict from copy import copy -from os import path from typing import TYPE_CHECKING from sphinx import addnodes @@ -424,12 +424,12 @@ def relfn2path(self, filename: str, docname: str | None = None) -> tuple[str, st if filename.startswith(('/', os.sep)): rel_fn = filename[1:] else: - docdir = path.dirname(self.doc2path(docname or self.docname, base=False)) - rel_fn = path.join(docdir, filename) + docdir = os.path.dirname(self.doc2path(docname or self.docname, base=False)) + rel_fn = os.path.join(docdir, filename) return ( - canon_path(path.normpath(rel_fn)), - path.normpath(path.join(self.srcdir, rel_fn)), + canon_path(os.path.normpath(rel_fn)), + os.path.normpath(os.path.join(self.srcdir, rel_fn)), ) @property @@ -493,8 +493,8 @@ def get_outdated_files( added.add(docname) continue # if the doctree file is not there, rebuild - filename = path.join(self.doctreedir, docname + '.doctree') - if not path.isfile(filename): + filename = os.path.join(self.doctreedir, docname + '.doctree') + if not os.path.isfile(filename): logger.debug('[build target] changed %r', docname) changed.add(docname) continue @@ -519,8 +519,8 @@ def get_outdated_files( for dep in self.dependencies[docname]: try: # this will do the right thing when dep is absolute too - deppath = path.join(self.srcdir, dep) - if not path.isfile(deppath): + deppath = os.path.join(self.srcdir, dep) + if not os.path.isfile(deppath): logger.debug( '[build target] changed %r missing dependency %r', docname, @@ -629,7 +629,7 @@ def get_doctree(self, docname: str) -> nodes.document: try: serialised = self._pickled_doctree_cache[docname] except KeyError: - filename = path.join(self.doctreedir, docname + '.doctree') + filename = os.path.join(self.doctreedir, docname + '.doctree') with open(filename, 'rb') as f: serialised = self._pickled_doctree_cache[docname] = f.read() diff --git a/sphinx/environment/collectors/asset.py b/sphinx/environment/collectors/asset.py index 5096a9d1a68..838391aa7da 100644 --- a/sphinx/environment/collectors/asset.py +++ b/sphinx/environment/collectors/asset.py @@ -3,8 +3,8 @@ from __future__ import annotations import os +import os.path from glob import glob -from os import path from typing import TYPE_CHECKING from docutils import nodes @@ -90,7 +90,7 @@ def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: # into a single directory) for imgpath in candidates.values(): app.env.dependencies[docname].add(imgpath) - if not os.access(path.join(app.srcdir, imgpath), os.R_OK): + if not os.access(os.path.join(app.srcdir, imgpath), os.R_OK): logger.warning( __('image file not readable: %s'), imgpath, @@ -110,11 +110,11 @@ def collect_candidates( ) -> None: globbed: dict[str, list[str]] = {} for filename in glob(imgpath): - new_imgpath = relative_path(path.join(env.srcdir, 'dummy'), filename) + new_imgpath = relative_path(os.path.join(env.srcdir, 'dummy'), filename) try: mimetype = guess_mimetype(filename) if mimetype is None: - basename, suffix = path.splitext(filename) + basename, suffix = os.path.splitext(filename) mimetype = 'image/x-' + suffix[1:] if mimetype not in candidates: globbed.setdefault(mimetype, []).append(new_imgpath) diff --git a/sphinx/environment/collectors/dependencies.py b/sphinx/environment/collectors/dependencies.py index 46fbf323609..1173dc3b24a 100644 --- a/sphinx/environment/collectors/dependencies.py +++ b/sphinx/environment/collectors/dependencies.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from os import path +import os.path from typing import TYPE_CHECKING from docutils.utils import relative_path @@ -39,7 +39,7 @@ def merge_other( def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Process docutils-generated dependency info.""" cwd = os.getcwd() - frompath = path.join(path.normpath(app.srcdir), 'dummy') + frompath = os.path.join(os.path.normpath(app.srcdir), 'dummy') deps = doctree.settings.record_dependencies if not deps: return @@ -48,7 +48,7 @@ def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: # one relative to the srcdir if isinstance(dep, bytes): dep = dep.decode(fs_encoding) - relpath = relative_path(frompath, path.normpath(path.join(cwd, dep))) + relpath = relative_path(frompath, os.path.normpath(os.path.join(cwd, dep))) app.env.dependencies[app.env.docname].add(relpath) diff --git a/sphinx/ext/apidoc.py b/sphinx/ext/apidoc.py index 0cab1d7f132..684eef57d38 100644 --- a/sphinx/ext/apidoc.py +++ b/sphinx/ext/apidoc.py @@ -16,11 +16,11 @@ import glob import locale import os +import os.path import re import sys from copy import copy from importlib.machinery import EXTENSION_SUFFIXES -from os import path from pathlib import Path from typing import TYPE_CHECKING, Any, Protocol @@ -50,7 +50,7 @@ PY_SUFFIXES = ('.py', '.pyx', *tuple(EXTENSION_SUFFIXES)) -template_dir = path.join(package_dir, 'templates', 'apidoc') +template_dir = os.path.join(package_dir, 'templates', 'apidoc') def is_initpy(filename: str | Path) -> bool: @@ -297,7 +297,7 @@ def recurse_tree( """ # check if the base directory is a package and get its name if is_packagedir(rootpath) or opts.implicit_namespaces: - root_package = rootpath.split(path.sep)[-1] + root_package = rootpath.split(os.path.sep)[-1] else: # otherwise, the base is a directory with packages root_package = None @@ -322,7 +322,7 @@ def recurse_tree( # we are in a package with something to document if subs or len(files) > 1 or not is_skipped_package(root, opts): subpackage = ( - root[len(rootpath) :].lstrip(path.sep).replace(path.sep, '.') + root[len(rootpath) :].lstrip(os.path.sep).replace(os.path.sep, '.') ) # if this is not a namespace or # a namespace and there is something there to document @@ -627,12 +627,12 @@ def main(argv: Sequence[str] = (), /) -> int: parser = get_parser() args: CliOptions = parser.parse_args(argv or sys.argv[1:]) - rootpath = path.abspath(args.module_path) + rootpath = os.path.abspath(args.module_path) # normalize opts if args.header is None: - args.header = rootpath.split(path.sep)[-1] + args.header = rootpath.split(os.path.sep)[-1] args.suffix = args.suffix.removeprefix('.') if not Path(rootpath).is_dir(): logger.error(__('%s is not a directory.'), rootpath) @@ -640,7 +640,7 @@ def main(argv: Sequence[str] = (), /) -> int: if not args.dryrun: ensuredir(args.destdir) excludes = tuple( - re.compile(fnmatch.translate(path.abspath(exclude))) + re.compile(fnmatch.translate(os.path.abspath(exclude))) for exclude in dict.fromkeys(args.exclude_pattern) ) written_files, modules = recurse_tree(rootpath, excludes, args, args.templatedir) diff --git a/sphinx/ext/autosummary/__init__.py b/sphinx/ext/autosummary/__init__.py index 3959f329c45..e77dbe91c79 100644 --- a/sphinx/ext/autosummary/__init__.py +++ b/sphinx/ext/autosummary/__init__.py @@ -52,11 +52,11 @@ import inspect import operator import os +import os.path import posixpath import re import sys from inspect import Parameter -from os import path from types import ModuleType from typing import TYPE_CHECKING, Any, ClassVar, cast @@ -814,7 +814,7 @@ def process_generate_options(app: Sphinx) -> None: for genfile in genfiles] for entry in genfiles[:]: - if not path.isfile(path.join(app.srcdir, entry)): + if not os.path.isfile(os.path.join(app.srcdir, entry)): logger.warning(__('autosummary_generate: file not found: %s'), entry) genfiles.remove(entry) diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index 5fbc51118bf..1d90438cfec 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -19,11 +19,11 @@ import inspect import locale import os +import os.path import pkgutil import pydoc import re import sys -from os import path from pathlib import Path from typing import TYPE_CHECKING, Any, NamedTuple @@ -853,7 +853,7 @@ def main(argv: Sequence[str] = (), /) -> None: args = get_parser().parse_args(argv or sys.argv[1:]) if args.templates: - app.config.templates_path.append(path.abspath(args.templates)) + app.config.templates_path.append(os.path.abspath(args.templates)) app.config.autosummary_ignore_module_all = not args.respect_module_all written_files = generate_autosummary_docs( diff --git a/sphinx/ext/coverage.py b/sphinx/ext/coverage.py index c9b997642d3..d1c4aea457c 100644 --- a/sphinx/ext/coverage.py +++ b/sphinx/ext/coverage.py @@ -8,12 +8,12 @@ import glob import inspect +import os.path import pickle import pkgutil import re import sys from importlib import import_module -from os import path from typing import IO, TYPE_CHECKING, Any, TextIO import sphinx @@ -161,12 +161,12 @@ class CoverageBuilder(Builder): name = 'coverage' epilog = __('Testing of coverage in the sources finished, look at the ' - 'results in %(outdir)s' + path.sep + 'python.txt.') + 'results in %(outdir)s' + os.path.sep + 'python.txt.') def init(self) -> None: self.c_sourcefiles: list[str] = [] for pattern in self.config.coverage_c_path: - pattern = path.join(self.srcdir, pattern) + pattern = os.path.join(self.srcdir, pattern) self.c_sourcefiles.extend(glob.glob(pattern)) self.c_regexes: list[tuple[str, re.Pattern[str]]] = [] @@ -230,7 +230,7 @@ def build_c_coverage(self) -> None: self.c_undoc[filename] = undoc def write_c_coverage(self) -> None: - output_file = path.join(self.outdir, 'c.txt') + output_file = os.path.join(self.outdir, 'c.txt') with open(output_file, 'w', encoding="utf-8") as op: if self.config.coverage_write_headline: write_header(op, 'Undocumented C API elements', '=') @@ -395,7 +395,7 @@ def _write_py_statistics(self, op: TextIO) -> None: op.write(f'{line}\n') def write_py_coverage(self) -> None: - output_file = path.join(self.outdir, 'python.txt') + output_file = os.path.join(self.outdir, 'python.txt') failed = [] with open(output_file, 'w', encoding="utf-8") as op: if self.config.coverage_write_headline: @@ -472,7 +472,7 @@ def write_py_coverage(self) -> None: def finish(self) -> None: # dump the coverage data to a pickle file too - picklepath = path.join(self.outdir, 'undoc.pickle') + picklepath = os.path.join(self.outdir, 'undoc.pickle') with open(picklepath, 'wb') as dumpfile: pickle.dump((self.py_undoc, self.c_undoc, self.py_undocumented, self.py_documented), dumpfile) diff --git a/sphinx/ext/doctest.py b/sphinx/ext/doctest.py index 0aead2db480..6c0327fab02 100644 --- a/sphinx/ext/doctest.py +++ b/sphinx/ext/doctest.py @@ -6,11 +6,11 @@ from __future__ import annotations import doctest +import os.path import re import sys import time from io import StringIO -from os import path from typing import TYPE_CHECKING, Any, ClassVar from docutils import nodes @@ -377,7 +377,7 @@ def get_line_number(node: Node) -> int | None: """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. - if ':docstring of ' in path.basename(node.source or ''): + if ':docstring of ' in os.path.basename(node.source or ''): # The line number is given relative to the stripped docstring, # not the file. This is correct where it is set, in # `docutils.nodes.Node.setup_child`, but Sphinx should report diff --git a/sphinx/ext/graphviz.py b/sphinx/ext/graphviz.py index 13bb5ea4111..eb85f2d4c99 100644 --- a/sphinx/ext/graphviz.py +++ b/sphinx/ext/graphviz.py @@ -3,13 +3,13 @@ from __future__ import annotations +import os.path import posixpath import re import subprocess import xml.etree.ElementTree as ET from hashlib import sha1 from itertools import chain -from os import path from subprocess import CalledProcessError from typing import TYPE_CHECKING, Any, ClassVar from urllib.parse import urlsplit, urlunsplit @@ -247,7 +247,7 @@ def fix_svg_relative_paths(self: HTML5Translator | LaTeXTranslator | TexinfoTran old_path = doc_dir / rel_uri img_path = doc_dir / self.builder.imgpath - new_path = path.relpath(old_path, start=img_path) + new_path = os.path.relpath(old_path, start=img_path) modified_url = urlunsplit((scheme, hostname, new_path, query, fragment)) element.set(href_name, modified_url) @@ -272,16 +272,16 @@ def render_dot(self: HTML5Translator | LaTeXTranslator | TexinfoTranslator, fname = f'{prefix}-{sha1(hashkey, usedforsecurity=False).hexdigest()}.{format}' relfn = posixpath.join(self.builder.imgpath, fname) - outfn = path.join(self.builder.outdir, self.builder.imagedir, fname) + outfn = os.path.join(self.builder.outdir, self.builder.imagedir, fname) - if path.isfile(outfn): + if os.path.isfile(outfn): return relfn, outfn if (hasattr(self.builder, '_graphviz_warned_dot') and self.builder._graphviz_warned_dot.get(graphviz_dot)): return None, None - ensuredir(path.dirname(outfn)) + ensuredir(os.path.dirname(outfn)) dot_args = [graphviz_dot] dot_args.extend(self.builder.config.graphviz_dot_args) @@ -289,9 +289,9 @@ def render_dot(self: HTML5Translator | LaTeXTranslator | TexinfoTranslator, docname = options.get('docname', 'index') if filename: - cwd = path.dirname(path.join(self.builder.srcdir, filename)) + cwd = os.path.dirname(os.path.join(self.builder.srcdir, filename)) else: - cwd = path.dirname(path.join(self.builder.srcdir, docname)) + cwd = os.path.dirname(os.path.join(self.builder.srcdir, docname)) if format == 'png': dot_args.extend(['-Tcmapx', '-o%s.map' % outfn]) @@ -309,7 +309,7 @@ def render_dot(self: HTML5Translator | LaTeXTranslator | TexinfoTranslator, except CalledProcessError as exc: raise GraphvizError(__('dot exited with error:\n[stderr]\n%r\n' '[stdout]\n%r') % (exc.stderr, exc.stdout)) from exc - if not path.isfile(outfn): + if not os.path.isfile(outfn): raise GraphvizError(__('dot did not produce an output file:\n[stderr]\n%r\n' '[stdout]\n%r') % (ret.stderr, ret.stdout)) @@ -448,7 +448,7 @@ def man_visit_graphviz(self: ManualPageTranslator, node: graphviz) -> None: def on_config_inited(_app: Sphinx, config: Config) -> None: - css_path = path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css') + css_path = os.path.join(sphinx.package_dir, 'templates', 'graphviz', 'graphviz.css') config.html_static_path.append(css_path) diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index aed8252a5cf..2a06dd208bb 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -6,12 +6,13 @@ import base64 import contextlib +import os +import os.path import re import shutil import subprocess import tempfile from hashlib import sha1 -from os import path from subprocess import CalledProcessError from typing import TYPE_CHECKING @@ -28,7 +29,6 @@ from sphinx.util.template import LaTeXRenderer if TYPE_CHECKING: - import os from docutils.nodes import Element @@ -40,7 +40,7 @@ logger = logging.getLogger(__name__) -templates_path = path.join(package_dir, 'templates', 'imgmath') +templates_path = os.path.join(package_dir, 'templates', 'imgmath') class MathExtError(SphinxError): @@ -109,8 +109,8 @@ def generate_latex_macro(image_format: str, for template_dir in config.templates_path: for template_suffix in ('.jinja', '_t'): - template = path.join(confdir, template_dir, template_name + template_suffix) - if path.exists(template): + template = os.path.join(confdir, template_dir, template_name + template_suffix) + if os.path.exists(template): return LaTeXRenderer().render(template, variables) return LaTeXRenderer(templates_path).render(template_name + '.jinja', variables) @@ -132,11 +132,11 @@ def ensure_tempdir(builder: Builder) -> str: def compile_math(latex: str, builder: Builder) -> str: """Compile LaTeX macros for math to DVI.""" tempdir = ensure_tempdir(builder) - filename = path.join(tempdir, 'math.tex') + filename = os.path.join(tempdir, 'math.tex') with open(filename, 'w', encoding='utf-8') as f: f.write(latex) - imgmath_latex_name = path.basename(builder.config.imgmath_latex) + imgmath_latex_name = os.path.basename(builder.config.imgmath_latex) # build latex command; old versions of latex don't have the # --output-directory option, so we have to manually chdir to the @@ -152,9 +152,9 @@ def compile_math(latex: str, builder: Builder) -> str: subprocess.run(command, capture_output=True, cwd=tempdir, check=True, encoding='ascii') if imgmath_latex_name in {'xelatex', 'tectonic'}: - return path.join(tempdir, 'math.xdv') + return os.path.join(tempdir, 'math.xdv') else: - return path.join(tempdir, 'math.dvi') + return os.path.join(tempdir, 'math.dvi') except OSError as exc: logger.warning(__('LaTeX command %r cannot be run (needed for math ' 'display), check the imgmath_latex setting'), @@ -251,9 +251,9 @@ def render_math( self.builder.confdir) filename = f"{sha1(latex.encode(), usedforsecurity=False).hexdigest()}.{image_format}" - generated_path = path.join(self.builder.outdir, self.builder.imagedir, 'math', filename) - ensuredir(path.dirname(generated_path)) - if path.isfile(generated_path): + generated_path = os.path.join(self.builder.outdir, self.builder.imagedir, 'math', filename) + ensuredir(os.path.dirname(generated_path)) + if os.path.isfile(generated_path): if image_format == 'png': depth = read_png_depth(generated_path) elif image_format == 'svg': @@ -308,7 +308,7 @@ def clean_up_files(app: Sphinx, exc: Exception) -> None: # in embed mode, the images are still generated in the math output dir # to be shared across workers, but are not useful to the final document with contextlib.suppress(Exception): - shutil.rmtree(path.join(app.builder.outdir, app.builder.imagedir, 'math')) + shutil.rmtree(os.path.join(app.builder.outdir, app.builder.imagedir, 'math')) def get_tooltip(self: HTML5Translator, node: Element) -> str: @@ -337,9 +337,9 @@ def html_visit_math(self: HTML5Translator, node: nodes.math) -> None: image_format = self.builder.config.imgmath_image_format.lower() img_src = render_maths_to_base64(image_format, rendered_path) else: - bname = path.basename(rendered_path) - relative_path = path.join(self.builder.imgpath, 'math', bname) - img_src = relative_path.replace(path.sep, '/') + bname = os.path.basename(rendered_path) + relative_path = os.path.join(self.builder.imgpath, 'math', bname) + img_src = relative_path.replace(os.path.sep, '/') c = f' Non image_format = self.builder.config.imgmath_image_format.lower() img_src = render_maths_to_base64(image_format, rendered_path) else: - bname = path.basename(rendered_path) - relative_path = path.join(self.builder.imgpath, 'math', bname) - img_src = relative_path.replace(path.sep, '/') + bname = os.path.basename(rendered_path) + relative_path = os.path.join(self.builder.imgpath, 'math', bname) + img_src = relative_path.replace(os.path.sep, '/') self.body.append(f'

\n') raise nodes.SkipNode diff --git a/sphinx/ext/inheritance_diagram.py b/sphinx/ext/inheritance_diagram.py index ed2a8c0936b..47dec78aa9f 100644 --- a/sphinx/ext/inheritance_diagram.py +++ b/sphinx/ext/inheritance_diagram.py @@ -33,10 +33,10 @@ class E(B): pass import builtins import hashlib import inspect +import os.path import re from collections.abc import Iterable, Sequence from importlib import import_module -from os import path from typing import TYPE_CHECKING, Any, ClassVar, cast from docutils import nodes @@ -422,7 +422,7 @@ def html_visit_inheritance_diagram(self: HTML5Translator, node: inheritance_diag # Create a mapping from fully-qualified class names to URLs. graphviz_output_format = self.builder.env.config.graphviz_output_format.upper() - current_filename = path.basename(self.builder.current_docname + self.builder.out_suffix) + current_filename = os.path.basename(self.builder.current_docname + self.builder.out_suffix) urls = {} pending_xrefs = cast(Iterable[addnodes.pending_xref], node) for child in pending_xrefs: diff --git a/sphinx/ext/intersphinx/_load.py b/sphinx/ext/intersphinx/_load.py index dde2e34e1c2..eb8b46be807 100644 --- a/sphinx/ext/intersphinx/_load.py +++ b/sphinx/ext/intersphinx/_load.py @@ -3,10 +3,10 @@ from __future__ import annotations import concurrent.futures +import os.path import posixpath import time from operator import itemgetter -from os import path from typing import TYPE_CHECKING from urllib.parse import urlsplit, urlunsplit @@ -303,7 +303,7 @@ def _fetch_inventory( if '://' in inv_location: f: _ReadableStream[bytes] = _read_from_url(inv_location, config=config) else: - f = open(path.join(srcdir, inv_location), 'rb') # NoQA: SIM115 + f = open(os.path.join(srcdir, inv_location), 'rb') # NoQA: SIM115 except Exception as err: err.args = ( 'intersphinx inventory %r not fetchable due to %s: %s', @@ -321,10 +321,10 @@ def _fetch_inventory( if target_uri in { inv_location, - path.dirname(inv_location), - path.dirname(inv_location) + '/', + os.path.dirname(inv_location), + os.path.dirname(inv_location) + '/', }: - target_uri = path.dirname(new_inv_location) + target_uri = os.path.dirname(new_inv_location) with f: try: invdata = InventoryFile.load(f, target_uri, posixpath.join) diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index 91ba1f049ed..ebfe4322f84 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -3,10 +3,10 @@ from __future__ import annotations import operator +import os.path import posixpath import traceback from importlib import import_module -from os import path from typing import TYPE_CHECKING, Any, cast from docutils import nodes @@ -229,7 +229,7 @@ def should_generate_module_page(app: Sphinx, modname: str) -> bool: builder = cast(StandaloneHTMLBuilder, app.builder) basename = modname.replace('.', '/') + builder.out_suffix - page_filename = path.join(app.outdir, '_modules/', basename) + page_filename = os.path.join(app.outdir, '_modules/', basename) try: if _last_modified_time(module_filename) <= _last_modified_time(page_filename): diff --git a/sphinx/jinja2glue.py b/sphinx/jinja2glue.py index 3621d417b94..909505fe5a2 100644 --- a/sphinx/jinja2glue.py +++ b/sphinx/jinja2glue.py @@ -3,7 +3,7 @@ from __future__ import annotations import os -from os import path +import os.path from pprint import pformat from typing import TYPE_CHECKING, Any @@ -127,12 +127,12 @@ def get_source( legacy_template = None for searchpath in self.searchpath: - filename = path.join(searchpath, template) + filename = os.path.join(searchpath, template) f = open_if_exists(filename) if f is not None: break if legacy_template is not None: - filename = path.join(searchpath, legacy_template) + filename = os.path.join(searchpath, legacy_template) f = open_if_exists(filename) if f is not None: break diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 4094d7a9b8b..81f0519a5a1 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -3,9 +3,9 @@ from __future__ import annotations import os +import os.path import tokenize from importlib import import_module -from os import path from typing import TYPE_CHECKING, Any, Literal from sphinx.errors import PycodeError @@ -60,15 +60,15 @@ def get_module_source(modname: str) -> tuple[str | None, str | None]: if filename is None: # all methods for getting filename failed, so raise... raise PycodeError('no source found for module %r' % modname) - filename = path.normpath(path.abspath(filename)) + filename = os.path.normpath(os.path.abspath(filename)) if filename.lower().endswith(('.pyo', '.pyc')): filename = filename[:-1] - if not path.isfile(filename) and path.isfile(filename + 'w'): + if not os.path.isfile(filename) and os.path.isfile(filename + 'w'): filename += 'w' elif not filename.lower().endswith(('.py', '.pyw')): raise PycodeError('source is not a .py file: %r' % filename) - if not path.isfile(filename): + if not os.path.isfile(filename): raise PycodeError('source file is not present: %r' % filename) return filename, None diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index 2dd9fbc9280..4c464d5e260 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -3,7 +3,7 @@ from __future__ import annotations import contextlib -from os import path +import os.path from re import DOTALL, match from textwrap import indent from typing import TYPE_CHECKING, Any, TypeVar @@ -395,7 +395,7 @@ def apply(self, **kwargs: Any) -> None: # fetch translations dirs = [ - path.join(self.env.srcdir, directory) + os.path.join(self.env.srcdir, directory) for directory in self.config.locale_dirs ] catalog, has_catalog = init_locale(dirs, self.config.language, textdomain) diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index cd5619a729e..826696c83e3 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -3,9 +3,9 @@ from __future__ import annotations import os +import os.path import re from datetime import datetime -from os import path from typing import TYPE_CHECKING import babel.dates @@ -151,7 +151,7 @@ def pofiles(self) -> Iterator[tuple[_StrPath, _StrPath]]: @property def catalogs(self) -> Iterator[CatalogInfo]: for basedir, filename in self.pofiles: - domain = canon_path(path.splitext(filename)[0]) + domain = canon_path(os.path.splitext(filename)[0]) yield CatalogInfo(basedir, domain, self.encoding) @@ -290,15 +290,15 @@ def get_image_filename_for_language( filename: str | os.PathLike[str], env: BuildEnvironment, ) -> str: - root, ext = path.splitext(filename) - dirname = path.dirname(root) - docpath = path.dirname(env.docname) + root, ext = os.path.splitext(filename) + dirname = os.path.dirname(root) + docpath = os.path.dirname(env.docname) try: return env.config.figure_language_filename.format( root=root, ext=ext, path=dirname and dirname + SEP, - basename=path.basename(root), + basename=os.path.basename(root), docpath=docpath and docpath + SEP, language=env.config.language, ) @@ -310,7 +310,7 @@ def get_image_filename_for_language( def search_image_for_language(filename: str, env: BuildEnvironment) -> str: translated = get_image_filename_for_language(filename, env) _, abspath = env.relfn2path(translated) - if path.exists(abspath): + if os.path.exists(abspath): return translated else: return filename diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 4a6a58698e4..634be6b6db8 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -3,7 +3,7 @@ from __future__ import annotations import base64 -from os import path +import os.path from typing import TYPE_CHECKING, NamedTuple, overload import imagesize @@ -68,10 +68,10 @@ def guess_mimetype( filename: PathLike[str] | str = '', default: str | None = None, ) -> str | None: - ext = path.splitext(filename)[1].lower() + ext = os.path.splitext(filename)[1].lower() if ext in mime_suffixes: return mime_suffixes[ext] - if path.exists(filename): + if os.path.exists(filename): try: imgtype = _image_type_from_file(filename) except ValueError: diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 7ec14d446b2..009f8cc9b7a 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -5,12 +5,12 @@ import contextlib import filecmp import os +import os.path import re import shutil import sys import unicodedata from io import StringIO -from os import path from pathlib import Path from typing import TYPE_CHECKING @@ -28,12 +28,12 @@ def os_path(canonical_path: str, /) -> str: - return canonical_path.replace(SEP, path.sep) + return canonical_path.replace(SEP, os.path.sep) def canon_path(native_path: str | os.PathLike[str], /) -> str: """Return path in OS-independent form""" - return os.fspath(native_path).replace(path.sep, SEP) + return os.fspath(native_path).replace(os.path.sep, SEP) def path_stabilize(filepath: str | os.PathLike[str], /) -> str: @@ -173,7 +173,7 @@ def relpath( fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() -abspath = path.abspath +abspath = os.path.abspath class FileAvoidWrite: diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index a9fbd0161c0..ec49bc721c7 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -6,10 +6,10 @@ from __future__ import annotations +import os.path import re from collections import defaultdict from collections.abc import Iterable -from os import path from typing import TYPE_CHECKING, Any, ClassVar, cast from docutils import nodes, writers @@ -583,12 +583,12 @@ def generate( def render(self, template_name: str, variables: dict[str, Any]) -> str: renderer = LaTeXRenderer(latex_engine=self.config.latex_engine) for template_dir in self.config.templates_path: - template = path.join(self.builder.confdir, template_dir, template_name) - if path.exists(template): + template = os.path.join(self.builder.confdir, template_dir, template_name) + if os.path.exists(template): return renderer.render(template, variables) elif template.endswith('.jinja'): legacy_template = template.removesuffix('.jinja') + '_t' - if path.exists(legacy_template): + if os.path.exists(legacy_template): logger.warning( __('template %s not found; loading from legacy %s instead'), template_name, @@ -1648,7 +1648,7 @@ def visit_image(self, node: Element) -> None: options = '' if include_graphics_options: options = '[%s]' % ','.join(include_graphics_options) - base, ext = path.splitext(uri) + base, ext = os.path.splitext(uri) if self.in_title and base: # Lowercase tokens forcely because some fncychap themes capitalize diff --git a/sphinx/writers/texinfo.py b/sphinx/writers/texinfo.py index 426b0288390..396e48bf91a 100644 --- a/sphinx/writers/texinfo.py +++ b/sphinx/writers/texinfo.py @@ -2,10 +2,10 @@ from __future__ import annotations +import os.path import re import textwrap from collections.abc import Iterable, Iterator -from os import path from typing import TYPE_CHECKING, Any, ClassVar, cast from docutils import nodes, writers @@ -1232,7 +1232,7 @@ def visit_image(self, node: Element) -> None: if uri.find('://') != -1: # ignore remote images return - name, ext = path.splitext(uri) + name, ext = os.path.splitext(uri) # width and height ignored in non-tex output width = self.tex_image_length(node.get('width', '')) height = self.tex_image_length(node.get('height', '')) From c1172022ed56ee44b0ad023fd4056ce55cd9d6bf Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:49:44 +0000 Subject: [PATCH 22/39] Prefer using ``BuildEnvironment.note_dependency()`` --- sphinx/environment/__init__.py | 10 +++++++--- sphinx/environment/collectors/asset.py | 4 ++-- sphinx/environment/collectors/dependencies.py | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 68d0249b151..37a18c35172 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -467,7 +467,7 @@ def find_files(self, config: Config, builder: Builder) -> None: for docname in self.found_docs: domain = docname_to_domain(docname, self.config.gettext_compact) if domain in mo_paths: - self.dependencies[docname].add(str(mo_paths[domain])) + self.note_dependency(mo_paths[domain], docname=docname) except OSError as exc: raise DocumentError( __('Failed to scan documents in %s: %r') % (self.srcdir, exc) @@ -585,14 +585,18 @@ def new_serialno(self, category: str = '') -> int: self.temp_data[key] = cur + 1 return cur - def note_dependency(self, filename: str) -> None: + def note_dependency( + self, filename: str | os.PathLike[str], *, docname: str | None = None + ) -> None: """Add *filename* as a dependency of the current document. This means that the document will be rebuilt if this file changes. *filename* should be absolute or relative to the source directory. """ - self.dependencies[self.docname].add(filename) + if docname is None: + docname = self.docname + self.dependencies[docname].add(os.fspath(filename)) def note_included(self, filename: str) -> None: """Add *filename* as a included from other document. diff --git a/sphinx/environment/collectors/asset.py b/sphinx/environment/collectors/asset.py index 838391aa7da..e7d976682b0 100644 --- a/sphinx/environment/collectors/asset.py +++ b/sphinx/environment/collectors/asset.py @@ -89,7 +89,7 @@ def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: # map image paths to unique image names (so that they can be put # into a single directory) for imgpath in candidates.values(): - app.env.dependencies[docname].add(imgpath) + app.env.note_dependency(imgpath) if not os.access(os.path.join(app.srcdir, imgpath), os.R_OK): logger.warning( __('image file not readable: %s'), @@ -154,7 +154,7 @@ def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: node['refuri'] = targetname else: rel_filename, filename = app.env.relfn2path(targetname, app.env.docname) - app.env.dependencies[app.env.docname].add(rel_filename) + app.env.note_dependency(rel_filename) if not os.access(filename, os.R_OK): logger.warning( __('download file not readable: %s'), diff --git a/sphinx/environment/collectors/dependencies.py b/sphinx/environment/collectors/dependencies.py index 1173dc3b24a..30701314be3 100644 --- a/sphinx/environment/collectors/dependencies.py +++ b/sphinx/environment/collectors/dependencies.py @@ -49,7 +49,7 @@ def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: if isinstance(dep, bytes): dep = dep.decode(fs_encoding) relpath = relative_path(frompath, os.path.normpath(os.path.join(cwd, dep))) - app.env.dependencies[app.env.docname].add(relpath) + app.env.note_dependency(relpath) def setup(app: Sphinx) -> ExtensionMetadata: From fbb23071bff27c0ea408e4d9c9d3409a930e4b3d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:52:39 +0000 Subject: [PATCH 23/39] Accept PathLike in ``BuildEnvironment.note_included()`` --- sphinx/environment/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 37a18c35172..e63e6c3833d 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -598,7 +598,7 @@ def note_dependency( docname = self.docname self.dependencies[docname].add(os.fspath(filename)) - def note_included(self, filename: str) -> None: + def note_included(self, filename: str | os.PathLike[str]) -> None: """Add *filename* as a included from other document. This means the document is not orphaned. From 1266c421be03dc9a5cbcf69a7c292eb71d21740d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sun, 3 Nov 2024 22:54:00 +0000 Subject: [PATCH 24/39] Use pathlib in ``sys.path`` manipulation examples --- doc/development/tutorials/extending_build.rst | 4 ++-- doc/development/tutorials/extending_syntax.rst | 4 ++-- doc/usage/configuration.rst | 7 +++++-- doc/usage/extensions/index.rst | 9 ++++++--- 4 files changed, 15 insertions(+), 9 deletions(-) diff --git a/doc/development/tutorials/extending_build.rst b/doc/development/tutorials/extending_build.rst index a81c84b0075..4d3606a0a33 100644 --- a/doc/development/tutorials/extending_build.rst +++ b/doc/development/tutorials/extending_build.rst @@ -313,10 +313,10 @@ For example: .. code-block:: python - import os import sys + from pathlib import Path - sys.path.append(os.path.abspath("./_ext")) + sys.path.append(str(Path('_ext').resolve())) extensions = ['todo'] diff --git a/doc/development/tutorials/extending_syntax.rst b/doc/development/tutorials/extending_syntax.rst index bab80371703..a8a5bfe62ec 100644 --- a/doc/development/tutorials/extending_syntax.rst +++ b/doc/development/tutorials/extending_syntax.rst @@ -169,10 +169,10 @@ For example: .. code-block:: python - import os import sys + from pathlib import Path - sys.path.append(os.path.abspath("./_ext")) + sys.path.append(str(Path('_ext').resolve())) extensions = ['helloworld'] diff --git a/doc/usage/configuration.rst b/doc/usage/configuration.rst index 9768d248cce..40fe5931250 100644 --- a/doc/usage/configuration.rst +++ b/doc/usage/configuration.rst @@ -223,11 +223,14 @@ General configuration Ensure that absolute paths are used when modifying :data:`sys.path`. If your custom extensions live in a directory that is relative to the - :term:`configuration directory`, use :func:`os.path.abspath` like so: + :term:`configuration directory`, use :meth:`pathlib.Path.resolve` like so: .. code-block:: python - import os, sys; sys.path.append(os.path.abspath('sphinxext')) + import sys + from pathlib import Path + + sys.path.append(str(Path('sphinxext').resolve())) extensions = [ ... diff --git a/doc/usage/extensions/index.rst b/doc/usage/extensions/index.rst index 929f2b604b2..4be426c3fe2 100644 --- a/doc/usage/extensions/index.rst +++ b/doc/usage/extensions/index.rst @@ -69,11 +69,14 @@ Where to put your own extensions? Extensions local to a project should be put within the project's directory structure. Set Python's module search path, ``sys.path``, accordingly so that Sphinx can find them. For example, if your extension ``foo.py`` lies in the -``exts`` subdirectory of the project root, put into :file:`conf.py`:: +``exts`` subdirectory of the project root, put into :file:`conf.py`: - import sys, os +.. code-block:: python - sys.path.append(os.path.abspath('exts')) + import sys + from pathlib import Path + + sys.path.append(str(Path('exts').resolve())) extensions = ['foo'] From 2ccbc3209ab8ead125a27dadaac26055b117fb73 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:26:05 +0000 Subject: [PATCH 25/39] Use type hints for directories --- sphinx/builders/__init__.py | 9 +++++---- sphinx/environment/__init__.py | 5 ++--- sphinx/writers/html5.py | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index f1733184243..1420417380d 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -48,6 +48,7 @@ from sphinx.application import Sphinx from sphinx.config import Config from sphinx.events import EventManager + from sphinx.util._pathlib import _StrPath from sphinx.util.tags import Tags @@ -94,10 +95,10 @@ class Builder: supported_data_uri_images: bool = False def __init__(self, app: Sphinx, env: BuildEnvironment) -> None: - self.srcdir = app.srcdir - self.confdir = app.confdir - self.outdir = app.outdir - self.doctreedir = app.doctreedir + self.srcdir: _StrPath = app.srcdir + self.confdir: _StrPath = app.confdir + self.outdir: _StrPath = app.outdir + self.doctreedir: _StrPath = app.doctreedir ensuredir(self.doctreedir) self.app: Sphinx = app diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index e63e6c3833d..397b3667e10 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -32,7 +32,6 @@ if TYPE_CHECKING: from collections.abc import Callable, Iterable, Iterator - from pathlib import Path from typing import Any, Literal from docutils import nodes @@ -102,8 +101,8 @@ class BuildEnvironment: def __init__(self, app: Sphinx) -> None: self.app: Sphinx = app - self.doctreedir: Path = app.doctreedir - self.srcdir: Path = app.srcdir + self.doctreedir: _StrPath = app.doctreedir + self.srcdir: _StrPath = app.srcdir self.config: Config = None # type: ignore[assignment] self.config_status: int = CONFIG_UNSET self.config_status_extra: str = '' diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index b6e342ac119..0ca97c5f361 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -752,7 +752,7 @@ def visit_image(self, node: Element) -> None: # but it tries the final file name, which does not necessarily exist # yet at the time the HTML file is written. if not ('width' in node and 'height' in node): - path = os.path.join(self.builder.srcdir, olduri) # type: ignore[has-type] + path = os.path.join(self.builder.srcdir, olduri) size = get_image_size(path) if size is None: logger.warning( From 6f0c8f75ac164d5d2cc02bba2d63560857f09d68 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:28:30 +0000 Subject: [PATCH 26/39] Improve lookup in ``Project.path2doc()`` --- sphinx/project.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/project.py b/sphinx/project.py index 01642999306..74499086034 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -96,10 +96,10 @@ def path2doc(self, filename: str | os.PathLike[str]) -> str | None: *filename* should be absolute or relative to the source directory. """ + path = Path(filename) try: - return self._path_to_docname[filename] # type: ignore[index] + return self._path_to_docname[path] except KeyError: - path = Path(filename) if path.is_absolute(): with contextlib.suppress(ValueError): path = path.relative_to(self.srcdir) From e83432792514998c9bca316319765f3856d6901d Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 4 Nov 2024 18:10:11 +0000 Subject: [PATCH 27/39] Use ``_StrPath`` in ``sphinx.util.images`` --- sphinx/util/images.py | 10 ++++++---- sphinx/writers/html5.py | 4 +--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/sphinx/util/images.py b/sphinx/util/images.py index 634be6b6db8..f1e7344eb7a 100644 --- a/sphinx/util/images.py +++ b/sphinx/util/images.py @@ -3,7 +3,7 @@ from __future__ import annotations import base64 -import os.path +from pathlib import Path from typing import TYPE_CHECKING, NamedTuple, overload import imagesize @@ -37,7 +37,8 @@ class DataURI(NamedTuple): data: bytes -def get_image_size(filename: str) -> tuple[int, int] | None: +def get_image_size(filename: str | PathLike[str]) -> tuple[int, int] | None: + filename = Path(filename) try: size = imagesize.get(filename) if size[0] == -1: @@ -68,10 +69,11 @@ def guess_mimetype( filename: PathLike[str] | str = '', default: str | None = None, ) -> str | None: - ext = os.path.splitext(filename)[1].lower() + filename = Path(filename) + ext = filename.suffix.lower() if ext in mime_suffixes: return mime_suffixes[ext] - if os.path.exists(filename): + if filename.exists(): try: imgtype = _image_type_from_file(filename) except ValueError: diff --git a/sphinx/writers/html5.py b/sphinx/writers/html5.py index 0ca97c5f361..608a84f34db 100644 --- a/sphinx/writers/html5.py +++ b/sphinx/writers/html5.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os import posixpath import re import urllib.parse @@ -752,8 +751,7 @@ def visit_image(self, node: Element) -> None: # but it tries the final file name, which does not necessarily exist # yet at the time the HTML file is written. if not ('width' in node and 'height' in node): - path = os.path.join(self.builder.srcdir, olduri) - size = get_image_size(path) + size = get_image_size(self.builder.srcdir / olduri) if size is None: logger.warning( __('Could not obtain image size. :scale: option is ignored.'), From 230ccf2a44d750f5d6c5542f970f48ffe0cbce1e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 4 Nov 2024 21:14:30 +0000 Subject: [PATCH 28/39] Use ``_StrPath`` in ``sphinx.environment`` --- sphinx/environment/__init__.py | 35 ++++++++++++++++------------------ sphinx/util/osutil.py | 16 ++++++++++++++++ 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 397b3667e10..b17c1ee453b 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -4,7 +4,6 @@ import functools import os -import os.path import pickle from collections import defaultdict from copy import copy @@ -28,7 +27,7 @@ from sphinx.util.docutils import LoggingReporter from sphinx.util.i18n import CatalogRepository, docname_to_domain from sphinx.util.nodes import is_translatable -from sphinx.util.osutil import _last_modified_time, canon_path, os_path +from sphinx.util.osutil import _last_modified_time, _relative_path, canon_path if TYPE_CHECKING: from collections.abc import Callable, Iterable, Iterator @@ -419,17 +418,15 @@ def relfn2path(self, filename: str, docname: str | None = None) -> tuple[str, st source dir, while relative filenames are relative to the dir of the containing document. """ - filename = os_path(filename) - if filename.startswith(('/', os.sep)): - rel_fn = filename[1:] + filename = canon_path(filename) + if filename.startswith('/'): + abs_fn = (self.srcdir / filename[1:]).resolve() else: - docdir = os.path.dirname(self.doc2path(docname or self.docname, base=False)) - rel_fn = os.path.join(docdir, filename) + doc_dir = self.doc2path(docname or self.docname, base=False).parent + abs_fn = (self.srcdir / doc_dir / filename).resolve() - return ( - canon_path(os.path.normpath(rel_fn)), - os.path.normpath(os.path.join(self.srcdir, rel_fn)), - ) + rel_fn = _relative_path(abs_fn, self.srcdir) + return canon_path(rel_fn), os.fspath(abs_fn) @property def found_docs(self) -> set[str]: @@ -492,8 +489,8 @@ def get_outdated_files( added.add(docname) continue # if the doctree file is not there, rebuild - filename = os.path.join(self.doctreedir, docname + '.doctree') - if not os.path.isfile(filename): + filename = self.doctreedir / f'{docname}.doctree' + if not filename.is_file(): logger.debug('[build target] changed %r', docname) changed.add(docname) continue @@ -518,21 +515,21 @@ def get_outdated_files( for dep in self.dependencies[docname]: try: # this will do the right thing when dep is absolute too - deppath = os.path.join(self.srcdir, dep) - if not os.path.isfile(deppath): + dep_path = self.srcdir / dep + if not dep_path.is_file(): logger.debug( '[build target] changed %r missing dependency %r', docname, - deppath, + dep_path, ) changed.add(docname) break - depmtime = _last_modified_time(deppath) + depmtime = _last_modified_time(dep_path) if depmtime > mtime: logger.debug( '[build target] outdated %r from dependency %r: %s -> %s', docname, - deppath, + dep_path, _format_rfc3339_microseconds(mtime), _format_rfc3339_microseconds(depmtime), ) @@ -632,7 +629,7 @@ def get_doctree(self, docname: str) -> nodes.document: try: serialised = self._pickled_doctree_cache[docname] except KeyError: - filename = os.path.join(self.doctreedir, docname + '.doctree') + filename = self.doctreedir / f'{docname}.doctree' with open(filename, 'rb') as f: serialised = self._pickled_doctree_cache[docname] = f.read() diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 009f8cc9b7a..779727b394b 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -169,6 +169,22 @@ def relpath( return str(path) +def _relative_path(path: Path, root: Path, /) -> Path: + """Return a relative filepath to *path* from the given *root* directory. + + This is an alternative of ``Path.relative_to``. + It returns the original path if *path* and *root* are on different drives, + which may happen on Windows. + """ + if path.anchor != root.anchor or '..' in root.parts: + # If the drives are different, no relative path exists. + # Path.relative_to() requires fully-resolved paths (no '..'). + return path + if sys.version_info[:2] < (3, 12): + return Path(os.path.relpath(path, root)) + return path.relative_to(root, walk_up=True) + + safe_relpath = relpath # for compatibility fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() From d374a1c30d348c95a89b07810fc6bd4a494783e5 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 4 Nov 2024 22:30:09 +0000 Subject: [PATCH 29/39] Use pathlib version of ``relative_path`` --- sphinx/builders/html/__init__.py | 4 ++-- sphinx/environment/collectors/asset.py | 7 ++++--- sphinx/environment/collectors/dependencies.py | 12 ++++-------- sphinx/ext/intersphinx/_resolve.py | 6 +++--- sphinx/util/fileutil.py | 11 +++++------ 5 files changed, 18 insertions(+), 22 deletions(-) diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 0dd25fbb644..9a1001fceaf 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -20,7 +20,6 @@ from docutils.core import Publisher from docutils.frontend import OptionParser from docutils.io import DocTreeInput, StringOutput -from docutils.utils import relative_path from sphinx import __display_version__, package_dir from sphinx import version_info as sphinx_version @@ -56,6 +55,7 @@ from sphinx.util.osutil import ( SEP, _last_modified_time, + _relative_path, copyfile, ensuredir, relative_uri, @@ -795,7 +795,7 @@ def copy_image_files(self) -> None: def copy_download_files(self) -> None: def to_relpath(f: str) -> str: - return relative_path(self.srcdir, f) + return _relative_path(Path(f), self.srcdir).as_posix() # copy downloadable files if self.env.dlfiles: diff --git a/sphinx/environment/collectors/asset.py b/sphinx/environment/collectors/asset.py index e7d976682b0..44f72294520 100644 --- a/sphinx/environment/collectors/asset.py +++ b/sphinx/environment/collectors/asset.py @@ -5,10 +5,10 @@ import os import os.path from glob import glob +from pathlib import Path from typing import TYPE_CHECKING from docutils import nodes -from docutils.utils import relative_path from sphinx import addnodes from sphinx.environment.collectors import EnvironmentCollector @@ -16,6 +16,7 @@ from sphinx.util import logging from sphinx.util.i18n import get_image_filename_for_language, search_image_for_language from sphinx.util.images import guess_mimetype +from sphinx.util.osutil import _relative_path if TYPE_CHECKING: from docutils.nodes import Node @@ -110,14 +111,14 @@ def collect_candidates( ) -> None: globbed: dict[str, list[str]] = {} for filename in glob(imgpath): - new_imgpath = relative_path(os.path.join(env.srcdir, 'dummy'), filename) + new_imgpath = _relative_path(Path(filename), env.srcdir) try: mimetype = guess_mimetype(filename) if mimetype is None: basename, suffix = os.path.splitext(filename) mimetype = 'image/x-' + suffix[1:] if mimetype not in candidates: - globbed.setdefault(mimetype, []).append(new_imgpath) + globbed.setdefault(mimetype, []).append(new_imgpath.as_posix()) except OSError as err: logger.warning( __('image file %s not readable: %s'), diff --git a/sphinx/environment/collectors/dependencies.py b/sphinx/environment/collectors/dependencies.py index 30701314be3..d77731218b1 100644 --- a/sphinx/environment/collectors/dependencies.py +++ b/sphinx/environment/collectors/dependencies.py @@ -2,14 +2,11 @@ from __future__ import annotations -import os -import os.path +from pathlib import Path from typing import TYPE_CHECKING -from docutils.utils import relative_path - from sphinx.environment.collectors import EnvironmentCollector -from sphinx.util.osutil import fs_encoding +from sphinx.util.osutil import _relative_path, fs_encoding if TYPE_CHECKING: from docutils import nodes @@ -38,8 +35,7 @@ def merge_other( def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: """Process docutils-generated dependency info.""" - cwd = os.getcwd() - frompath = os.path.join(os.path.normpath(app.srcdir), 'dummy') + cwd = Path.cwd() deps = doctree.settings.record_dependencies if not deps: return @@ -48,7 +44,7 @@ def process_doc(self, app: Sphinx, doctree: nodes.document) -> None: # one relative to the srcdir if isinstance(dep, bytes): dep = dep.decode(fs_encoding) - relpath = relative_path(frompath, os.path.normpath(os.path.join(cwd, dep))) + relpath = _relative_path(cwd / dep, app.srcdir) app.env.note_dependency(relpath) diff --git a/sphinx/ext/intersphinx/_resolve.py b/sphinx/ext/intersphinx/_resolve.py index be279b8c350..0dbab63dc69 100644 --- a/sphinx/ext/intersphinx/_resolve.py +++ b/sphinx/ext/intersphinx/_resolve.py @@ -2,12 +2,11 @@ from __future__ import annotations -import posixpath import re +from pathlib import Path from typing import TYPE_CHECKING, cast from docutils import nodes -from docutils.utils import relative_path from sphinx.addnodes import pending_xref from sphinx.deprecation import _deprecation_warning @@ -16,6 +15,7 @@ from sphinx.locale import _, __ from sphinx.transforms.post_transforms import ReferencesResolver from sphinx.util.docutils import CustomReSTDispatcher, SphinxRole +from sphinx.util.osutil import _relative_path if TYPE_CHECKING: from collections.abc import Iterable @@ -42,7 +42,7 @@ def _create_element_from_result( proj, version, uri, dispname = data if '://' not in uri and node.get('refdoc'): # get correct path in case of subdirectories - uri = posixpath.join(relative_path(node['refdoc'], '.'), uri) + uri = (_relative_path(Path(), Path(node['refdoc']).parent) / uri).as_posix() if version: reftitle = _('(in %s v%s)') % (proj, version) else: diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py index acd52b07674..d5e2e2692d5 100644 --- a/sphinx/util/fileutil.py +++ b/sphinx/util/fileutil.py @@ -7,11 +7,9 @@ from pathlib import Path from typing import TYPE_CHECKING, Any -from docutils.utils import relative_path - from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.osutil import copyfile, ensuredir +from sphinx.util.osutil import _relative_path, copyfile, ensuredir if TYPE_CHECKING: from collections.abc import Callable @@ -125,7 +123,8 @@ def copy_asset( :param onerror: The error handler. :param bool force: Overwrite the destination file even if it exists. """ - if not os.path.exists(source): + source = Path(source) + if not source.exists(): return if renderer is None: @@ -134,14 +133,14 @@ def copy_asset( renderer = SphinxRenderer() ensuredir(destination) - if os.path.isfile(source): + if source.is_file(): copy_asset_file( source, destination, context=context, renderer=renderer, force=force ) return for root, dirs, files in os.walk(source, followlinks=True): - reldir = relative_path(source, root) + reldir = _relative_path(Path(root), source).as_posix() for dir in dirs.copy(): if excluded(posixpath.join(reldir, dir)): dirs.remove(dir) From 5b1dc828ca988dea07c0e47c725d95d59acb17ac Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:48:24 +0000 Subject: [PATCH 30/39] Import abspath from os.path --- sphinx/util/logging.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/util/logging.py b/sphinx/util/logging.py index 758a48f8b0e..20293c8fabe 100644 --- a/sphinx/util/logging.py +++ b/sphinx/util/logging.py @@ -6,6 +6,7 @@ import logging.handlers from collections import defaultdict from contextlib import contextmanager, nullcontext +from os.path import abspath from typing import IO, TYPE_CHECKING, Any from docutils import nodes @@ -13,7 +14,6 @@ from sphinx.errors import SphinxWarning from sphinx.util.console import colorize -from sphinx.util.osutil import abspath if TYPE_CHECKING: from collections.abc import Iterator, Sequence, Set From e8db4379d4613e14e057b12fb5cb50dcbe3518d5 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:49:24 +0000 Subject: [PATCH 31/39] Use pathlib in ``sphinx.transforms.i18n`` --- sphinx/transforms/i18n.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/sphinx/transforms/i18n.py b/sphinx/transforms/i18n.py index 4c464d5e260..31542e81e5f 100644 --- a/sphinx/transforms/i18n.py +++ b/sphinx/transforms/i18n.py @@ -3,7 +3,6 @@ from __future__ import annotations import contextlib -import os.path from re import DOTALL, match from textwrap import indent from typing import TYPE_CHECKING, Any, TypeVar @@ -394,10 +393,8 @@ def apply(self, **kwargs: Any) -> None: textdomain = docname_to_domain(self.env.docname, self.config.gettext_compact) # fetch translations - dirs = [ - os.path.join(self.env.srcdir, directory) - for directory in self.config.locale_dirs - ] + srcdir = self.env.srcdir + dirs = [srcdir / directory for directory in self.config.locale_dirs] catalog, has_catalog = init_locale(dirs, self.config.language, textdomain) if not has_catalog: return From bb6ecbc251a5aa6d5b5dac9f551dd0bc2f787ea3 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:01:31 +0000 Subject: [PATCH 32/39] Use ``_StrPath`` in ``sphinx.writers.latex`` --- sphinx/writers/latex.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index ec49bc721c7..2dffd7e3f15 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -6,10 +6,10 @@ from __future__ import annotations -import os.path import re from collections import defaultdict from collections.abc import Iterable +from pathlib import Path from typing import TYPE_CHECKING, Any, ClassVar, cast from docutils import nodes, writers @@ -583,18 +583,19 @@ def generate( def render(self, template_name: str, variables: dict[str, Any]) -> str: renderer = LaTeXRenderer(latex_engine=self.config.latex_engine) for template_dir in self.config.templates_path: - template = os.path.join(self.builder.confdir, template_dir, template_name) - if os.path.exists(template): - return renderer.render(template, variables) - elif template.endswith('.jinja'): - legacy_template = template.removesuffix('.jinja') + '_t' - if os.path.exists(legacy_template): + template = self.builder.confdir / template_dir / template_name + if template.exists(): + return renderer.render(str(template), variables) + elif template.suffix == '.jinja': + legacy_template_name = template.name.removesuffix('.jinja') + '_t' + legacy_template = template.with_name(legacy_template_name) + if legacy_template.exists(): logger.warning( __('template %s not found; loading from legacy %s instead'), template_name, legacy_template, ) - return renderer.render(legacy_template, variables) + return renderer.render(str(legacy_template), variables) return renderer.render(template_name, variables) @@ -1648,7 +1649,9 @@ def visit_image(self, node: Element) -> None: options = '' if include_graphics_options: options = '[%s]' % ','.join(include_graphics_options) - base, ext = os.path.splitext(uri) + img_path = Path(uri) + base = img_path.with_suffix('') + ext = img_path.suffix if self.in_title and base: # Lowercase tokens forcely because some fncychap themes capitalize @@ -1657,8 +1660,8 @@ def visit_image(self, node: Element) -> None: else: cmd = rf'\sphinxincludegraphics{options}{{{{{base}}}{ext}}}' # escape filepath for includegraphics, https://tex.stackexchange.com/a/202714/41112 - if '#' in base: - cmd = r'{\catcode`\#=12' + cmd + '}' + if '#' in str(base): + cmd = rf'{{\catcode`\#=12{cmd}}}' self.body.append(cmd) self.body.extend(post) From c4495d0ce06a1c4b894317ea0277decd38127246 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 7 Nov 2024 00:31:28 +0000 Subject: [PATCH 33/39] Use ``_StrPath`` in ``sphinx.pycode`` --- sphinx/ext/viewcode.py | 3 ++- sphinx/pycode/__init__.py | 38 +++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/sphinx/ext/viewcode.py b/sphinx/ext/viewcode.py index ebfe4322f84..b64d67bdad3 100644 --- a/sphinx/ext/viewcode.py +++ b/sphinx/ext/viewcode.py @@ -29,6 +29,7 @@ from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.environment import BuildEnvironment + from sphinx.util._pathlib import _StrPath from sphinx.util.typing import ExtensionMetadata logger = logging.getLogger(__name__) @@ -207,7 +208,7 @@ def remove_viewcode_anchors(self) -> None: node.parent.remove(node) -def get_module_filename(app: Sphinx, modname: str) -> str | None: +def get_module_filename(app: Sphinx, modname: str) -> _StrPath | None: """Get module filename for *modname*.""" source_info = app.emit_firstresult('viewcode-find-source', modname) if source_info: diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index 81f0519a5a1..0242cf9c8be 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -2,8 +2,6 @@ from __future__ import annotations -import os -import os.path import tokenize from importlib import import_module from typing import TYPE_CHECKING, Any, Literal @@ -13,6 +11,7 @@ from sphinx.util._pathlib import _StrPath if TYPE_CHECKING: + import os from inspect import Signature @@ -28,7 +27,7 @@ class ModuleAnalyzer: cache: dict[tuple[Literal['file', 'module'], str | _StrPath], Any] = {} @staticmethod - def get_module_source(modname: str) -> tuple[str | None, str | None]: + def get_module_source(modname: str) -> tuple[_StrPath | None, str | None]: """Try to find the source code for a module. Returns ('filename', 'source'). One of it can be None if @@ -39,14 +38,15 @@ def get_module_source(modname: str) -> tuple[str | None, str | None]: except Exception as err: raise PycodeError('error importing %r' % modname, err) from err loader = getattr(mod, '__loader__', None) - filename = getattr(mod, '__file__', None) + filename: str | None = getattr(mod, '__file__', None) if loader and getattr(loader, 'get_source', None): # prefer Native loader, as it respects #coding directive try: source = loader.get_source(modname) if source: + mod_path = None if filename is None else _StrPath(filename) # no exception and not None - it must be module source - return filename, source + return mod_path, source except ImportError: pass # Try other "source-mining" methods if filename is None and loader and getattr(loader, 'get_filename', None): @@ -60,24 +60,28 @@ def get_module_source(modname: str) -> tuple[str | None, str | None]: if filename is None: # all methods for getting filename failed, so raise... raise PycodeError('no source found for module %r' % modname) - filename = os.path.normpath(os.path.abspath(filename)) - if filename.lower().endswith(('.pyo', '.pyc')): - filename = filename[:-1] - if not os.path.isfile(filename) and os.path.isfile(filename + 'w'): - filename += 'w' - elif not filename.lower().endswith(('.py', '.pyw')): - raise PycodeError('source is not a .py file: %r' % filename) - - if not os.path.isfile(filename): - raise PycodeError('source file is not present: %r' % filename) - return filename, None + mod_path = _StrPath(filename).resolve() + if mod_path.suffix in {'.pyo', '.pyc'}: + mod_path_pyw = mod_path.with_suffix('.pyw') + if not mod_path.is_file() and mod_path_pyw.is_file(): + mod_path = mod_path_pyw + else: + mod_path = mod_path.with_suffix('.py') + elif mod_path.suffix not in {'.py', '.pyw'}: + msg = f'source is not a .py file: {mod_path!r}' + raise PycodeError(msg) + + if not mod_path.is_file(): + msg = f'source file is not present: {mod_path!r}' + raise PycodeError(msg) + return mod_path, None @classmethod def for_string( cls: type[ModuleAnalyzer], string: str, modname: str, - srcname: str = '', + srcname: str | os.PathLike[str] = '', ) -> ModuleAnalyzer: return cls(string, modname, srcname) From c6b9dc16b4be3aaa868aed974543eda8ac98f1bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2024 16:13:40 +0000 Subject: [PATCH 34/39] Bump pyright to 1.1.388 (#13111) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4eb5269ed2e..ac079b0766b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -91,7 +91,7 @@ lint = [ "types-Pygments==2.18.0.20240506", "types-requests==2.32.0.20241016", # align with requests "types-urllib3==1.26.25.14", - "pyright==1.1.387", + "pyright==1.1.388", "pytest>=6.0", ] test = [ From 8117bcaae8ccd75754bb6c1fabf060a73b32d000 Mon Sep 17 00:00:00 2001 From: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Date: Thu, 7 Nov 2024 17:25:40 +0100 Subject: [PATCH 35/39] Format autosummary options in docs (#13106) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- doc/usage/extensions/autosummary.rst | 60 +++++++++++++++------------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/doc/usage/extensions/autosummary.rst b/doc/usage/extensions/autosummary.rst index 0a25d8dbd21..0b2b0c69cf8 100644 --- a/doc/usage/extensions/autosummary.rst +++ b/doc/usage/extensions/autosummary.rst @@ -61,10 +61,12 @@ The :mod:`sphinx.ext.autosummary` extension does this in two parts: :event:`autodoc-process-docstring` and :event:`autodoc-process-signature` hooks as :mod:`~sphinx.ext.autodoc`. - **Options** + .. rubric:: Options - * If you want the :rst:dir:`autosummary` table to also serve as a - :rst:dir:`toctree` entry, use the ``toctree`` option, for example:: + .. rst:directive:option:: toctree: optional directory name + + If you want the :rst:dir:`autosummary` table to also serve as a + :rst:dir:`toctree` entry, use the ``toctree`` option, for example:: .. autosummary:: :toctree: DIRNAME @@ -72,52 +74,54 @@ The :mod:`sphinx.ext.autosummary` extension does this in two parts: sphinx.environment.BuildEnvironment sphinx.util.relative_uri - The ``toctree`` option also signals to the :program:`sphinx-autogen` script - that stub pages should be generated for the entries listed in this - directive. The option accepts a directory name as an argument; - :program:`sphinx-autogen` will by default place its output in this - directory. If no argument is given, output is placed in the same directory - as the file that contains the directive. + The ``toctree`` option also signals to the :program:`sphinx-autogen` script + that stub pages should be generated for the entries listed in this + directive. The option accepts a directory name as an argument; + :program:`sphinx-autogen` will by default place its output in this + directory. If no argument is given, output is placed in the same directory + as the file that contains the directive. - You can also use ``caption`` option to give a caption to the toctree. + .. versionadded:: 0.6 - .. versionadded:: 3.1 + .. rst:directive:option:: caption: caption of ToC - caption option added. + Add a caption to the toctree. - * If you don't want the :rst:dir:`autosummary` to show function signatures in - the listing, include the ``nosignatures`` option:: + .. versionadded:: 3.1 - .. autosummary:: - :nosignatures: + .. rst:directive:option:: nosignatures - sphinx.environment.BuildEnvironment - sphinx.util.relative_uri + Do not show function signatures in the summary. - * You can specify a custom template with the ``template`` option. - For example, :: + .. versionadded:: 0.6 + + .. rst:directive:option:: template: filename + + Specify a custom template for rendering the summary. + For example, :: .. autosummary:: :template: mytemplate.rst sphinx.environment.BuildEnvironment - would use the template :file:`mytemplate.rst` in your - :confval:`templates_path` to generate the pages for all entries - listed. See `Customizing templates`_ below. + would use the template :file:`mytemplate.rst` in your + :confval:`templates_path` to generate the pages for all entries + listed. See `Customizing templates`_ below. + + .. versionadded:: 1.0 - .. versionadded:: 1.0 + .. rst:directive:option:: recursive - * You can specify the ``recursive`` option to generate documents for - modules and sub-packages recursively. It defaults to disabled. - For example, :: + Generate documents for modules and sub-packages recursively. + For example, :: .. autosummary:: :recursive: sphinx.environment.BuildEnvironment - .. versionadded:: 3.1 + .. versionadded:: 3.1 :program:`sphinx-autogen` -- generate autodoc stub pages From c427edb0bddba4366e71cd9dca7e70faad18346a Mon Sep 17 00:00:00 2001 From: Dimitri Papadopoulos Orfanos <3234522+DimitriPapadopoulos@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:11:42 +0100 Subject: [PATCH 36/39] Emend spelling errors (#13113) Co-authored-by: Adam Turner <9087854+aa-turner@users.noreply.github.com> --- sphinx/texinputs/sphinxlatexadmonitions.sty | 2 +- sphinx/util/_pathlib.py | 2 +- tests/test_extensions/test_ext_intersphinx_cache.py | 2 +- tests/test_intl/test_intl.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/sphinx/texinputs/sphinxlatexadmonitions.sty b/sphinx/texinputs/sphinxlatexadmonitions.sty index 76fef5a8c4f..0519903591b 100644 --- a/sphinx/texinputs/sphinxlatexadmonitions.sty +++ b/sphinx/texinputs/sphinxlatexadmonitions.sty @@ -32,7 +32,7 @@ % sphinxlatexshadowbox.sty, and handles both "with icon" and "without % icon" situations). % -% The sphinxlightbox environment is kept for backward compatiblity, for user +% The sphinxlightbox environment is kept for backward compatibility, for user % custom code which used it via custom definitions done in preamble or via % raw latex directive. % MEMO: here is for example how sphinxnote was formerly defined: diff --git a/sphinx/util/_pathlib.py b/sphinx/util/_pathlib.py index b44fec30285..31b47ce5a67 100644 --- a/sphinx/util/_pathlib.py +++ b/sphinx/util/_pathlib.py @@ -3,7 +3,7 @@ Instances of _StrPath should not be constructed except in Sphinx itself. Consumers of Sphinx APIs should prefer using ``pathlib.Path`` objects where possible. _StrPath objects can be treated as equivalent to ``Path``, -save that ``_StrPath.replace`` is overriden with ``str.replace``. +save that ``_StrPath.replace`` is overridden with ``str.replace``. To continue treating path-like objects as strings, use ``os.fspath``, or explicit string coercion. diff --git a/tests/test_extensions/test_ext_intersphinx_cache.py b/tests/test_extensions/test_ext_intersphinx_cache.py index 72d942846bc..047589b7fd5 100644 --- a/tests/test_extensions/test_ext_intersphinx_cache.py +++ b/tests/test_extensions/test_ext_intersphinx_cache.py @@ -295,7 +295,7 @@ def test_load_mappings_cache_revert_update(tmp_path): app2.build() app2.cleanup() - # switch back to old url (re-use 'old_item') + # switch back to old url (reuse 'old_item') confoverrides3 = BASE_CONFIG | {'intersphinx_mapping': old_project.record} app3 = SphinxTestApp('dummy', srcdir=tmp_path, confoverrides=confoverrides3) app3.build() diff --git a/tests/test_intl/test_intl.py b/tests/test_intl/test_intl.py index 21e7b301919..19808f6d538 100644 --- a/tests/test_intl/test_intl.py +++ b/tests/test_intl/test_intl.py @@ -1569,7 +1569,7 @@ def test_additional_targets_should_be_translated(app): # [literalblock.txt] result = (app.outdir / 'literalblock.html').read_text(encoding='utf8') - # basic literal bloc should be translated + # basic literal block should be translated expected_expr = ( 'THIS IS\n' 'LITERAL BLOCK' From eb337fec0acd8ce978caa0663f60f3a6afd76322 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 8 Nov 2024 12:44:09 +0000 Subject: [PATCH 37/39] Remove duplicate entries in AUTHORS --- AUTHORS.rst | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 3c5de42f249..8068ae4ae22 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -8,7 +8,7 @@ Maintainers * Chris Sewell <@chrisjsewell> * François Freitag <@francoisfreitag> * Jakob Lykke Andersen <@jakobandersen> -* Jean-François Burnol <@jfbu> +* Jean-François B. <@jfbu> * Stephen Finucane <@stephenfin> * Takayuki Shimizukawa <@shimizukawa> * Takeshi Komiya <@tk0miya> @@ -35,7 +35,6 @@ Contributors * Christopher Perkins -- autosummary integration * Dan MacKinlay -- metadata fixes * Daniel Bültmann -- todo extension -* Daniel Neuhäuser -- JavaScript domain, Python 3 support (GSOC) * Daniel Pizetta -- inheritance diagram improvements * Dave Kuhlman -- original LaTeX writer * Doug Hellmann -- graphviz improvements @@ -73,14 +72,12 @@ Contributors * Michael Wilson -- Intersphinx HTTP basic auth support * Nathan Damon -- bugfix in validation of static paths in html builders * Pauli Virtanen -- autodoc improvements, autosummary extension -* A. Rafey Khan -- improved intersphinx typing -* Rob Ruana -- napoleon extension -* Robert Lehmann -- gettext builder (GSOC project) +* \A. Rafey Khan -- improved intersphinx typing * Roland Meister -- epub builder * Sebastian Wiesner -- image handling, distutils support * Stefan Seefeld -- toctree improvements * Stefan van der Walt -- autosummary extension -* T. Powers -- HTML output improvements +* \T. Powers -- HTML output improvements * Taku Shimizu -- epub3 builder * Thomas Lamb -- linkcheck builder * Thomas Waldmann -- apidoc module fixes From 2dfb0b907d54b187b5ddc8d4b6490f106f4d51a9 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+AA-Turner@users.noreply.github.com> Date: Wed, 13 Nov 2024 21:41:26 +0000 Subject: [PATCH 38/39] Account for removal of ``docutils.utils.roman`` (#13131) --- pyproject.toml | 1 + sphinx/writers/latex.py | 12 ++++-------- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index ac079b0766b..e3abc9d218d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -70,6 +70,7 @@ dependencies = [ "alabaster>=0.7.14", "imagesize>=1.3", "requests>=2.30.0", + "roman-numerals-py>=1.0.0", "packaging>=23.0", "colorama>=0.4.6; sys_platform == 'win32'", ] diff --git a/sphinx/writers/latex.py b/sphinx/writers/latex.py index 2dffd7e3f15..454543b043c 100644 --- a/sphinx/writers/latex.py +++ b/sphinx/writers/latex.py @@ -13,6 +13,7 @@ from typing import TYPE_CHECKING, Any, ClassVar, cast from docutils import nodes, writers +from roman_numerals import RomanNumeral from sphinx import addnodes, highlighting from sphinx.errors import SphinxError @@ -24,12 +25,6 @@ from sphinx.util.template import LaTeXRenderer from sphinx.util.texescape import tex_replace_map -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, import-not-found] - if TYPE_CHECKING: from docutils.nodes import Element, Node, Text @@ -1421,8 +1416,9 @@ def get_nested_level(node: Element) -> int: else: return get_nested_level(node.parent) - enum = 'enum%s' % toRoman(get_nested_level(node)).lower() - enumnext = 'enum%s' % toRoman(get_nested_level(node) + 1).lower() + nested_level = get_nested_level(node) + enum = f'enum{RomanNumeral(nested_level).to_lowercase()}' + enumnext = f'enum{RomanNumeral(nested_level + 1).to_lowercase()}' style = ENUMERATE_LIST_STYLE.get(get_enumtype(node)) prefix = node.get('prefix', '') suffix = node.get('suffix', '.') From d6da26a7f5e2c50acd2e5728125aff9667a1a33b Mon Sep 17 00:00:00 2001 From: Dmitry Shachnev Date: Thu, 14 Nov 2024 00:53:01 +0300 Subject: [PATCH 39/39] Properly strip time zones which are west of UTC (#13128) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com> --- tests/test_util/test_util_i18n.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_util/test_util_i18n.py b/tests/test_util/test_util_i18n.py index d5ee52fb1f8..95d0909f90a 100644 --- a/tests/test_util/test_util_i18n.py +++ b/tests/test_util/test_util_i18n.py @@ -108,7 +108,7 @@ def test_format_date_timezone(): assert fd_gmt == '2016-08-07 05:11:17' assert fd_gmt == iso_gmt - iso_local = dt.astimezone().isoformat(' ').split('+')[0] + iso_local = dt.astimezone().isoformat(' ')[:19] # strip the timezone fd_local = i18n.format_date(fmt, date=dt, language='en', local_time=True) assert fd_local == iso_local assert fd_local != fd_gmt