From 04ec5d2dc7963f288087c79461a6241a26e8c3d6 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Mon, 17 Oct 2022 15:00:40 +0100 Subject: [PATCH 01/16] Use `pathlib.Path` and `tmp_path` in tests --- sphinx/application.py | 15 +- sphinx/builders/__init__.py | 2 +- sphinx/builders/gettext.py | 8 +- sphinx/builders/html/__init__.py | 6 +- sphinx/cmd/build.py | 4 +- sphinx/config.py | 4 +- sphinx/directives/code.py | 2 +- sphinx/environment/__init__.py | 10 +- sphinx/project.py | 11 +- sphinx/testing/comparer.py | 97 ------------ sphinx/testing/fixtures.py | 22 +-- sphinx/testing/util.py | 28 ++-- sphinx/util/fileutil.py | 2 +- sphinx/util/i18n.py | 4 +- sphinx/util/osutil.py | 43 +++--- tests/conftest.py | 25 ++- tests/test_build.py | 12 +- tests/test_build_gettext.py | 16 +- tests/test_build_html.py | 8 +- tests/test_build_latex.py | 4 +- tests/test_build_texinfo.py | 2 +- tests/test_catalogs.py | 20 +-- tests/test_config.py | 34 ++--- tests/test_directive_code.py | 4 +- tests/test_domain_c.py | 6 +- tests/test_domain_cpp.py | 6 +- tests/test_environment.py | 22 +-- tests/test_ext_apidoc.py | 209 +++++++++++++------------- tests/test_ext_autosummary.py | 13 +- tests/test_ext_inheritance_diagram.py | 2 +- tests/test_ext_intersphinx.py | 74 ++++----- tests/test_intl.py | 41 +++-- tests/test_project.py | 12 +- tests/test_pycode.py | 2 +- tests/test_quickstart.py | 72 ++++----- tests/test_theming.py | 6 +- tests/test_util.py | 4 - tests/test_util_fileutil.py | 50 +++--- tests/test_util_i18n.py | 58 +++---- tests/test_versioning.py | 3 +- 40 files changed, 420 insertions(+), 543 deletions(-) delete mode 100644 sphinx/testing/comparer.py diff --git a/sphinx/application.py b/sphinx/application.py index a3711ce59ef..03c21963e9c 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -11,8 +11,9 @@ from collections import deque from io import StringIO from os import path +from os.path import abspath from typing import IO, TYPE_CHECKING, Any, Callable - +from pathlib import Path from docutils import nodes from docutils.nodes import Element, TextElement from docutils.parsers import Parser @@ -41,7 +42,7 @@ from sphinx.util.display import progress_message from sphinx.util.i18n import CatalogRepository from sphinx.util.logging import prefixed_warnings -from sphinx.util.osutil import abspath, ensuredir, relpath +from sphinx.util.osutil import ensuredir, relpath, StrPath from sphinx.util.tags import Tags from sphinx.util.typing import RoleFunction, TitleGetter @@ -132,7 +133,7 @@ class Sphinx: warningiserror: bool _warncount: int - def __init__(self, srcdir: str, confdir: str | None, outdir: str, doctreedir: str, + def __init__(self, srcdir: StrPath, confdir: StrPath | None, outdir: StrPath, doctreedir: StrPath, buildername: str, confoverrides: dict | None = None, status: IO | None = sys.stdout, warning: IO | None = sys.stderr, freshenv: bool = False, warningiserror: bool = False, @@ -145,9 +146,9 @@ def __init__(self, srcdir: str, confdir: str | None, outdir: str, doctreedir: st self.registry = SphinxComponentRegistry() # validate provided directories - self.srcdir = abspath(srcdir) - self.outdir = abspath(outdir) - self.doctreedir = abspath(doctreedir) + self.srcdir = Path(abspath(srcdir)) + self.outdir = Path(abspath(outdir)) + self.doctreedir = Path(abspath(doctreedir)) if not path.isdir(self.srcdir): raise ApplicationError(__('Cannot find source directory (%s)') % @@ -203,7 +204,7 @@ def __init__(self, srcdir: str, confdir: str | None, outdir: str, doctreedir: st self.confdir = self.srcdir self.config = Config({}, confoverrides or {}) else: - self.confdir = abspath(confdir) + self.confdir = Path(abspath(confdir)) self.config = Config.read(self.confdir, confoverrides or {}, self.tags) # initialize some limited config variables before initialize i18n and loading diff --git a/sphinx/builders/__init__.py b/sphinx/builders/__init__.py index d393a131d95..8f639b23f2d 100644 --- a/sphinx/builders/__init__.py +++ b/sphinx/builders/__init__.py @@ -262,7 +262,7 @@ def build_specific(self, filenames: list[str]) -> None: filename) continue - if not filename.startswith(self.srcdir): + if not filename.startswith(str(self.srcdir)): logger.warning(__('file %r given on command line is not under the ' 'source directory, ignoring'), filename) continue diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index b2f66eabef3..6d825aa8572 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -23,7 +23,7 @@ from sphinx.util.display import status_iterator from sphinx.util.i18n import CatalogInfo, docname_to_domain from sphinx.util.nodes import extract_messages, traverse_translatable_index -from sphinx.util.osutil import canon_path, ensuredir, relpath +from sphinx.util.osutil import canon_path, ensuredir, relpath, StrPath from sphinx.util.tags import Tags from sphinx.util.template import SphinxRenderer @@ -84,11 +84,11 @@ def __init__(self, source: str, line: int) -> None: class GettextRenderer(SphinxRenderer): def __init__( - self, template_path: str | None = None, outdir: str | None = None, + self, template_path: StrPath | None = None, outdir: StrPath | None = None, ) -> None: - self.outdir = outdir + self.outdir = str(outdir) if outdir is not None else None if template_path is None: - template_path = path.join(package_dir, 'templates', 'gettext') + template_path = str(path.join(package_dir, 'templates', 'gettext')) super().__init__(template_path) def escape(s: str) -> str: diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index e70324fb5ce..81ec866e1d7 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -786,7 +786,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(str(self.srcdir), f) # copy downloadable files if self.env.dlfiles: @@ -1305,7 +1305,7 @@ def validate_html_extra_path(app: Sphinx, config: Config) -> None: logger.warning(__('html_extra_path entry %r does not exist'), entry) config.html_extra_path.remove(entry) elif (path.splitdrive(app.outdir)[0] == path.splitdrive(extra_path)[0] and - path.commonpath([app.outdir, extra_path]) == app.outdir): + path.commonpath((app.outdir, extra_path)) == path.normpath(app.outdir)): logger.warning(__('html_extra_path entry %r is placed inside outdir'), entry) config.html_extra_path.remove(entry) @@ -1318,7 +1318,7 @@ def validate_html_static_path(app: Sphinx, config: Config) -> None: logger.warning(__('html_static_path entry %r does not exist'), entry) config.html_static_path.remove(entry) elif (path.splitdrive(app.outdir)[0] == path.splitdrive(static_path)[0] and - path.commonpath([app.outdir, static_path]) == app.outdir): + path.commonpath((app.outdir, static_path)) == path.normpath(app.outdir)): logger.warning(__('html_static_path entry %r is placed inside outdir'), entry) config.html_static_path.remove(entry) diff --git a/sphinx/cmd/build.py b/sphinx/cmd/build.py index 4f427e0628f..e43cfa67de4 100644 --- a/sphinx/cmd/build.py +++ b/sphinx/cmd/build.py @@ -24,7 +24,7 @@ from sphinx.util.console import color_terminal, nocolor, red, terminal_safe # type: ignore from sphinx.util.docutils import docutils_namespace, patch_docutils from sphinx.util.exceptions import format_exception_cut_frames, save_traceback -from sphinx.util.osutil import abspath, ensuredir +from sphinx.util.osutil import ensuredir def handle_exception( @@ -234,7 +234,7 @@ def _parse_arguments(argv: list[str] = sys.argv[1:]) -> argparse.Namespace: if warning and args.warnfile: try: - warnfile = abspath(args.warnfile) + warnfile = path.abspath(args.warnfile) ensuredir(path.dirname(warnfile)) warnfp = open(args.warnfile, 'w', encoding="utf-8") except Exception as exc: diff --git a/sphinx/config.py b/sphinx/config.py index 41791ebd176..1fb63c97568 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -11,7 +11,7 @@ from sphinx.errors import ConfigError, ExtensionError from sphinx.locale import _, __ from sphinx.util import logging -from sphinx.util.osutil import fs_encoding +from sphinx.util.osutil import fs_encoding, StrPath from sphinx.util.tags import Tags from sphinx.util.typing import NoneType @@ -169,7 +169,7 @@ def __init__(self, config: dict[str, Any] = {}, overrides: dict[str, Any] = {}) @classmethod def read( - cls, confdir: str, overrides: dict | None = None, tags: Tags | None = None, + cls, confdir: StrPath, overrides: dict | None = None, tags: Tags | None = None, ) -> Config: """Create a Config object from configuration file.""" filename = path.join(confdir, CONFIG_FILENAME) diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index c2e97a5aea9..fa5c9dde1e5 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -244,7 +244,7 @@ def show_diff(self, location: tuple[str, int] | None = None) -> list[str]: new_lines = self.read_file(self.filename) old_filename = self.options['diff'] old_lines = self.read_file(old_filename) - diff = unified_diff(old_lines, new_lines, old_filename, self.filename) + diff = unified_diff(old_lines, new_lines, str(old_filename), str(self.filename)) return list(diff) def pyobject_filter( diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 7f0baa07420..a08b35e6788 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -10,7 +10,7 @@ from datetime import datetime, timezone from os import path from typing import TYPE_CHECKING, Any, Callable - +from pathlib import Path from docutils import nodes from docutils.nodes import Node @@ -27,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 canon_path, os_path +from sphinx.util.osutil import canon_path, os_path, StrPath if TYPE_CHECKING: from collections.abc import Generator, Iterator @@ -147,8 +147,8 @@ class BuildEnvironment: def __init__(self, app: Sphinx): self.app: Sphinx = app - self.doctreedir: str = app.doctreedir - self.srcdir: str = app.srcdir + self.doctreedir: Path = app.doctreedir + self.srcdir: Path = app.srcdir self.config: Config = None # type: ignore[assignment] self.config_status: int = CONFIG_UNSET self.config_status_extra: str = '' @@ -387,7 +387,7 @@ def merge_info_from(self, docnames: list[str], other: BuildEnvironment, domain.merge_domaindata(docnames, other.domaindata[domainname]) self.events.emit('env-merge-info', self, docnames, other) - def path2doc(self, filename: str) -> str | None: + def path2doc(self, filename: StrPath) -> str | None: """Return the docname for the filename if the file is document. *filename* should be absolute or relative to the source directory. diff --git a/sphinx/project.py b/sphinx/project.py index 52043d97a93..c713fb7fd46 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -9,7 +9,7 @@ from sphinx.locale import __ from sphinx.util import logging from sphinx.util.matching import get_matching_files -from sphinx.util.osutil import SEP, path_stabilize, relpath +from sphinx.util.osutil import SEP, path_stabilize, relpath, StrPath if TYPE_CHECKING: from collections.abc import Iterable @@ -21,9 +21,9 @@ class Project: """A project is the source code set of the Sphinx document(s).""" - def __init__(self, srcdir: str, source_suffix: dict[str, str]) -> None: + def __init__(self, srcdir: StrPath, source_suffix: dict[str, str]) -> None: #: Source directory. - self.srcdir = srcdir + self.srcdir = str(srcdir) #: source_suffix. Same as :confval:`source_suffix`. self.source_suffix = source_suffix @@ -61,12 +61,13 @@ def discover(self, exclude_paths: Iterable[str] = (), return self.docnames - def path2doc(self, filename: str) -> str | None: + def path2doc(self, filename: StrPath) -> str | None: """Return the docname for the filename if the file is a document. *filename* should be absolute or relative to the source directory. """ - if filename.startswith(self.srcdir): + filename = str(filename) + if filename.startswith(os.path.normpath(self.srcdir)): filename = relpath(filename, self.srcdir) for suffix in self.source_suffix: if filename.endswith(suffix): diff --git a/sphinx/testing/comparer.py b/sphinx/testing/comparer.py deleted file mode 100644 index 4ea57a5751c..00000000000 --- a/sphinx/testing/comparer.py +++ /dev/null @@ -1,97 +0,0 @@ -"""Sphinx test comparer for pytest""" -from __future__ import annotations - -import difflib -import pathlib -from typing import Any - - -class PathComparer: - """ - OS-independent path comparison. - - Windows path sep and posix path sep: - - >>> '\\to\\index' == PathComparer('/to/index') - True - >>> '\\to\\index' == PathComparer('/to/index2') - False - - Windows path with drive letters - - >>> 'C:\\to\\index' == PathComparer('/to/index') - True - >>> 'C:\\to\\index' == PathComparer('C:/to/index') - True - >>> 'C:\\to\\index' == PathComparer('D:/to/index') - False - """ - def __init__(self, path: str | pathlib.Path): - """ - :param str path: path string, it will be cast as pathlib.Path. - """ - self.path = pathlib.Path(path) - - def __str__(self) -> str: - return self.path.as_posix() - - def __repr__(self) -> str: - return f"<{self.__class__.__name__}: '{self}'>" - - def __eq__(self, other: str | pathlib.Path) -> bool: # type: ignore - return not bool(self.ldiff(other)) - - def diff(self, other: str | pathlib.Path) -> list[str]: - """compare self and other. - - When different is not exist, return empty list. - - >>> PathComparer('/to/index').diff('C:\\to\\index') - [] - - When different is exist, return unified diff style list as: - - >>> PathComparer('/to/index').diff('C:\\to\\index2') - [ - '- C:/to/index' - '+ C:/to/index2' - '? +' - ] - """ - return self.ldiff(other) - - def ldiff(self, other: str | pathlib.Path) -> list[str]: - return self._diff( - self.path, - pathlib.Path(other), - ) - - def rdiff(self, other: str | pathlib.Path) -> list[str]: - return self._diff( - pathlib.Path(other), - self.path, - ) - - def _diff(self, lhs: pathlib.Path, rhs: pathlib.Path) -> list[str]: - if lhs == rhs: - return [] - - if lhs.drive or rhs.drive: - # If either has a drive letter compare by absolute path - s_path, o_path = lhs.absolute().as_posix(), rhs.absolute().as_posix() - else: - s_path, o_path = lhs.as_posix(), rhs.as_posix() - - if s_path == o_path: - return [] - - return [line.strip() for line in difflib.Differ().compare([s_path], [o_path])] - - -def pytest_assertrepr_compare(op: str, left: Any, right: Any) -> list[str]: - if isinstance(left, PathComparer) and op == "==": - return ['Comparing path:'] + left.ldiff(right) - elif isinstance(right, PathComparer) and op == "==": - return ['Comparing path:'] + right.rdiff(left) - else: - return [] diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index 157fe93414a..1c34abceae7 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -2,10 +2,12 @@ from __future__ import annotations +import shutil import subprocess import sys from collections import namedtuple from io import StringIO +from pathlib import Path from typing import TYPE_CHECKING, Any, Callable import pytest @@ -96,7 +98,7 @@ def app_params(request: Any, test_params: dict, shared_result: SharedResult, # special support for sphinx/tests if rootdir and not srcdir.exists(): testroot_path = rootdir / ('test-' + testroot) - testroot_path.copytree(srcdir) + shutil.copytree(testroot_path, srcdir) return namedtuple('app_params', 'args,kwargs')(args, kwargs) # type: ignore @@ -218,21 +220,9 @@ def if_graphviz_found(app: SphinxTestApp) -> None: # NoQA: PT004 @pytest.fixture(scope='session') -def sphinx_test_tempdir(tmpdir_factory: Any) -> util.path: - """ - Temporary directory wrapped with `path` class. - """ - tmpdir = tmpdir_factory.getbasetemp() - return util.path(tmpdir).abspath() - - -@pytest.fixture() -def tempdir(tmpdir: str) -> util.path: - """ - Temporary directory wrapped with `path` class. - This fixture is for back-compatibility with old test implementation. - """ - return util.path(tmpdir) +def sphinx_test_tempdir(tmp_path_factory: Any) -> Path: + """Temporary directory.""" + return tmp_path_factory.getbasetemp() @pytest.fixture() diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 48ae126c210..23631256d69 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -6,6 +6,8 @@ import re import sys import warnings +from io import StringIO +from pathlib import Path from typing import IO, TYPE_CHECKING, Any from xml.etree import ElementTree @@ -15,8 +17,6 @@ from sphinx import application, locale from sphinx.pycode import ModuleAnalyzer -from sphinx.testing.path import path -from sphinx.util.osutil import relpath if TYPE_CHECKING: from collections.abc import Generator @@ -102,8 +102,8 @@ class SphinxTestApp(application.Sphinx): def __init__( self, buildername: str = 'html', - srcdir: path | None = None, - builddir: path | None = None, + srcdir: Path | None = None, + builddir: Path | None = None, freshenv: bool = False, confoverrides: dict | None = None, status: IO | None = None, @@ -123,9 +123,9 @@ def __init__( confdir = srcdir outdir = builddir.joinpath(buildername) - outdir.makedirs(exist_ok=True) + outdir.mkdir(parents=True, exist_ok=True) doctreedir = builddir.joinpath('doctrees') - doctreedir.makedirs(exist_ok=True) + doctreedir.mkdir(parents=True, exist_ok=True) if confoverrides is None: confoverrides = {} warningiserror = False @@ -138,7 +138,7 @@ def __init__( if v.startswith('visit_')} try: - super().__init__(srcdir, confdir, outdir, doctreedir, + super().__init__(str(srcdir), str(confdir), str(outdir), str(doctreedir), buildername, confoverrides, status, warning, freshenv, warningiserror, tags, parallel=parallel) except Exception: @@ -184,7 +184,7 @@ def __getattr__(self, name: str) -> Any: return getattr(self.app, name) def build(self, *args: Any, **kwargs: Any) -> None: - if not self.app.outdir.listdir(): # type: ignore + if not os.listdir(self.app.outdir): # if listdir is empty, do build. self.app.build(*args, **kwargs) # otherwise, we can use built cache @@ -193,12 +193,12 @@ def build(self, *args: Any, **kwargs: Any) -> None: _unicode_literals_re = re.compile(r'u(".*?")|u(\'.*?\')') -def find_files(root: str, suffix: str | None = None) -> Generator[str, None, None]: - for dirpath, _dirs, files in os.walk(root, followlinks=True): - dirpath = path(dirpath) - for f in [f for f in files if not suffix or f.endswith(suffix)]: - fpath = dirpath / f - yield relpath(fpath, root) +def find_files(root: str, suffix: str) -> Iterator[Path]: + for file, _dirs, files in os.walk(root, followlinks=True): + dir_path = Path(file) + for f in files: + if f.endswith(suffix): + yield (dir_path / f).relative_to(root) def strip_escseq(text: str) -> str: diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py index 03928710e88..130aabe1fc6 100644 --- a/sphinx/util/fileutil.py +++ b/sphinx/util/fileutil.py @@ -35,7 +35,7 @@ def copy_asset_file(source: str, destination: str, # Use source filename if destination points a directory destination = os.path.join(destination, os.path.basename(source)) - if source.lower().endswith('_t') and context is not None: + if str(source).lower().endswith('_t') and context is not None: if renderer is None: from sphinx.util.template import SphinxRenderer renderer = SphinxRenderer() diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 04c8ccb35fa..5df56121496 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -15,7 +15,7 @@ from sphinx.errors import SphinxError from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.osutil import SEP, canon_path, relpath +from sphinx.util.osutil import SEP, canon_path, relpath, StrPath if TYPE_CHECKING: from collections.abc import Generator @@ -73,7 +73,7 @@ def write_mo(self, locale: str, use_fuzzy: bool = False) -> None: class CatalogRepository: """A repository for message catalogs.""" - def __init__(self, basedir: str, locale_dirs: list[str], + def __init__(self, basedir: StrPath, locale_dirs: list[str], language: str, encoding: str) -> None: self.basedir = basedir self._locale_dirs = locale_dirs diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index f5f0a2e0c96..eb078635a37 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -11,19 +11,15 @@ import unicodedata from io import StringIO from os import path -from typing import TYPE_CHECKING, Any +from pathlib import Path as Path +from typing import TYPE_CHECKING, Any, Union from sphinx.deprecation import _deprecation_warning if TYPE_CHECKING: from collections.abc import Iterator -try: - # for ALT Linux (#6712) - from sphinx.testing.path import path as Path -except ImportError: - Path = None # type: ignore - +StrPath = Union[str, os.PathLike[str]] # SEP separates path elements in the canonical file names # @@ -71,9 +67,11 @@ def relative_uri(base: str, to: str) -> str: return ('..' + SEP) * (len(b2) - 1) + SEP.join(t2) -def ensuredir(path: str) -> None: +def ensuredir(file: StrPath) -> None: """Ensure that a path exists.""" - os.makedirs(path, exist_ok=True) + file = path.normpath(file) + if not path.exists(file): + os.makedirs(file) def mtimes_of_files(dirnames: list[str], suffix: str) -> Iterator[float]: @@ -119,7 +117,7 @@ def make_filename_from_project(project: str) -> str: return make_filename(project_suffix_re.sub('', project)).lower() -def relpath(path: str, start: str | None = os.curdir) -> str: +def relpath(path: StrPath, start: StrPath | None = os.curdir) -> str: """Return a relative filepath to *path* either from the current directory or from an optional *start* directory. @@ -129,26 +127,23 @@ def relpath(path: str, start: str | None = os.curdir) -> str: try: return os.path.relpath(path, start) except ValueError: - return path + return str(path) safe_relpath = relpath # for compatibility fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() -def abspath(pathdir: str) -> str: - if Path is not None and isinstance(pathdir, Path): - return pathdir.abspath() - else: - pathdir = path.abspath(pathdir) - if isinstance(pathdir, bytes): - try: - pathdir = pathdir.decode(fs_encoding) - except UnicodeDecodeError as exc: - raise UnicodeDecodeError('multibyte filename not supported on ' - 'this filesystem encoding ' - '(%r)' % fs_encoding) from exc - return pathdir +def abspath(pathdir: StrPath) -> str: + pathdir = path.abspath(pathdir) + if isinstance(pathdir, bytes): + try: + pathdir = pathdir.decode(fs_encoding) + except UnicodeDecodeError as exc: + raise UnicodeDecodeError('multibyte filename not supported on ' + 'this filesystem encoding ' + '(%r)' % fs_encoding) from exc + return pathdir class _chdir: diff --git a/tests/conftest.py b/tests/conftest.py index 3934203eab3..204c9fd67e5 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,13 +1,12 @@ import os import shutil +from pathlib import Path import docutils import pytest import sphinx import sphinx.locale -from sphinx.testing import comparer -from sphinx.testing.path import path def _init_console(locale_dir=sphinx.locale._LOCALE_DIR, catalog='sphinx'): @@ -30,31 +29,25 @@ def _init_console(locale_dir=sphinx.locale._LOCALE_DIR, catalog='sphinx'): @pytest.fixture(scope='session') def rootdir(): - return path(__file__).parent.abspath() / 'roots' + return Path(__file__).parent.absolute() / 'roots' def pytest_report_header(config): - header = ("libraries: Sphinx-%s, docutils-%s" % - (sphinx.__display_version__, docutils.__version__)) + header = f"libraries: Sphinx-{sphinx.__display_version__}, docutils-{docutils.__version__}" if hasattr(config, '_tmp_path_factory'): - header += "\nbase tempdir: %s" % config._tmp_path_factory.getbasetemp() - + header += f"\nbase tmp_path: {config._tmp_path_factory.getbasetemp()}" return header -def pytest_assertrepr_compare(op, left, right): - comparer.pytest_assertrepr_compare(op, left, right) - - def _initialize_test_directory(session): if 'SPHINX_TEST_TEMPDIR' in os.environ: - tempdir = os.path.abspath(os.getenv('SPHINX_TEST_TEMPDIR')) - print('Temporary files will be placed in %s.' % tempdir) + tmp_path = os.path.abspath(os.getenv('SPHINX_TEST_TEMPDIR')) + print(f'Temporary files will be placed in {tmp_path}.') - if os.path.exists(tempdir): - shutil.rmtree(tempdir) + if os.path.exists(tmp_path): + shutil.rmtree(tmp_path) - os.makedirs(tempdir) + os.mkdir(tmp_path) def pytest_sessionstart(session): diff --git a/tests/test_build.py b/tests/test_build.py index f3a48a3bdfa..546798b0216 100644 --- a/tests/test_build.py +++ b/tests/test_build.py @@ -1,5 +1,7 @@ """Test all builders.""" +import os +import shutil import sys from unittest import mock @@ -23,7 +25,7 @@ def nonascii_srcdir(request, rootdir, sphinx_test_tempdir): basedir = sphinx_test_tempdir / request.node.originalname srcdir = basedir / test_name if not srcdir.exists(): - (rootdir / 'test-root').copytree(srcdir) + shutil.copytree(rootdir / 'test-root', srcdir) # add a doc with a non-ASCII file name to the source dir (srcdir / (test_name + '.txt')).write_text(""" @@ -54,11 +56,11 @@ def test_build_all(requests_head, make_app, nonascii_srcdir, buildername): app.build() -def test_root_doc_not_found(tempdir, make_app): - (tempdir / 'conf.py').write_text('', encoding='utf8') - assert tempdir.listdir() == ['conf.py'] +def test_root_doc_not_found(tmp_path, make_app): + (tmp_path / 'conf.py').write_text('', encoding='utf8') + assert os.listdir(tmp_path) == ['conf.py'] - app = make_app('dummy', srcdir=tempdir) + app = make_app('dummy', srcdir=tmp_path) with pytest.raises(SphinxError): app.builder.build_all() # no index.rst diff --git a/tests/test_build_gettext.py b/tests/test_build_gettext.py index dee9757b024..bce6bd2dbda 100644 --- a/tests/test_build_gettext.py +++ b/tests/test_build_gettext.py @@ -42,9 +42,9 @@ def test_build_gettext(app): # Do messages end up in the correct location? # top-level documents end up in a message catalog - assert (app.outdir / 'extapi.pot').isfile() + assert (app.outdir / 'extapi.pot').is_file() # directory items are grouped into sections - assert (app.outdir / 'subdir.pot').isfile() + assert (app.outdir / 'subdir.pot').is_file() # regression test for issue #960 catalog = (app.outdir / 'markup.pot').read_text(encoding='utf8') @@ -54,7 +54,7 @@ def test_build_gettext(app): @pytest.mark.sphinx('gettext', srcdir='root-gettext') def test_msgfmt(app): app.builder.build_all() - (app.outdir / 'en' / 'LC_MESSAGES').makedirs() + (app.outdir / 'en' / 'LC_MESSAGES').mkdir(parents=True, exist_ok=True) with chdir(app.outdir): try: args = ['msginit', '--no-translator', '-i', 'markup.pot', '--locale', 'en_US'] @@ -66,7 +66,7 @@ def test_msgfmt(app): print(exc.stderr) raise AssertionError('msginit exited with return code %s' % exc.returncode) - assert (app.outdir / 'en_US.po').isfile(), 'msginit failed' + assert (app.outdir / 'en_US.po').is_file(), 'msginit failed' try: args = ['msgfmt', 'en_US.po', '-o', os.path.join('en', 'LC_MESSAGES', 'test_root.mo')] @@ -79,7 +79,7 @@ def test_msgfmt(app): raise AssertionError('msgfmt exited with return code %s' % exc.returncode) mo = app.outdir / 'en' / 'LC_MESSAGES' / 'test_root.mo' - assert mo.isfile(), 'msgfmt failed' + assert mo.is_file(), 'msgfmt failed' _ = gettext.translation('test_root', app.outdir, languages=['en']).gettext assert _("Testing various markup") == "Testing various markup" @@ -164,7 +164,7 @@ def msgid_getter(msgid): @pytest.mark.sphinx('gettext', testroot='intl', srcdir='gettext') def test_gettext_template(app): app.build() - assert (app.outdir / 'sphinx.pot').isfile() + assert (app.outdir / 'sphinx.pot').is_file() result = (app.outdir / 'sphinx.pot').read_text(encoding='utf8') assert "Welcome" in result @@ -174,7 +174,7 @@ def test_gettext_template(app): @pytest.mark.sphinx('gettext', testroot='gettext-template') def test_gettext_template_msgid_order_in_sphinxpot(app): app.builder.build_all() - assert (app.outdir / 'sphinx.pot').isfile() + assert (app.outdir / 'sphinx.pot').is_file() result = (app.outdir / 'sphinx.pot').read_text(encoding='utf8') assert re.search( @@ -192,7 +192,7 @@ def test_gettext_template_msgid_order_in_sphinxpot(app): def test_build_single_pot(app): app.builder.build_all() - assert (app.outdir / 'documentation.pot').isfile() + assert (app.outdir / 'documentation.pot').is_file() result = (app.outdir / 'documentation.pot').read_text(encoding='utf8') assert re.search( diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 2604f3df920..4e61c73007e 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -116,7 +116,7 @@ def test_html_warnings(app, warning): app.build() html_warnings = strip_escseq(re.sub(re.escape(os.sep) + '{1,2}', '/', warning.getvalue())) html_warnings_exp = HTML_WARNINGS % { - 'root': re.escape(app.srcdir.replace(os.sep, '/'))} + 'root': re.escape(app.srcdir.as_posix())} assert re.match(html_warnings_exp + '$', html_warnings), \ "Warnings don't match:\n" + \ '--- Expected (regex):\n' + html_warnings_exp + \ @@ -753,7 +753,7 @@ def test_numfig_without_numbered_toctree(app, cached_etree_parse, fname, expect) index = re.sub(':numbered:.*', '', index) (app.srcdir / 'index.rst').write_text(index, encoding='utf8') - if not app.outdir.listdir(): + if not os.listdir(app.outdir): app.build() check_xpath(cached_etree_parse(app.outdir / fname), fname, *expect) @@ -1563,7 +1563,7 @@ def test_html_dark_pygments_style_default(app): @pytest.mark.sphinx(testroot='basic', srcdir='validate_html_extra_path') def test_validate_html_extra_path(app): - (app.confdir / '_static').makedirs() + (app.confdir / '_static').mkdir(parents=True, exist_ok=True) app.config.html_extra_path = [ '/path/to/not_found', # not found '_static', @@ -1576,7 +1576,7 @@ def test_validate_html_extra_path(app): @pytest.mark.sphinx(testroot='basic', srcdir='validate_html_static_path') def test_validate_html_static_path(app): - (app.confdir / '_static').makedirs() + (app.confdir / '_static').mkdir(parents=True, exist_ok=True) app.config.html_static_path = [ '/path/to/not_found', # not found '_static', diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index f6c336935d7..10b0ccbe63f 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -124,7 +124,7 @@ def test_build_latex_doc(app, status, warning, engine, docclass, python_maximum_ app.builder.build_all() # file from latex_additional_files - assert (app.outdir / 'svgimg.svg').isfile() + assert (app.outdir / 'svgimg.svg').is_file() compile_latex_document(app, 'sphinxtests.tex', docclass) @@ -179,7 +179,7 @@ def test_latex_warnings(app, status, warning): warnings = strip_escseq(re.sub(re.escape(os.sep) + '{1,2}', '/', warning.getvalue())) warnings_exp = LATEX_WARNINGS % { - 'root': re.escape(app.srcdir.replace(os.sep, '/'))} + 'root': re.escape(app.srcdir.as_posix())} assert re.match(warnings_exp + '$', warnings), \ "Warnings don't match:\n" + \ '--- Expected (regex):\n' + warnings_exp + \ diff --git a/tests/test_build_texinfo.py b/tests/test_build_texinfo.py index be9a7452e53..7d851bbbee7 100644 --- a/tests/test_build_texinfo.py +++ b/tests/test_build_texinfo.py @@ -31,7 +31,7 @@ def test_texinfo_warnings(app, status, warning): app.builder.build_all() warnings = strip_escseq(re.sub(re.escape(os.sep) + '{1,2}', '/', warning.getvalue())) warnings_exp = TEXINFO_WARNINGS % { - 'root': re.escape(app.srcdir.replace(os.sep, '/'))} + 'root': re.escape(app.srcdir.as_posix())} assert re.match(warnings_exp + '$', warnings), \ "Warnings don't match:\n" + \ '--- Expected (regex):\n' + warnings_exp + \ diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index f8a2129a239..6abd0903f1e 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -1,5 +1,6 @@ """Test the base build process.""" import shutil +from pathlib import Path import pytest @@ -8,6 +9,7 @@ @pytest.fixture() def _setup_test(app_params): + assert isinstance(app_params.kwargs['srcdir'], Path) srcdir = app_params.kwargs['srcdir'] src_locale_dir = srcdir / 'xx' / 'LC_MESSAGES' dest_locale_dir = srcdir / 'locale' @@ -15,14 +17,14 @@ def _setup_test(app_params): for po in find_files(src_locale_dir, '.po'): copy_po = (dest_locale_dir / 'en' / 'LC_MESSAGES' / po) if not copy_po.parent.exists(): - copy_po.parent.makedirs() + copy_po.parent.mkdir(parents=True, exist_ok=True) shutil.copy(src_locale_dir / po, copy_po) yield # delete remnants left over after failed build - dest_locale_dir.rmtree(True) - (srcdir / '_build').rmtree(True) + shutil.rmtree(dest_locale_dir, ignore_errors=True) + shutil.rmtree(srcdir / '_build', ignore_errors=True) @pytest.mark.usefixtures('_setup_test') @@ -35,10 +37,7 @@ def test_compile_all_catalogs(app, status, warning): locale_dir = app.srcdir / 'locale' catalog_dir = locale_dir / app.config.language / 'LC_MESSAGES' - expect = { - x.replace('.po', '.mo') - for x in find_files(catalog_dir, '.po') - } + expect = {x.with_suffix('.mo') for x in find_files(catalog_dir, '.po')} actual = set(find_files(catalog_dir, '.mo')) assert actual # not empty assert actual == expect @@ -59,7 +58,7 @@ def get_actual(): actual_on_boot = get_actual() # sphinx.mo might be included app.builder.compile_specific_catalogs([app.srcdir / 'admonitions.txt']) actual = get_actual() - actual_on_boot - assert actual == {'admonitions.mo'} + assert set(map(str, actual)) == {'admonitions.mo'} @pytest.mark.usefixtures('_setup_test') @@ -72,10 +71,7 @@ def test_compile_update_catalogs(app, status, warning): locale_dir = app.srcdir / 'locale' catalog_dir = locale_dir / app.config.language / 'LC_MESSAGES' - expect = { - x.replace('.po', '.mo') - for x in find_files(catalog_dir, '.po') - } + expect = {x.with_suffix('.mo') for x in find_files(catalog_dir, '.po')} actual = set(find_files(catalog_dir, '.mo')) assert actual # not empty assert actual == expect diff --git a/tests/test_config.py b/tests/test_config.py index 19ad6969789..def3dcea7fa 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,7 @@ """Test the sphinx.config.Config class.""" import time +from pathlib import Path from unittest import mock import pytest @@ -8,7 +9,6 @@ import sphinx from sphinx.config import ENUM, Config, check_confval_types from sphinx.errors import ConfigError, ExtensionError, VersionRequirementError -from sphinx.testing.path import path @pytest.mark.sphinx(testroot='config', confoverrides={ @@ -66,9 +66,9 @@ def test_core_config(app, status, warning): assert cfg['project'] == cfg.project == 'Sphinx Tests' -def test_config_not_found(tempdir): +def test_config_not_found(tmp_path): with pytest.raises(ConfigError): - Config.read(tempdir) + Config.read(tmp_path) def test_extension_values(): @@ -131,35 +131,35 @@ def test_overrides_boolean(): @mock.patch("sphinx.config.logger") -def test_errors_warnings(logger, tempdir): +def test_errors_warnings(logger, tmp_path): # test the error for syntax errors in the config file - (tempdir / 'conf.py').write_text('project = \n', encoding='ascii') + (tmp_path / 'conf.py').write_text('project = \n', encoding='ascii') with pytest.raises(ConfigError) as excinfo: - Config.read(tempdir, {}, None) + Config.read(tmp_path, {}, None) assert 'conf.py' in str(excinfo.value) # test the automatic conversion of 2.x only code in configs - (tempdir / 'conf.py').write_text('project = u"Jägermeister"\n', encoding='utf8') - cfg = Config.read(tempdir, {}, None) + (tmp_path / 'conf.py').write_text('project = u"Jägermeister"\n', encoding='utf8') + cfg = Config.read(tmp_path, {}, None) cfg.init_values() assert cfg.project == 'Jägermeister' assert logger.called is False -def test_errors_if_setup_is_not_callable(tempdir, make_app): +def test_errors_if_setup_is_not_callable(tmp_path, make_app): # test the error to call setup() in the config file - (tempdir / 'conf.py').write_text('setup = 1', encoding='utf8') + (tmp_path / 'conf.py').write_text('setup = 1', encoding='utf8') with pytest.raises(ConfigError) as excinfo: - make_app(srcdir=tempdir) + make_app(srcdir=tmp_path) assert 'callable' in str(excinfo.value) @pytest.fixture() -def make_app_with_empty_project(make_app, tempdir): - (tempdir / 'conf.py').write_text('', encoding='utf8') +def make_app_with_empty_project(make_app, tmp_path): + (tmp_path / 'conf.py').write_text('', encoding='utf8') def _make_app(*args, **kw): - kw.setdefault('srcdir', path(tempdir)) + kw.setdefault('srcdir', Path(tmp_path)) return make_app(*args, **kw) return _make_app @@ -187,12 +187,12 @@ def test_needs_sphinx(make_app_with_empty_project): @mock.patch("sphinx.config.logger") -def test_config_eol(logger, tempdir): +def test_config_eol(logger, tmp_path): # test config file's eol patterns: LF, CRLF - configfile = tempdir / 'conf.py' + configfile = tmp_path / 'conf.py' for eol in (b'\n', b'\r\n'): configfile.write_bytes(b'project = "spam"' + eol) - cfg = Config.read(tempdir, {}, None) + cfg = Config.read(tmp_path, {}, None) cfg.init_values() assert cfg.project == 'spam' assert logger.called is False diff --git a/tests/test_directive_code.py b/tests/test_directive_code.py index 1e368e045a5..e53393fd842 100644 --- a/tests/test_directive_code.py +++ b/tests/test_directive_code.py @@ -297,8 +297,8 @@ def test_LiteralIncludeReader_diff(testroot, literal_inc_path): options = {'diff': testroot / 'literal-diff.inc'} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) content, lines = reader.read() - assert content == ("--- " + testroot + "/literal-diff.inc\n" - "+++ " + testroot + "/literal.inc\n" + assert content == ("--- " + str(testroot) + "/literal-diff.inc\n" + "+++ " + str(testroot) + "/literal.inc\n" "@@ -6,8 +6,8 @@\n" " pass\n" " \n" diff --git a/tests/test_domain_c.py b/tests/test_domain_c.py index a6316484533..cd9b0672ebe 100644 --- a/tests/test_domain_c.py +++ b/tests/test_domain_c.py @@ -737,7 +737,7 @@ def _get_obj(app, queryName): @pytest.mark.sphinx(testroot='domain-c-intersphinx', confoverrides={'nitpicky': True}) -def test_domain_c_build_intersphinx(tempdir, app, status, warning): +def test_domain_c_build_intersphinx(tmp_path, app, status, warning): # a splitting of test_ids_vs_tags0 into the primary directives in a remote project, # and then the references in the test project origSource = """\ @@ -754,7 +754,7 @@ def test_domain_c_build_intersphinx(tempdir, app, status, warning): .. c:type:: _type .. c:function:: void _functionParam(int param) """ # noqa: F841 - inv_file = tempdir / 'inventory' + inv_file = tmp_path / 'inventory' inv_file.write_bytes(b'''\ # Sphinx inventory version 2 # Project: C Intersphinx Test @@ -775,7 +775,7 @@ def test_domain_c_build_intersphinx(tempdir, app, status, warning): _var c:member 1 index.html#c.$ - ''')) # noqa: W291 app.config.intersphinx_mapping = { - 'https://localhost/intersphinx/c/': inv_file, + 'https://localhost/intersphinx/c/': str(inv_file), } app.config.intersphinx_cache_limit = 0 # load the inventory and check if it's done correctly diff --git a/tests/test_domain_cpp.py b/tests/test_domain_cpp.py index 49b0c22e9ba..1dbef867a18 100644 --- a/tests/test_domain_cpp.py +++ b/tests/test_domain_cpp.py @@ -1363,7 +1363,7 @@ def test_domain_cpp_build_field_role(app, status, warning): @pytest.mark.sphinx(testroot='domain-cpp-intersphinx', confoverrides={'nitpicky': True}) -def test_domain_cpp_build_intersphinx(tempdir, app, status, warning): +def test_domain_cpp_build_intersphinx(tmp_path, app, status, warning): origSource = """\ .. cpp:class:: _class .. cpp:struct:: _struct @@ -1385,7 +1385,7 @@ def test_domain_cpp_build_intersphinx(tempdir, app, status, warning): .. cpp:function:: void _functionParam(int param) .. cpp:function:: template void _templateParam() """ # noqa: F841 - inv_file = tempdir / 'inventory' + inv_file = tmp_path / 'inventory' inv_file.write_bytes(b'''\ # Sphinx inventory version 2 # Project: C Intersphinx Test @@ -1413,7 +1413,7 @@ def test_domain_cpp_build_intersphinx(tempdir, app, status, warning): _var cpp:member 1 index.html#_CPPv44$ - ''')) # noqa: W291 app.config.intersphinx_mapping = { - 'https://localhost/intersphinx/cpp/': inv_file, + 'https://localhost/intersphinx/cpp/': str(inv_file), } app.config.intersphinx_cache_limit = 0 # load the inventory and check if it's done correctly diff --git a/tests/test_environment.py b/tests/test_environment.py index c6f6b5aba52..8a34457cb90 100644 --- a/tests/test_environment.py +++ b/tests/test_environment.py @@ -1,13 +1,13 @@ """Test the BuildEnvironment class.""" import os import shutil +from pathlib import Path import pytest from sphinx.builders.html import StandaloneHTMLBuilder from sphinx.builders.latex import LaTeXBuilder from sphinx.environment import CONFIG_CHANGED, CONFIG_EXTENSIONS_CHANGED, CONFIG_NEW, CONFIG_OK -from sphinx.testing.comparer import PathComparer @pytest.mark.sphinx('dummy', testroot='basic') @@ -101,43 +101,43 @@ def test_env_relfn2path(app): # relative filename and root document relfn, absfn = app.env.relfn2path('logo.jpg', 'index') assert relfn == 'logo.jpg' - assert absfn == app.srcdir / 'logo.jpg' + assert absfn == str(app.srcdir / 'logo.jpg') # absolute filename and root document relfn, absfn = app.env.relfn2path('/logo.jpg', 'index') assert relfn == 'logo.jpg' - assert absfn == app.srcdir / 'logo.jpg' + assert absfn == str(app.srcdir / 'logo.jpg') # relative filename and a document in subdir relfn, absfn = app.env.relfn2path('logo.jpg', 'subdir/index') - assert relfn == PathComparer('subdir/logo.jpg') - assert absfn == app.srcdir / 'subdir' / 'logo.jpg' + assert Path(relfn) == Path('subdir/logo.jpg') + assert absfn == str(app.srcdir / 'subdir' / 'logo.jpg') # absolute filename and a document in subdir relfn, absfn = app.env.relfn2path('/logo.jpg', 'subdir/index') assert relfn == 'logo.jpg' - assert absfn == app.srcdir / 'logo.jpg' + assert absfn == str(app.srcdir / 'logo.jpg') # relative filename having subdir relfn, absfn = app.env.relfn2path('images/logo.jpg', 'index') assert relfn == 'images/logo.jpg' - assert absfn == app.srcdir / 'images' / 'logo.jpg' + assert absfn == str(app.srcdir / 'images' / 'logo.jpg') # relative path traversal relfn, absfn = app.env.relfn2path('../logo.jpg', 'index') assert relfn == '../logo.jpg' - assert absfn == app.srcdir.parent / 'logo.jpg' + assert absfn == str(app.srcdir.parent / 'logo.jpg') # relative path traversal relfn, absfn = app.env.relfn2path('subdir/../logo.jpg', 'index') assert relfn == 'logo.jpg' - assert absfn == app.srcdir / 'logo.jpg' + assert absfn == str(app.srcdir / 'logo.jpg') # omit docname (w/ current docname) app.env.temp_data['docname'] = 'subdir/document' relfn, absfn = app.env.relfn2path('images/logo.jpg') - assert relfn == PathComparer('subdir/images/logo.jpg') - assert absfn == app.srcdir / 'subdir' / 'images' / 'logo.jpg' + assert Path(relfn) == Path('subdir/images/logo.jpg') + assert absfn == str(app.srcdir / 'subdir' / 'images' / 'logo.jpg') # omit docname (w/o current docname) app.env.temp_data.clear() diff --git a/tests/test_ext_apidoc.py b/tests/test_ext_apidoc.py index 4276d512858..bf21a5fbddb 100644 --- a/tests/test_ext_apidoc.py +++ b/tests/test_ext_apidoc.py @@ -6,16 +6,15 @@ import sphinx.ext.apidoc from sphinx.ext.apidoc import main as apidoc_main -from sphinx.testing.path import path @pytest.fixture() -def apidoc(rootdir, tempdir, apidoc_params): +def apidoc(rootdir, tmp_path, apidoc_params): _, kwargs = apidoc_params coderoot = rootdir / kwargs.get('coderoot', 'test-root') - outdir = tempdir / 'out' - excludes = [coderoot / e for e in kwargs.get('excludes', [])] - args = ['-o', outdir, '-F', coderoot] + excludes + kwargs.get('options', []) + outdir = tmp_path / 'out' + excludes = [str(coderoot / e) for e in kwargs.get('excludes', [])] + args = ['-o', str(outdir), '-F', str(coderoot)] + excludes + kwargs.get('options', []) apidoc_main(args) return namedtuple('apidoc', 'coderoot,outdir')(coderoot, outdir) @@ -37,8 +36,8 @@ def apidoc_params(request): @pytest.mark.apidoc(coderoot='test-root') def test_simple(make_app, apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'index.rst').isfile() + assert (outdir / 'conf.py').is_file() + assert (outdir / 'index.rst').is_file() app = make_app('text', srcdir=outdir) app.build() @@ -52,10 +51,10 @@ def test_simple(make_app, apidoc): ) def test_pep_0420_enabled(make_app, apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'a.b.c.rst').isfile() - assert (outdir / 'a.b.e.rst').isfile() - assert (outdir / 'a.b.x.rst').isfile() + assert (outdir / 'conf.py').is_file() + assert (outdir / 'a.b.c.rst').is_file() + assert (outdir / 'a.b.e.rst').is_file() + assert (outdir / 'a.b.x.rst').is_file() with open(outdir / 'a.b.c.rst', encoding='utf-8') as f: rst = f.read() @@ -77,9 +76,9 @@ def test_pep_0420_enabled(make_app, apidoc): print(app._warning.getvalue()) builddir = outdir / '_build' / 'text' - assert (builddir / 'a.b.c.txt').isfile() - assert (builddir / 'a.b.e.txt').isfile() - assert (builddir / 'a.b.x.txt').isfile() + assert (builddir / 'a.b.c.txt').is_file() + assert (builddir / 'a.b.e.txt').is_file() + assert (builddir / 'a.b.x.txt').is_file() with open(builddir / 'a.b.c.txt', encoding='utf-8') as f: txt = f.read() @@ -100,12 +99,12 @@ def test_pep_0420_enabled(make_app, apidoc): ) def test_pep_0420_enabled_separate(make_app, apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'a.b.c.rst').isfile() - assert (outdir / 'a.b.e.rst').isfile() - assert (outdir / 'a.b.e.f.rst').isfile() - assert (outdir / 'a.b.x.rst').isfile() - assert (outdir / 'a.b.x.y.rst').isfile() + assert (outdir / 'conf.py').is_file() + assert (outdir / 'a.b.c.rst').is_file() + assert (outdir / 'a.b.e.rst').is_file() + assert (outdir / 'a.b.e.f.rst').is_file() + assert (outdir / 'a.b.x.rst').is_file() + assert (outdir / 'a.b.x.y.rst').is_file() with open(outdir / 'a.b.c.rst', encoding='utf-8') as f: rst = f.read() @@ -125,11 +124,11 @@ def test_pep_0420_enabled_separate(make_app, apidoc): print(app._warning.getvalue()) builddir = outdir / '_build' / 'text' - assert (builddir / 'a.b.c.txt').isfile() - assert (builddir / 'a.b.e.txt').isfile() - assert (builddir / 'a.b.e.f.txt').isfile() - assert (builddir / 'a.b.x.txt').isfile() - assert (builddir / 'a.b.x.y.txt').isfile() + assert (builddir / 'a.b.c.txt').is_file() + assert (builddir / 'a.b.e.txt').is_file() + assert (builddir / 'a.b.e.f.txt').is_file() + assert (builddir / 'a.b.x.txt').is_file() + assert (builddir / 'a.b.x.y.txt').is_file() with open(builddir / 'a.b.c.txt', encoding='utf-8') as f: txt = f.read() @@ -147,7 +146,7 @@ def test_pep_0420_enabled_separate(make_app, apidoc): @pytest.mark.apidoc(coderoot='test-apidoc-pep420/a') def test_pep_0420_disabled(make_app, apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() + assert (outdir / 'conf.py').is_file() assert not (outdir / 'a.b.c.rst').exists() assert not (outdir / 'a.b.x.rst').exists() @@ -161,8 +160,8 @@ def test_pep_0420_disabled(make_app, apidoc): coderoot='test-apidoc-pep420/a/b') def test_pep_0420_disabled_top_level_verify(make_app, apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'c.rst').isfile() + assert (outdir / 'conf.py').is_file() + assert (outdir / 'c.rst').is_file() assert not (outdir / 'x.rst').exists() with open(outdir / 'c.rst', encoding='utf-8') as f: @@ -181,8 +180,8 @@ def test_pep_0420_disabled_top_level_verify(make_app, apidoc): coderoot='test-apidoc-trailing-underscore') def test_trailing_underscore(make_app, apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'package_.rst').isfile() + assert (outdir / 'conf.py').is_file() + assert (outdir / 'package_.rst').is_file() app = make_app('text', srcdir=outdir) app.build() @@ -203,13 +202,13 @@ def test_trailing_underscore(make_app, apidoc): ) def test_excludes(apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'a.rst').isfile() - assert (outdir / 'a.b.rst').isfile() - assert (outdir / 'a.b.c.rst').isfile() # generated because not empty - assert not (outdir / 'a.b.e.rst').isfile() # skipped because of empty after excludes - assert (outdir / 'a.b.x.rst').isfile() - assert (outdir / 'a.b.x.y.rst').isfile() + assert (outdir / 'conf.py').is_file() + assert (outdir / 'a.rst').is_file() + assert (outdir / 'a.b.rst').is_file() + assert (outdir / 'a.b.c.rst').is_file() # generated because not empty + assert not (outdir / 'a.b.e.rst').is_file() # skipped because of empty after excludes + assert (outdir / 'a.b.x.rst').is_file() + assert (outdir / 'a.b.x.y.rst').is_file() @pytest.mark.apidoc( @@ -220,11 +219,11 @@ def test_excludes(apidoc): def test_excludes_subpackage_should_be_skipped(apidoc): """Subpackage exclusion should work.""" outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'a.rst').isfile() - assert (outdir / 'a.b.rst').isfile() - assert (outdir / 'a.b.c.rst').isfile() # generated because not empty - assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because 'b/e' subpackage is skipped + assert (outdir / 'conf.py').is_file() + assert (outdir / 'a.rst').is_file() + assert (outdir / 'a.b.rst').is_file() + assert (outdir / 'a.b.c.rst').is_file() # generated because not empty + assert not (outdir / 'a.b.e.f.rst').is_file() # skipped because 'b/e' subpackage is skipped @pytest.mark.apidoc( @@ -235,11 +234,11 @@ def test_excludes_subpackage_should_be_skipped(apidoc): def test_excludes_module_should_be_skipped(apidoc): """Module exclusion should work.""" outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'a.rst').isfile() - assert (outdir / 'a.b.rst').isfile() - assert (outdir / 'a.b.c.rst').isfile() # generated because not empty - assert not (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes + assert (outdir / 'conf.py').is_file() + assert (outdir / 'a.rst').is_file() + assert (outdir / 'a.b.rst').is_file() + assert (outdir / 'a.b.c.rst').is_file() # generated because not empty + assert not (outdir / 'a.b.e.f.rst').is_file() # skipped because of empty after excludes @pytest.mark.apidoc( @@ -250,11 +249,11 @@ def test_excludes_module_should_be_skipped(apidoc): def test_excludes_module_should_not_be_skipped(apidoc): """Module should be included if no excludes are used.""" outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'a.rst').isfile() - assert (outdir / 'a.b.rst').isfile() - assert (outdir / 'a.b.c.rst').isfile() # generated because not empty - assert (outdir / 'a.b.e.f.rst').isfile() # skipped because of empty after excludes + assert (outdir / 'conf.py').is_file() + assert (outdir / 'a.rst').is_file() + assert (outdir / 'a.b.rst').is_file() + assert (outdir / 'a.b.c.rst').is_file() # generated because not empty + assert (outdir / 'a.b.e.f.rst').is_file() # skipped because of empty after excludes @pytest.mark.apidoc( @@ -268,8 +267,8 @@ def test_excludes_module_should_not_be_skipped(apidoc): ) def test_multibyte_parameters(make_app, apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() - assert (outdir / 'index.rst').isfile() + assert (outdir / 'conf.py').is_file() + assert (outdir / 'index.rst').is_file() conf_py = (outdir / 'conf.py').read_text(encoding='utf8') assert "project = 'プロジェクト名'" in conf_py @@ -289,7 +288,7 @@ def test_multibyte_parameters(make_app, apidoc): ) def test_extension_parsed(make_app, apidoc): outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() + assert (outdir / 'conf.py').is_file() with open(outdir / 'conf.py', encoding='utf-8') as f: rst = f.read() @@ -307,7 +306,7 @@ def test_toc_all_references_should_exist_pep420_enabled(make_app, apidoc): and what is created. This is the variant with pep420 enabled. """ outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() + assert (outdir / 'conf.py').is_file() toc = extract_toc(outdir / 'mypackage.rst') @@ -319,7 +318,7 @@ def test_toc_all_references_should_exist_pep420_enabled(make_app, apidoc): continue found_refs.append(ref) filename = f"{ref}.rst" - if not (outdir / filename).isfile(): + if not (outdir / filename).is_file(): missing_files.append(filename) assert len(missing_files) == 0, \ @@ -337,7 +336,7 @@ def test_toc_all_references_should_exist_pep420_disabled(make_app, apidoc): and what is created. This is the variant with pep420 disabled. """ outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() + assert (outdir / 'conf.py').is_file() toc = extract_toc(outdir / 'mypackage.rst') @@ -349,7 +348,7 @@ def test_toc_all_references_should_exist_pep420_disabled(make_app, apidoc): continue filename = f"{ref}.rst" found_refs.append(ref) - if not (outdir / filename).isfile(): + if not (outdir / filename).is_file(): missing_files.append(filename) assert len(missing_files) == 0, \ @@ -382,44 +381,44 @@ def test_subpackage_in_toc(make_app, apidoc): are not skipped (issue #4520) """ outdir = apidoc.outdir - assert (outdir / 'conf.py').isfile() + assert (outdir / 'conf.py').is_file() - assert (outdir / 'parent.rst').isfile() + assert (outdir / 'parent.rst').is_file() with open(outdir / 'parent.rst', encoding='utf-8') as f: parent = f.read() assert 'parent.child' in parent - assert (outdir / 'parent.child.rst').isfile() + assert (outdir / 'parent.child.rst').is_file() with open(outdir / 'parent.child.rst', encoding='utf-8') as f: parent_child = f.read() assert 'parent.child.foo' in parent_child - assert (outdir / 'parent.child.foo.rst').isfile() + assert (outdir / 'parent.child.foo.rst').is_file() -def test_private(tempdir): - (tempdir / 'hello.py').write_text('', encoding='utf8') - (tempdir / '_world.py').write_text('', encoding='utf8') +def test_private(tmp_path): + (tmp_path / 'hello.py').write_text('', encoding='utf8') + (tmp_path / '_world.py').write_text('', encoding='utf8') # without --private option - apidoc_main(['-o', tempdir, tempdir]) - assert (tempdir / 'hello.rst').exists() - assert ':private-members:' not in (tempdir / 'hello.rst').read_text(encoding='utf8') - assert not (tempdir / '_world.rst').exists() + apidoc_main(['-o', str(tmp_path), str(tmp_path)]) + assert (tmp_path / 'hello.rst').exists() + assert ':private-members:' not in (tmp_path / 'hello.rst').read_text(encoding='utf8') + assert not (tmp_path / '_world.rst').exists() # with --private option - apidoc_main(['--private', '-f', '-o', tempdir, tempdir]) - assert (tempdir / 'hello.rst').exists() - assert ':private-members:' in (tempdir / 'hello.rst').read_text(encoding='utf8') - assert (tempdir / '_world.rst').exists() + apidoc_main(['--private', '-f', '-o', str(tmp_path), str(tmp_path)]) + assert (tmp_path / 'hello.rst').exists() + assert ':private-members:' in (tmp_path / 'hello.rst').read_text(encoding='utf8') + assert (tmp_path / '_world.rst').exists() -def test_toc_file(tempdir): - outdir = path(tempdir) - (outdir / 'module').makedirs() +def test_toc_file(tmp_path): + outdir = tmp_path + (outdir / 'module').mkdir(parents=True, exist_ok=True) (outdir / 'example.py').write_text('', encoding='utf8') (outdir / 'module' / 'example.py').write_text('', encoding='utf8') - apidoc_main(['-o', tempdir, tempdir]) + apidoc_main(['-o', str(tmp_path), str(tmp_path)]) assert (outdir / 'modules.rst').exists() content = (outdir / 'modules.rst').read_text(encoding='utf8') @@ -432,10 +431,10 @@ def test_toc_file(tempdir): " example\n") -def test_module_file(tempdir): - outdir = path(tempdir) +def test_module_file(tmp_path): + outdir = tmp_path (outdir / 'example.py').write_text('', encoding='utf8') - apidoc_main(['-o', tempdir, tempdir]) + apidoc_main(['-o', str(tmp_path), str(tmp_path)]) assert (outdir / 'example.rst').exists() content = (outdir / 'example.rst').read_text(encoding='utf8') @@ -448,10 +447,10 @@ def test_module_file(tempdir): " :show-inheritance:\n") -def test_module_file_noheadings(tempdir): - outdir = path(tempdir) +def test_module_file_noheadings(tmp_path): + outdir = tmp_path (outdir / 'example.py').write_text('', encoding='utf8') - apidoc_main(['--no-headings', '-o', tempdir, tempdir]) + apidoc_main(['--no-headings', '-o', str(tmp_path), str(tmp_path)]) assert (outdir / 'example.rst').exists() content = (outdir / 'example.rst').read_text(encoding='utf8') @@ -461,15 +460,15 @@ def test_module_file_noheadings(tempdir): " :show-inheritance:\n") -def test_package_file(tempdir): - outdir = path(tempdir) - (outdir / 'testpkg').makedirs() +def test_package_file(tmp_path): + outdir = tmp_path + (outdir / 'testpkg').mkdir(parents=True, exist_ok=True) (outdir / 'testpkg' / '__init__.py').write_text('', encoding='utf8') (outdir / 'testpkg' / 'hello.py').write_text('', encoding='utf8') (outdir / 'testpkg' / 'world.py').write_text('', encoding='utf8') - (outdir / 'testpkg' / 'subpkg').makedirs() + (outdir / 'testpkg' / 'subpkg').mkdir(parents=True, exist_ok=True) (outdir / 'testpkg' / 'subpkg' / '__init__.py').write_text('', encoding='utf8') - apidoc_main(['-o', tempdir, tempdir / 'testpkg']) + apidoc_main(['-o', str(outdir), str(outdir / 'testpkg')]) assert (outdir / 'testpkg.rst').exists() assert (outdir / 'testpkg.subpkg.rst').exists() @@ -525,12 +524,12 @@ def test_package_file(tempdir): " :show-inheritance:\n") -def test_package_file_separate(tempdir): - outdir = path(tempdir) - (outdir / 'testpkg').makedirs() +def test_package_file_separate(tmp_path): + outdir = tmp_path + (outdir / 'testpkg').mkdir(parents=True, exist_ok=True) (outdir / 'testpkg' / '__init__.py').write_text('', encoding='utf8') (outdir / 'testpkg' / 'example.py').write_text('', encoding='utf8') - apidoc_main(['--separate', '-o', tempdir, tempdir / 'testpkg']) + apidoc_main(['--separate', '-o', str(tmp_path), str(tmp_path / 'testpkg')]) assert (outdir / 'testpkg.rst').exists() assert (outdir / 'testpkg.example.rst').exists() @@ -564,12 +563,12 @@ def test_package_file_separate(tempdir): " :show-inheritance:\n") -def test_package_file_module_first(tempdir): - outdir = path(tempdir) - (outdir / 'testpkg').makedirs() +def test_package_file_module_first(tmp_path): + outdir = tmp_path + (outdir / 'testpkg').mkdir(parents=True, exist_ok=True) (outdir / 'testpkg' / '__init__.py').write_text('', encoding='utf8') (outdir / 'testpkg' / 'example.py').write_text('', encoding='utf8') - apidoc_main(['--module-first', '-o', tempdir, tempdir]) + apidoc_main(['--module-first', '-o', str(tmp_path), str(tmp_path)]) content = (outdir / 'testpkg.rst').read_text(encoding='utf8') assert content == ("testpkg package\n" @@ -592,11 +591,11 @@ def test_package_file_module_first(tempdir): " :show-inheritance:\n") -def test_package_file_without_submodules(tempdir): - outdir = path(tempdir) - (outdir / 'testpkg').makedirs() +def test_package_file_without_submodules(tmp_path): + outdir = tmp_path + (outdir / 'testpkg').mkdir(parents=True, exist_ok=True) (outdir / 'testpkg' / '__init__.py').write_text('', encoding='utf8') - apidoc_main(['-o', tempdir, tempdir / 'testpkg']) + apidoc_main(['-o', str(tmp_path), str(tmp_path / 'testpkg')]) assert (outdir / 'testpkg.rst').exists() content = (outdir / 'testpkg.rst').read_text(encoding='utf8') @@ -612,11 +611,11 @@ def test_package_file_without_submodules(tempdir): " :show-inheritance:\n") -def test_namespace_package_file(tempdir): - outdir = path(tempdir) - (outdir / 'testpkg').makedirs() +def test_namespace_package_file(tmp_path): + outdir = tmp_path + (outdir / 'testpkg').mkdir(parents=True, exist_ok=True) (outdir / 'testpkg' / 'example.py').write_text('', encoding='utf8') - apidoc_main(['--implicit-namespace', '-o', tempdir, tempdir / 'testpkg']) + apidoc_main(['--implicit-namespace', '-o', str(tmp_path), str(tmp_path / 'testpkg')]) assert (outdir / 'testpkg.rst').exists() content = (outdir / 'testpkg.rst').read_text(encoding='utf8') diff --git a/tests/test_ext_autosummary.py b/tests/test_ext_autosummary.py index 1b56ef38eb2..0f917df1e38 100644 --- a/tests/test_ext_autosummary.py +++ b/tests/test_ext_autosummary.py @@ -2,6 +2,7 @@ import sys from io import StringIO +from pathlib import Path from unittest.mock import Mock, patch import pytest @@ -199,7 +200,7 @@ def str_content(elem): def test_escaping(app, status, warning): app.builder.build_all() - outdir = app.builder.outdir + outdir = Path(app.builder.outdir) docpage = outdir / 'underscore_module_.xml' assert docpage.exists() @@ -452,7 +453,7 @@ def test_autosummary_generate_overwrite1(app_params, make_app): args, kwargs = app_params srcdir = kwargs.get('srcdir') - (srcdir / 'generated').makedirs(exist_ok=True) + (srcdir / 'generated').mkdir(parents=True, exist_ok=True) (srcdir / 'generated' / 'autosummary_dummy_module.rst').write_text('', encoding='utf8') app = make_app(*args, **kwargs) @@ -467,7 +468,7 @@ def test_autosummary_generate_overwrite2(app_params, make_app): args, kwargs = app_params srcdir = kwargs.get('srcdir') - (srcdir / 'generated').makedirs(exist_ok=True) + (srcdir / 'generated').mkdir(parents=True, exist_ok=True) (srcdir / 'generated' / 'autosummary_dummy_module.rst').write_text('', encoding='utf8') app = make_app(*args, **kwargs) @@ -669,8 +670,8 @@ def test_invalid_autosummary_generate(app, status, warning): assert 'WARNING: autosummary_generate: file not found: unknown.rst' in warning.getvalue() -def test_autogen(rootdir, tempdir): +def test_autogen(rootdir, tmp_path): with chdir(rootdir / 'test-templating'): - args = ['-o', tempdir, '-t', '.', 'autosummary_templating.txt'] + args = ['-o', str(tmp_path), '-t', '.', 'autosummary_templating.txt'] autogen_main(args) - assert (tempdir / 'sphinx.application.TemplateBridge.rst').exists() + assert (tmp_path / 'sphinx.application.TemplateBridge.rst').exists() diff --git a/tests/test_ext_inheritance_diagram.py b/tests/test_ext_inheritance_diagram.py index 00b1d689798..16581b07541 100644 --- a/tests/test_ext_inheritance_diagram.py +++ b/tests/test_ext_inheritance_diagram.py @@ -214,7 +214,7 @@ def test_import_classes(rootdir): from sphinx.util.i18n import CatalogInfo try: - sys.path.append(rootdir / 'test-ext-inheritance_diagram') + sys.path.append(str(rootdir / 'test-ext-inheritance_diagram')) from example.sphinx import DummyClass # got exception for unknown class or module diff --git a/tests/test_ext_intersphinx.py b/tests/test_ext_intersphinx.py index ac49584b115..ae869159ab7 100644 --- a/tests/test_ext_intersphinx.py +++ b/tests/test_ext_intersphinx.py @@ -91,14 +91,14 @@ def test_fetch_inventory_redirection(_read_from_url, InventoryFile, app, status, @pytest.mark.xfail(os.name != 'posix', reason="Path separator mismatch issue") -def test_missing_reference(tempdir, app, status, warning): - inv_file = tempdir / 'inventory' +def test_missing_reference(tmp_path, app, status, warning): + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) set_config(app, { - 'https://docs.python.org/': inv_file, - 'py3k': ('https://docs.python.org/py3k/', inv_file), - 'py3krel': ('py3k', inv_file), # relative path - 'py3krelparent': ('../../py3k', inv_file), # relative path, parent dir + 'https://docs.python.org/': str(inv_file), + 'py3k': ('https://docs.python.org/py3k/', str(inv_file)), + 'py3krel': ('py3k', str(inv_file)), # relative path + 'py3krelparent': ('../../py3k', str(inv_file)), # relative path, parent dir }) # load the inventory and check if it's done correctly @@ -169,11 +169,11 @@ def test_missing_reference(tempdir, app, status, warning): assert rn['refuri'] == 'https://docs.python.org/docname.html' -def test_missing_reference_pydomain(tempdir, app, status, warning): - inv_file = tempdir / 'inventory' +def test_missing_reference_pydomain(tmp_path, app, status, warning): + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) set_config(app, { - 'https://docs.python.org/': inv_file, + 'https://docs.python.org/': str(inv_file), }) # load the inventory and check if it's done correctly @@ -209,11 +209,11 @@ def test_missing_reference_pydomain(tempdir, app, status, warning): assert rn.astext() == 'A TERM' -def test_missing_reference_stddomain(tempdir, app, status, warning): - inv_file = tempdir / 'inventory' +def test_missing_reference_stddomain(tmp_path, app, status, warning): + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) set_config(app, { - 'cmd': ('https://docs.python.org/', inv_file), + 'cmd': ('https://docs.python.org/', str(inv_file)), }) # load the inventory and check if it's done correctly @@ -240,11 +240,11 @@ def test_missing_reference_stddomain(tempdir, app, status, warning): @pytest.mark.sphinx('html', testroot='ext-intersphinx-cppdomain') -def test_missing_reference_cppdomain(tempdir, app, status, warning): - inv_file = tempdir / 'inventory' +def test_missing_reference_cppdomain(tmp_path, app, status, warning): + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) set_config(app, { - 'https://docs.python.org/': inv_file, + 'https://docs.python.org/': str(inv_file), }) # load the inventory and check if it's done correctly @@ -266,11 +266,11 @@ def test_missing_reference_cppdomain(tempdir, app, status, warning): ' title="(in foo v2.0)">bartype' in html) -def test_missing_reference_jsdomain(tempdir, app, status, warning): - inv_file = tempdir / 'inventory' +def test_missing_reference_jsdomain(tmp_path, app, status, warning): + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) set_config(app, { - 'https://docs.python.org/': inv_file, + 'https://docs.python.org/': str(inv_file), }) # load the inventory and check if it's done correctly @@ -290,11 +290,11 @@ def test_missing_reference_jsdomain(tempdir, app, status, warning): assert rn.astext() == 'baz()' -def test_missing_reference_disabled_domain(tempdir, app, status, warning): - inv_file = tempdir / 'inventory' +def test_missing_reference_disabled_domain(tmp_path, app, status, warning): + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) set_config(app, { - 'inv': ('https://docs.python.org/', inv_file), + 'inv': ('https://docs.python.org/', str(inv_file)), }) # load the inventory and check if it's done correctly @@ -353,11 +353,11 @@ def assert_(rn, expected): @pytest.mark.xfail(os.name != 'posix', reason="Path separator mismatch issue") -def test_inventory_not_having_version(tempdir, app, status, warning): - inv_file = tempdir / 'inventory' +def test_inventory_not_having_version(tmp_path, app, status, warning): + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2_not_having_version) set_config(app, { - 'https://docs.python.org/': inv_file, + 'https://docs.python.org/': str(inv_file), }) # load the inventory and check if it's done correctly @@ -371,20 +371,20 @@ def test_inventory_not_having_version(tempdir, app, status, warning): assert rn[0].astext() == 'Long Module desc' -def test_load_mappings_warnings(tempdir, app, status, warning): +def test_load_mappings_warnings(tmp_path, app, status, warning): """ load_mappings issues a warning if new-style mapping identifiers are not string """ - inv_file = tempdir / 'inventory' + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) set_config(app, { - 'https://docs.python.org/': inv_file, - 'py3k': ('https://docs.python.org/py3k/', inv_file), - 'repoze.workflow': ('http://docs.repoze.org/workflow/', inv_file), + 'https://docs.python.org/': str(inv_file), + 'py3k': ('https://docs.python.org/py3k/', str(inv_file)), + 'repoze.workflow': ('http://docs.repoze.org/workflow/', str(inv_file)), 'django-taggit': ('http://django-taggit.readthedocs.org/en/latest/', - inv_file), - 12345: ('http://www.sphinx-doc.org/en/stable/', inv_file), + str(inv_file)), + 12345: ('http://www.sphinx-doc.org/en/stable/', str(inv_file)), }) # load the inventory and check if it's done correctly @@ -396,8 +396,8 @@ def test_load_mappings_warnings(tempdir, app, status, warning): assert 'intersphinx identifier 12345 is not string. Ignored' in warnings[1] -def test_load_mappings_fallback(tempdir, app, status, warning): - inv_file = tempdir / 'inventory' +def test_load_mappings_fallback(tmp_path, app, status, warning): + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) set_config(app, {}) @@ -419,7 +419,7 @@ def test_load_mappings_fallback(tempdir, app, status, warning): # add fallbacks to mapping app.config.intersphinx_mapping = { 'fallback': ('https://docs.python.org/py3k/', ('/invalid/inventory/path', - inv_file)), + str(inv_file))), } normalize_intersphinx_mapping(app, app.config) load_mappings(app) @@ -493,9 +493,9 @@ def test_inspect_main_noargs(capsys): assert stderr == expected + "\n" -def test_inspect_main_file(capsys, tempdir): +def test_inspect_main_file(capsys, tmp_path): """inspect_main interface, with file argument""" - inv_file = tempdir / 'inventory' + inv_file = tmp_path / 'inventory' inv_file.write_bytes(inventory_v2) inspect_main([str(inv_file)]) @@ -532,7 +532,7 @@ def test_intersphinx_role(app, warning): inv_file = app.srcdir / 'inventory' inv_file.write_bytes(inventory_v2) app.config.intersphinx_mapping = { - 'inv': ('http://example.org/', inv_file), + 'inv': ('http://example.org/', str(inv_file)), } app.config.intersphinx_cache_limit = 0 app.config.nitpicky = True diff --git a/tests/test_intl.py b/tests/test_intl.py index e451a664e27..7b3bfd95799 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -4,8 +4,11 @@ """ import os +import os.path import re +import shutil import time +from pathlib import Path import pygments import pytest @@ -14,13 +17,8 @@ from docutils import nodes from sphinx import locale -from sphinx.testing.util import ( - assert_node, - assert_not_re_search, - assert_re_search, - assert_startswith, - etree_parse, - path, +from sphinx.testing.util import (assert_node, assert_not_re_search, assert_re_search, + assert_startswith, etree_parse, strip_escseq, ) from sphinx.util.nodes import NodeMatcher @@ -35,28 +33,29 @@ def read_po(pathname): - with pathname.open(encoding='utf-8') as f: + with open(pathname, encoding='utf-8') as f: return pofile.read_po(f) def write_mo(pathname, po): - with pathname.open('wb') as f: + with open(pathname, 'wb') as f: return mofile.write_mo(f, po) @pytest.fixture(autouse=True) def _setup_intl(app_params): - srcdir = path(app_params.kwargs['srcdir']) + assert isinstance(app_params.kwargs['srcdir'], Path) + srcdir = app_params.kwargs['srcdir'] for dirpath, _dirs, files in os.walk(srcdir): - dirpath = path(dirpath) + dirpath = Path(dirpath) for f in [f for f in files if f.endswith('.po')]: - po = dirpath / f + po = str(dirpath / f) mo = srcdir / 'xx' / 'LC_MESSAGES' / ( os.path.relpath(po[:-3], srcdir) + '.mo') if not mo.parent.exists(): - mo.parent.makedirs() + mo.parent.mkdir(parents=True, exist_ok=True) - if not mo.exists() or mo.stat().st_mtime < po.stat().st_mtime: + if not mo.exists() or os.stat(mo).st_mtime < os.stat(po).st_mtime: # compile .mo file only if needed write_mo(mo, read_po(po)) @@ -697,13 +696,13 @@ def get_update_targets(app_): # When rewriting the timestamp of mo file, the number of documents to be # updated will be changed. mtime = (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime - (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime((mtime + 5, mtime + 5)) + os.utime(app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 5, mtime + 5)) update_targets = get_update_targets(app0) assert update_targets[1] == {'bom'}, update_targets # Because doctree for gettext builder can not be shared with other builders, # erase doctreedir before gettext build. - app0.doctreedir.rmtree() + shutil.rmtree(app0.doctreedir) # phase2: build document with gettext builder. # The mo file in the srcdir directory is retained. @@ -715,7 +714,7 @@ def get_update_targets(app_): assert update_targets[1] == set(), update_targets # Even if the timestamp of the mo file is updated, the number of documents # to be updated is 0. gettext builder does not rebuild because of mo update. - (app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime((mtime + 10, mtime + 10)) + os.utime(app0.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 10, mtime + 10)) update_targets = get_update_targets(app) assert update_targets[1] == set(), update_targets @@ -867,7 +866,7 @@ def test_html_rebuild_mo(app): assert len(updated) == 0 mtime = (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').stat().st_mtime - (app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo').utime((mtime + 5, mtime + 5)) + os.utime(app.srcdir / 'xx' / 'LC_MESSAGES' / 'bom.mo', (mtime + 5, mtime + 5)) app.env.find_files(app.config, app.builder) _, updated, _ = app.env.get_outdated_files(config_changed=False) assert len(updated) == 1 @@ -1401,7 +1400,7 @@ def getwarning(warnings): }) def test_gettext_allow_fuzzy_translations(app): locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES' - locale_dir.makedirs() + locale_dir.mkdir(parents=True, exist_ok=True) with (locale_dir / 'index.po').open('wb') as f: catalog = Catalog() catalog.add('features', 'FEATURES', flags=('fuzzy',)) @@ -1420,7 +1419,7 @@ def test_gettext_allow_fuzzy_translations(app): }) def test_gettext_disallow_fuzzy_translations(app): locale_dir = app.srcdir / 'locales' / 'de' / 'LC_MESSAGES' - locale_dir.makedirs() + locale_dir.mkdir(parents=True, exist_ok=True) with (locale_dir / 'index.po').open('wb') as f: catalog = Catalog() catalog.add('features', 'FEATURES', flags=('fuzzy',)) @@ -1439,7 +1438,7 @@ def test_customize_system_message(make_app, app_params, sphinx_test_tempdir): # prepare message catalog (.po) locale_dir = sphinx_test_tempdir / 'basic' / 'locales' / 'de' / 'LC_MESSAGES' - locale_dir.makedirs() + locale_dir.mkdir(parents=True, exist_ok=True) with (locale_dir / 'sphinx.po').open('wb') as f: catalog = Catalog() catalog.add('Quick search', 'QUICK SEARCH') diff --git a/tests/test_project.py b/tests/test_project.py index 9eba9316407..790865fc1cf 100644 --- a/tests/test_project.py +++ b/tests/test_project.py @@ -6,7 +6,7 @@ def test_project_discover(rootdir): - project = Project(rootdir / 'test-root', {}) + project = Project(str(rootdir / 'test-root'), {}) docnames = {'autodoc', 'bom', 'extapi', 'extensions', 'footnote', 'images', 'includes', 'index', 'lists', 'markup', 'math', 'objects', @@ -48,7 +48,7 @@ def test_project_path2doc(app): assert project.path2doc('index.foo.rst') == 'index.foo' assert project.path2doc('index') is None assert project.path2doc('path/to/index.rst') == 'path/to/index' - assert project.path2doc(app.srcdir / 'to/index.rst') == 'to/index' + assert project.path2doc(str(app.srcdir / 'to/index.rst')) == 'to/index' @pytest.mark.sphinx(srcdir='project_doc2path', testroot='basic') @@ -56,17 +56,17 @@ def test_project_doc2path(app): source_suffix = {'.rst': 'restructuredtext', '.txt': 'restructuredtext'} project = Project(app.srcdir, source_suffix) - assert project.doc2path('index') == (app.srcdir / 'index.rst') + assert project.doc2path('index') == str(app.srcdir / 'index.rst') # first source_suffix is used for missing file - assert project.doc2path('foo') == (app.srcdir / 'foo.rst') + assert project.doc2path('foo') == str(app.srcdir / 'foo.rst') # matched source_suffix is used if exists (app.srcdir / 'foo.txt').write_text('', encoding='utf8') - assert project.doc2path('foo') == (app.srcdir / 'foo.txt') + assert project.doc2path('foo') == str(app.srcdir / 'foo.txt') # absolute path - assert project.doc2path('index', basedir=True) == (app.srcdir / 'index.rst') + assert project.doc2path('index', basedir=True) == str(app.srcdir / 'index.rst') # relative path assert project.doc2path('index', basedir=False) == 'index.rst' diff --git a/tests/test_pycode.py b/tests/test_pycode.py index 92a8a19e912..5739787fff2 100644 --- a/tests/test_pycode.py +++ b/tests/test_pycode.py @@ -40,7 +40,7 @@ def test_ModuleAnalyzer_for_module(rootdir): assert analyzer.srcname in (SPHINX_MODULE_PATH, os.path.abspath(SPHINX_MODULE_PATH)) - path = rootdir / 'test-pycode' + path = str(rootdir / 'test-pycode') sys.path.insert(0, path) try: analyzer = ModuleAnalyzer.for_module('cp_1251_coded') diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index bba97f5e3c4..6a9f5c70979 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -85,9 +85,9 @@ def test_do_prompt_with_nonascii(): assert result == '\u30c9\u30a4\u30c4' -def test_quickstart_defaults(tempdir): +def test_quickstart_defaults(tmp_path): answers = { - 'Root path': tempdir, + 'Root path': str(tmp_path), 'Project name': 'Sphinx Test', 'Author name': 'Georg Brandl', 'Project version': '0.1', @@ -97,8 +97,8 @@ def test_quickstart_defaults(tempdir): qs.ask_user(d) qs.generate(d) - conffile = tempdir / 'conf.py' - assert conffile.isfile() + conffile = tmp_path / 'conf.py' + assert conffile.is_file() ns = {} exec(conffile.read_text(encoding='utf8'), ns) # NoQA: S102 assert ns['extensions'] == [] @@ -109,16 +109,16 @@ def test_quickstart_defaults(tempdir): assert ns['release'] == '0.1' assert ns['html_static_path'] == ['_static'] - assert (tempdir / '_static').isdir() - assert (tempdir / '_templates').isdir() - assert (tempdir / 'index.rst').isfile() - assert (tempdir / 'Makefile').isfile() - assert (tempdir / 'make.bat').isfile() + assert (tmp_path / '_static').is_dir() + assert (tmp_path / '_templates').is_dir() + assert (tmp_path / 'index.rst').is_file() + assert (tmp_path / 'Makefile').is_file() + assert (tmp_path / 'make.bat').is_file() -def test_quickstart_all_answers(tempdir): +def test_quickstart_all_answers(tmp_path): answers = { - 'Root path': tempdir, + 'Root path': str(tmp_path), 'Separate source and build': 'y', 'Name prefix for templates': '.', 'Project name': 'STASI™', @@ -147,8 +147,8 @@ def test_quickstart_all_answers(tempdir): qs.ask_user(d) qs.generate(d) - conffile = tempdir / 'source' / 'conf.py' - assert conffile.isfile() + conffile = tmp_path / 'source' / 'conf.py' + assert conffile.is_file() ns = {} exec(conffile.read_text(encoding='utf8'), ns) # NoQA: S102 assert ns['extensions'] == [ @@ -165,15 +165,15 @@ def test_quickstart_all_answers(tempdir): assert ns['todo_include_todos'] is True assert ns['html_static_path'] == ['.static'] - assert (tempdir / 'build').isdir() - assert (tempdir / 'source' / '.static').isdir() - assert (tempdir / 'source' / '.templates').isdir() - assert (tempdir / 'source' / 'contents.txt').isfile() + assert (tmp_path / 'build').is_dir() + assert (tmp_path / 'source' / '.static').is_dir() + assert (tmp_path / 'source' / '.templates').is_dir() + assert (tmp_path / 'source' / 'contents.txt').is_file() -def test_generated_files_eol(tempdir): +def test_generated_files_eol(tmp_path): answers = { - 'Root path': tempdir, + 'Root path': str(tmp_path), 'Project name': 'Sphinx Test', 'Author name': 'Georg Brandl', 'Project version': '0.1', @@ -187,13 +187,13 @@ def assert_eol(filename, eol): content = filename.read_bytes().decode() assert all(l[-len(eol):] == eol for l in content.splitlines(keepends=True)) - assert_eol(tempdir / 'make.bat', '\r\n') - assert_eol(tempdir / 'Makefile', '\n') + assert_eol(tmp_path / 'make.bat', '\r\n') + assert_eol(tmp_path / 'Makefile', '\n') -def test_quickstart_and_build(tempdir): +def test_quickstart_and_build(tmp_path): answers = { - 'Root path': tempdir, + 'Root path': str(tmp_path), 'Project name': 'Fullwidth characters: \u30c9\u30a4\u30c4', 'Author name': 'Georg Brandl', 'Project version': '0.1', @@ -204,10 +204,10 @@ def test_quickstart_and_build(tempdir): qs.generate(d) app = application.Sphinx( - tempdir, # srcdir - tempdir, # confdir - (tempdir / '_build' / 'html'), # outdir - (tempdir / '_build' / '.doctree'), # doctreedir + tmp_path, # srcdir + tmp_path, # confdir + (tmp_path / '_build' / 'html'), # outdir + (tmp_path / '_build' / '.doctree'), # doctreedir 'html', # buildername status=StringIO(), warning=warnfile) @@ -216,9 +216,9 @@ def test_quickstart_and_build(tempdir): assert not warnings -def test_default_filename(tempdir): +def test_default_filename(tmp_path): answers = { - 'Root path': tempdir, + 'Root path': str(tmp_path), 'Project name': '\u30c9\u30a4\u30c4', # Fullwidth characters only 'Author name': 'Georg Brandl', 'Project version': '0.1', @@ -228,25 +228,25 @@ def test_default_filename(tempdir): qs.ask_user(d) qs.generate(d) - conffile = tempdir / 'conf.py' - assert conffile.isfile() + conffile = tmp_path / 'conf.py' + assert conffile.is_file() ns = {} exec(conffile.read_text(encoding='utf8'), ns) # NoQA: S102 -def test_extensions(tempdir): +def test_extensions(tmp_path): qs.main(['-q', '-p', 'project_name', '-a', 'author', - '--extensions', 'foo,bar,baz', tempdir]) + '--extensions', 'foo,bar,baz', str(tmp_path)]) - conffile = tempdir / 'conf.py' - assert conffile.isfile() + conffile = tmp_path / 'conf.py' + assert conffile.is_file() ns = {} exec(conffile.read_text(encoding='utf8'), ns) # NoQA: S102 assert ns['extensions'] == ['foo', 'bar', 'baz'] def test_exits_when_existing_confpy(monkeypatch): - # The code detects existing conf.py with path.isfile() + # The code detects existing conf.py with path.is_file() # so we mock it as True with pytest's monkeypatch def mock_isfile(path): return True diff --git a/tests/test_theming.py b/tests/test_theming.py index b7ccda95c98..7473cb371c5 100644 --- a/tests/test_theming.py +++ b/tests/test_theming.py @@ -27,9 +27,9 @@ def test_theme_api(app, status, warning): # test Theme class API assert set(app.registry.html_themes.keys()) == set(themes) - assert app.registry.html_themes['test-theme'] == app.srcdir / 'test_theme' / 'test-theme' - assert app.registry.html_themes['ziptheme'] == app.srcdir / 'ziptheme.zip' - assert app.registry.html_themes['staticfiles'] == app.srcdir / 'test_theme' / 'staticfiles' + assert app.registry.html_themes['test-theme'] == str(app.srcdir / 'test_theme' / 'test-theme') + assert app.registry.html_themes['ziptheme'] == str(app.srcdir / 'ziptheme.zip') + assert app.registry.html_themes['staticfiles'] == str(app.srcdir / 'test_theme' / 'staticfiles') # test Theme instance API theme = app.builder.theme diff --git a/tests/test_util.py b/tests/test_util.py index bb4f10a8cf6..40a5515071c 100644 --- a/tests/test_util.py +++ b/tests/test_util.py @@ -33,10 +33,6 @@ def test_ensuredir(): ensuredir(path) assert os.path.isdir(path) - with tempfile.NamedTemporaryFile() as tmp: - with pytest.raises(OSError): - ensuredir(tmp.name) - def test_import_object(): module = import_object('sphinx') diff --git a/tests/test_util_fileutil.py b/tests/test_util_fileutil.py index 7ea12508c61..fea21309aac 100644 --- a/tests/test_util_fileutil.py +++ b/tests/test_util_fileutil.py @@ -15,42 +15,42 @@ def __init__(self): self.init(builder) -def test_copy_asset_file(tempdir): +def test_copy_asset_file(tmp_path): renderer = DummyTemplateLoader() # copy normal file - src = (tempdir / 'asset.txt') + src = (tmp_path / 'asset.txt') src.write_text('# test data') - dest = (tempdir / 'output.txt') + dest = (tmp_path / 'output.txt') copy_asset_file(src, dest) assert dest.exists() assert src.read_text(encoding='utf8') == dest.read_text(encoding='utf8') # copy template file - src = (tempdir / 'asset.txt_t') + src = (tmp_path / 'asset.txt_t') src.write_text('# {{var1}} data') - dest = (tempdir / 'output.txt_t') + dest = (tmp_path / 'output.txt_t') - copy_asset_file(src, dest, {'var1': 'template'}, renderer) + copy_asset_file(str(src), str(dest), {'var1': 'template'}, renderer) assert not dest.exists() - assert (tempdir / 'output.txt').exists() - assert (tempdir / 'output.txt').read_text(encoding='utf8') == '# template data' + assert (tmp_path / 'output.txt').exists() + assert (tmp_path / 'output.txt').read_text(encoding='utf8') == '# template data' # copy template file to subdir - src = (tempdir / 'asset.txt_t') + src = (tmp_path / 'asset.txt_t') src.write_text('# {{var1}} data') - subdir1 = (tempdir / 'subdir') - subdir1.makedirs() + subdir1 = (tmp_path / 'subdir') + subdir1.mkdir(parents=True, exist_ok=True) copy_asset_file(src, subdir1, {'var1': 'template'}, renderer) assert (subdir1 / 'asset.txt').exists() assert (subdir1 / 'asset.txt').read_text(encoding='utf8') == '# template data' # copy template file without context - src = (tempdir / 'asset.txt_t') - subdir2 = (tempdir / 'subdir2') - subdir2.makedirs() + src = (tmp_path / 'asset.txt_t') + subdir2 = (tmp_path / 'subdir2') + subdir2.mkdir(parents=True, exist_ok=True) copy_asset_file(src, subdir2) assert not (subdir2 / 'asset.txt').exists() @@ -58,28 +58,28 @@ def test_copy_asset_file(tempdir): assert (subdir2 / 'asset.txt_t').read_text(encoding='utf8') == '# {{var1}} data' -def test_copy_asset(tempdir): +def test_copy_asset(tmp_path): renderer = DummyTemplateLoader() # prepare source files - source = (tempdir / 'source') - source.makedirs() + source = (tmp_path / 'source') + source.mkdir(parents=True, exist_ok=True) (source / 'index.rst').write_text('index.rst', encoding='utf8') (source / 'foo.rst_t').write_text('{{var1}}.rst', encoding='utf8') - (source / '_static').makedirs() + (source / '_static').mkdir(parents=True, exist_ok=True) (source / '_static' / 'basic.css').write_text('basic.css', encoding='utf8') - (source / '_templates').makedirs() + (source / '_templates').mkdir(parents=True, exist_ok=True) (source / '_templates' / 'layout.html').write_text('layout.html', encoding='utf8') (source / '_templates' / 'sidebar.html_t').write_text('sidebar: {{var2}}', encoding='utf8') # copy a single file - assert not (tempdir / 'test1').exists() - copy_asset(source / 'index.rst', tempdir / 'test1') - assert (tempdir / 'test1').exists() - assert (tempdir / 'test1/index.rst').exists() + assert not (tmp_path / 'test1').exists() + copy_asset(source / 'index.rst', tmp_path / 'test1') + assert (tmp_path / 'test1').exists() + assert (tmp_path / 'test1/index.rst').exists() # copy directories - destdir = tempdir / 'test2' + destdir = tmp_path / 'test2' copy_asset(source, destdir, context={'var1': 'bar', 'var2': 'baz'}, renderer=renderer) assert (destdir / 'index.rst').exists() assert (destdir / 'foo.rst').exists() @@ -93,7 +93,7 @@ def test_copy_asset(tempdir): def excluded(path): return ('sidebar.html' in path or 'basic.css' in path) - destdir = tempdir / 'test3' + destdir = tmp_path / 'test3' copy_asset(source, destdir, excluded, context={'var1': 'bar', 'var2': 'baz'}, renderer=renderer) assert (destdir / 'index.rst').exists() diff --git a/tests/test_util_i18n.py b/tests/test_util_i18n.py index 8aae9402847..735ee678852 100644 --- a/tests/test_util_i18n.py +++ b/tests/test_util_i18n.py @@ -29,12 +29,12 @@ def test_catalog_info_for_sub_domain_file_and_path(): assert cat.mo_path == os.path.join('path', 'sub/domain.mo') -def test_catalog_outdated(tempdir): - (tempdir / 'test.po').write_text('#', encoding='utf8') - cat = i18n.CatalogInfo(tempdir, 'test', 'utf-8') +def test_catalog_outdated(tmp_path): + (tmp_path / 'test.po').write_text('#', encoding='utf8') + cat = i18n.CatalogInfo(tmp_path, 'test', 'utf-8') assert cat.is_outdated() # if mo is not exist - mo_file = (tempdir / 'test.mo') + mo_file = (tmp_path / 'test.mo') mo_file.write_text('#', encoding='utf8') assert not cat.is_outdated() # if mo is exist and newer than po @@ -42,9 +42,9 @@ def test_catalog_outdated(tempdir): assert cat.is_outdated() # if mo is exist and older than po -def test_catalog_write_mo(tempdir): - (tempdir / 'test.po').write_text('#', encoding='utf8') - cat = i18n.CatalogInfo(tempdir, 'test', 'utf-8') +def test_catalog_write_mo(tmp_path): + (tmp_path / 'test.po').write_text('#', encoding='utf8') + cat = i18n.CatalogInfo(tmp_path, 'test', 'utf-8') cat.write_mo('en') assert os.path.exists(cat.mo_path) with open(cat.mo_path, 'rb') as f: @@ -147,45 +147,45 @@ def test_get_filename_for_language(app): '/subdir/en/foo.png') -def test_CatalogRepository(tempdir): - (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES').makedirs() - (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#', encoding='utf8') - (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#', encoding='utf8') - (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub').makedirs() - (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test3.po').write_text('#', encoding='utf8') - (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#', encoding='utf8') - (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / '.dotdir').makedirs() - (tempdir / 'loc1' / 'xx' / 'LC_MESSAGES' / '.dotdir' / 'test5.po').write_text('#', encoding='utf8') - (tempdir / 'loc1' / 'yy' / 'LC_MESSAGES').makedirs() - (tempdir / 'loc1' / 'yy' / 'LC_MESSAGES' / 'test6.po').write_text('#', encoding='utf8') - (tempdir / 'loc2' / 'xx' / 'LC_MESSAGES').makedirs() - (tempdir / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#', encoding='utf8') - (tempdir / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test7.po').write_text('#', encoding='utf8') +def test_CatalogRepository(tmp_path): + (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES').mkdir(parents=True, exist_ok=True) + (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#', encoding='utf8') + (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / 'test2.po').write_text('#', encoding='utf8') + (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub').mkdir(parents=True, exist_ok=True) + (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test3.po').write_text('#', encoding='utf8') + (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / 'sub' / 'test4.po').write_text('#', encoding='utf8') + (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / '.dotdir').mkdir(parents=True, exist_ok=True) + (tmp_path / 'loc1' / 'xx' / 'LC_MESSAGES' / '.dotdir' / 'test5.po').write_text('#', encoding='utf8') + (tmp_path / 'loc1' / 'yy' / 'LC_MESSAGES').mkdir(parents=True, exist_ok=True) + (tmp_path / 'loc1' / 'yy' / 'LC_MESSAGES' / 'test6.po').write_text('#', encoding='utf8') + (tmp_path / 'loc2' / 'xx' / 'LC_MESSAGES').mkdir(parents=True, exist_ok=True) + (tmp_path / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test1.po').write_text('#', encoding='utf8') + (tmp_path / 'loc2' / 'xx' / 'LC_MESSAGES' / 'test7.po').write_text('#', encoding='utf8') # for language xx - repo = i18n.CatalogRepository(tempdir, ['loc1', 'loc2'], 'xx', 'utf-8') - assert list(repo.locale_dirs) == [str(tempdir / 'loc1'), - str(tempdir / 'loc2')] + repo = i18n.CatalogRepository(tmp_path, ['loc1', 'loc2'], 'xx', 'utf-8') + assert list(repo.locale_dirs) == [str(tmp_path / 'loc1'), + str(tmp_path / 'loc2')] assert all(isinstance(c, i18n.CatalogInfo) for c in repo.catalogs) assert sorted(c.domain for c in repo.catalogs) == ['sub/test3', 'sub/test4', 'test1', 'test1', 'test2', 'test7'] # for language yy - repo = i18n.CatalogRepository(tempdir, ['loc1', 'loc2'], 'yy', 'utf-8') + repo = i18n.CatalogRepository(tmp_path, ['loc1', 'loc2'], 'yy', 'utf-8') assert sorted(c.domain for c in repo.catalogs) == ['test6'] # unknown languages - repo = i18n.CatalogRepository(tempdir, ['loc1', 'loc2'], 'zz', 'utf-8') + repo = i18n.CatalogRepository(tmp_path, ['loc1', 'loc2'], 'zz', 'utf-8') assert sorted(c.domain for c in repo.catalogs) == [] # no languages - repo = i18n.CatalogRepository(tempdir, ['loc1', 'loc2'], None, 'utf-8') + repo = i18n.CatalogRepository(tmp_path, ['loc1', 'loc2'], None, 'utf-8') assert sorted(c.domain for c in repo.catalogs) == [] # unknown locale_dirs - repo = i18n.CatalogRepository(tempdir, ['loc3'], None, 'utf-8') + repo = i18n.CatalogRepository(tmp_path, ['loc3'], None, 'utf-8') assert sorted(c.domain for c in repo.catalogs) == [] # no locale_dirs - repo = i18n.CatalogRepository(tempdir, [], None, 'utf-8') + repo = i18n.CatalogRepository(tmp_path, [], None, 'utf-8') assert sorted(c.domain for c in repo.catalogs) == [] diff --git a/tests/test_versioning.py b/tests/test_versioning.py index 80f1798f65d..ae159dfebac 100644 --- a/tests/test_versioning.py +++ b/tests/test_versioning.py @@ -1,6 +1,7 @@ """Test the versioning implementation.""" import pickle +import shutil import pytest @@ -15,7 +16,7 @@ def _setup_module(rootdir, sphinx_test_tempdir): global app, original, original_uids srcdir = sphinx_test_tempdir / 'test-versioning' if not srcdir.exists(): - (rootdir / 'test-versioning').copytree(srcdir) + shutil.copytree(rootdir / 'test-versioning', srcdir) app = SphinxTestApp(srcdir=srcdir) app.builder.env.app = app app.connect('doctree-resolved', on_doctree_resolved) From a11f29425457123e583a71d895b0723840d41d80 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:08:25 +0100 Subject: [PATCH 02/16] linting --- sphinx/application.py | 8 +++++--- sphinx/builders/gettext.py | 2 +- sphinx/config.py | 2 +- sphinx/environment/__init__.py | 5 +++-- sphinx/project.py | 2 +- sphinx/testing/fixtures.py | 3 +-- sphinx/testing/util.py | 3 +-- sphinx/util/i18n.py | 2 +- sphinx/util/osutil.py | 1 - tests/test_intl.py | 8 ++++++-- 10 files changed, 20 insertions(+), 16 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 03c21963e9c..636343f0677 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -12,8 +12,9 @@ from io import StringIO from os import path from os.path import abspath -from typing import IO, TYPE_CHECKING, Any, Callable from pathlib import Path +from typing import IO, TYPE_CHECKING, Any, Callable + from docutils import nodes from docutils.nodes import Element, TextElement from docutils.parsers import Parser @@ -42,7 +43,7 @@ from sphinx.util.display import progress_message from sphinx.util.i18n import CatalogRepository from sphinx.util.logging import prefixed_warnings -from sphinx.util.osutil import ensuredir, relpath, StrPath +from sphinx.util.osutil import StrPath, ensuredir, relpath from sphinx.util.tags import Tags from sphinx.util.typing import RoleFunction, TitleGetter @@ -133,7 +134,8 @@ class Sphinx: warningiserror: bool _warncount: int - def __init__(self, srcdir: StrPath, confdir: StrPath | None, outdir: StrPath, doctreedir: StrPath, + def __init__(self, srcdir: StrPath, confdir: StrPath | None, + outdir: StrPath, doctreedir: StrPath, buildername: str, confoverrides: dict | None = None, status: IO | None = sys.stdout, warning: IO | None = sys.stderr, freshenv: bool = False, warningiserror: bool = False, diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index 6d825aa8572..e8ac150da8b 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -23,7 +23,7 @@ from sphinx.util.display import status_iterator from sphinx.util.i18n import CatalogInfo, docname_to_domain from sphinx.util.nodes import extract_messages, traverse_translatable_index -from sphinx.util.osutil import canon_path, ensuredir, relpath, StrPath +from sphinx.util.osutil import StrPath, canon_path, ensuredir, relpath from sphinx.util.tags import Tags from sphinx.util.template import SphinxRenderer diff --git a/sphinx/config.py b/sphinx/config.py index 1fb63c97568..d3cf309fd9b 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -11,7 +11,7 @@ from sphinx.errors import ConfigError, ExtensionError from sphinx.locale import _, __ from sphinx.util import logging -from sphinx.util.osutil import fs_encoding, StrPath +from sphinx.util.osutil import StrPath, fs_encoding from sphinx.util.tags import Tags from sphinx.util.typing import NoneType diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index a08b35e6788..7931093fa85 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -10,7 +10,7 @@ from datetime import datetime, timezone from os import path from typing import TYPE_CHECKING, Any, Callable -from pathlib import Path + from docutils import nodes from docutils.nodes import Node @@ -27,10 +27,11 @@ 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 canon_path, os_path, StrPath +from sphinx.util.osutil import StrPath, canon_path, os_path if TYPE_CHECKING: from collections.abc import Generator, Iterator + from pathlib import Path from sphinx.application import Sphinx from sphinx.builders import Builder diff --git a/sphinx/project.py b/sphinx/project.py index c713fb7fd46..04bc73c30f0 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -9,7 +9,7 @@ from sphinx.locale import __ from sphinx.util import logging from sphinx.util.matching import get_matching_files -from sphinx.util.osutil import SEP, path_stabilize, relpath, StrPath +from sphinx.util.osutil import SEP, StrPath, path_stabilize, relpath if TYPE_CHECKING: from collections.abc import Iterable diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index 1c34abceae7..f6ac053ae6b 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -7,16 +7,15 @@ import sys from collections import namedtuple from io import StringIO -from pathlib import Path from typing import TYPE_CHECKING, Any, Callable import pytest -from sphinx.testing import util from sphinx.testing.util import SphinxTestApp, SphinxTestAppWrapperForSkipBuilding if TYPE_CHECKING: from collections.abc import Generator + from pathlib import Path DEFAULT_ENABLED_MARKERS = [ ( diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 23631256d69..33e61fe70d4 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -6,7 +6,6 @@ import re import sys import warnings -from io import StringIO from pathlib import Path from typing import IO, TYPE_CHECKING, Any from xml.etree import ElementTree @@ -19,7 +18,7 @@ from sphinx.pycode import ModuleAnalyzer if TYPE_CHECKING: - from collections.abc import Generator + from collections.abc import Iterator from io import StringIO __all__ = [ diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index 5df56121496..ae36a5f97be 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -15,7 +15,7 @@ from sphinx.errors import SphinxError from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.osutil import SEP, canon_path, relpath, StrPath +from sphinx.util.osutil import SEP, StrPath, canon_path, relpath if TYPE_CHECKING: from collections.abc import Generator diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index eb078635a37..d3ae9a6836c 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -11,7 +11,6 @@ import unicodedata from io import StringIO from os import path -from pathlib import Path as Path from typing import TYPE_CHECKING, Any, Union from sphinx.deprecation import _deprecation_warning diff --git a/tests/test_intl.py b/tests/test_intl.py index 7b3bfd95799..9a47345879a 100644 --- a/tests/test_intl.py +++ b/tests/test_intl.py @@ -17,8 +17,12 @@ from docutils import nodes from sphinx import locale -from sphinx.testing.util import (assert_node, assert_not_re_search, assert_re_search, - assert_startswith, etree_parse, +from sphinx.testing.util import ( + assert_node, + assert_not_re_search, + assert_re_search, + assert_startswith, + etree_parse, strip_escseq, ) from sphinx.util.nodes import NodeMatcher From c26967d4472a177d9c3e233c0b1a62c2b2d2624a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:21:20 +0100 Subject: [PATCH 03/16] Updates --- sphinx/application.py | 14 +++++++------- sphinx/config.py | 8 ++++---- sphinx/project.py | 11 +++++------ sphinx/util/fileutil.py | 4 ++-- sphinx/util/i18n.py | 4 ++-- sphinx/util/osutil.py | 17 ++++------------- tests/conftest.py | 15 --------------- 7 files changed, 24 insertions(+), 49 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 636343f0677..12b324634f9 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -43,7 +43,7 @@ from sphinx.util.display import progress_message from sphinx.util.i18n import CatalogRepository from sphinx.util.logging import prefixed_warnings -from sphinx.util.osutil import StrPath, ensuredir, relpath +from sphinx.util.osutil import ensuredir, relpath from sphinx.util.tags import Tags from sphinx.util.typing import RoleFunction, TitleGetter @@ -134,8 +134,8 @@ class Sphinx: warningiserror: bool _warncount: int - def __init__(self, srcdir: StrPath, confdir: StrPath | None, - outdir: StrPath, doctreedir: StrPath, + def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[str] | None, + outdir: str | os.PathLike[str], doctreedir: str | os.PathLike[str], buildername: str, confoverrides: dict | None = None, status: IO | None = sys.stdout, warning: IO | None = sys.stderr, freshenv: bool = False, warningiserror: bool = False, @@ -148,9 +148,9 @@ def __init__(self, srcdir: StrPath, confdir: StrPath | None, self.registry = SphinxComponentRegistry() # validate provided directories - self.srcdir = Path(abspath(srcdir)) - self.outdir = Path(abspath(outdir)) - self.doctreedir = Path(abspath(doctreedir)) + self.srcdir = Path(srcdir).resolve() + self.outdir = Path(outdir).resolve() + self.doctreedir = Path(doctreedir).resolve() if not path.isdir(self.srcdir): raise ApplicationError(__('Cannot find source directory (%s)') % @@ -206,7 +206,7 @@ def __init__(self, srcdir: StrPath, confdir: StrPath | None, self.confdir = self.srcdir self.config = Config({}, confoverrides or {}) else: - self.confdir = Path(abspath(confdir)) + self.confdir = Path(confdir).resolve() self.config = Config.read(self.confdir, confoverrides or {}, self.tags) # initialize some limited config variables before initialize i18n and loading diff --git a/sphinx/config.py b/sphinx/config.py index d3cf309fd9b..de6b7cf64e9 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os import time import traceback import types @@ -11,7 +12,7 @@ from sphinx.errors import ConfigError, ExtensionError from sphinx.locale import _, __ from sphinx.util import logging -from sphinx.util.osutil import StrPath, fs_encoding +from sphinx.util.osutil import fs_encoding from sphinx.util.tags import Tags from sphinx.util.typing import NoneType @@ -168,9 +169,8 @@ def __init__(self, config: dict[str, Any] = {}, overrides: dict[str, Any] = {}) self.extensions: list[str] = config.get('extensions', []) @classmethod - def read( - cls, confdir: StrPath, overrides: dict | None = None, tags: Tags | None = None, - ) -> Config: + def read(cls, 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): diff --git a/sphinx/project.py b/sphinx/project.py index 04bc73c30f0..aa557b129cb 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -9,7 +9,7 @@ from sphinx.locale import __ from sphinx.util import logging from sphinx.util.matching import get_matching_files -from sphinx.util.osutil import SEP, StrPath, path_stabilize, relpath +from sphinx.util.osutil import SEP, path_stabilize, relpath if TYPE_CHECKING: from collections.abc import Iterable @@ -21,9 +21,9 @@ class Project: """A project is the source code set of the Sphinx document(s).""" - def __init__(self, srcdir: StrPath, source_suffix: dict[str, str]) -> None: + def __init__(self, srcdir: str | os.PathLike[str], source_suffix: dict[str, str]) -> None: #: Source directory. - self.srcdir = str(srcdir) + self.srcdir = srcdir #: source_suffix. Same as :confval:`source_suffix`. self.source_suffix = source_suffix @@ -61,13 +61,12 @@ def discover(self, exclude_paths: Iterable[str] = (), return self.docnames - def path2doc(self, filename: StrPath) -> str | None: + def path2doc(self, filename: str | os.PathLike[str]) -> str | None: """Return the docname for the filename if the file is a document. *filename* should be absolute or relative to the source directory. """ - filename = str(filename) - if filename.startswith(os.path.normpath(self.srcdir)): + if str(filename).startswith(str(self.srcdir)): filename = relpath(filename, self.srcdir) for suffix in self.source_suffix: if filename.endswith(suffix): diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py index 130aabe1fc6..5411cccf5b4 100644 --- a/sphinx/util/fileutil.py +++ b/sphinx/util/fileutil.py @@ -15,7 +15,7 @@ from sphinx.util.template import BaseRenderer -def copy_asset_file(source: str, destination: str, +def copy_asset_file(source: str | os.PathLike[str], destination: str, context: dict | None = None, renderer: BaseRenderer | None = None) -> None: """Copy an asset file to destination. @@ -35,7 +35,7 @@ def copy_asset_file(source: str, destination: str, # Use source filename if destination points a directory destination = os.path.join(destination, os.path.basename(source)) - if str(source).lower().endswith('_t') and context is not None: + if os.path.splitext(source)[1].lower().endswith('_t') and context is not None: if renderer is None: from sphinx.util.template import SphinxRenderer renderer = SphinxRenderer() diff --git a/sphinx/util/i18n.py b/sphinx/util/i18n.py index ae36a5f97be..11cb5b64ce0 100644 --- a/sphinx/util/i18n.py +++ b/sphinx/util/i18n.py @@ -15,7 +15,7 @@ from sphinx.errors import SphinxError from sphinx.locale import __ from sphinx.util import logging -from sphinx.util.osutil import SEP, StrPath, canon_path, relpath +from sphinx.util.osutil import SEP, canon_path, relpath if TYPE_CHECKING: from collections.abc import Generator @@ -73,7 +73,7 @@ def write_mo(self, locale: str, use_fuzzy: bool = False) -> None: class CatalogRepository: """A repository for message catalogs.""" - def __init__(self, basedir: StrPath, locale_dirs: list[str], + def __init__(self, basedir: str | os.PathLike[str], locale_dirs: list[str], language: str, encoding: str) -> None: self.basedir = basedir self._locale_dirs = locale_dirs diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index d3ae9a6836c..ae5af5a0117 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -18,7 +18,7 @@ if TYPE_CHECKING: from collections.abc import Iterator -StrPath = Union[str, os.PathLike[str]] +# StrPath = Union[str, os.PathLike[str]] # SEP separates path elements in the canonical file names # @@ -66,7 +66,7 @@ def relative_uri(base: str, to: str) -> str: return ('..' + SEP) * (len(b2) - 1) + SEP.join(t2) -def ensuredir(file: StrPath) -> None: +def ensuredir(file: str | os.PathLike[str]) -> None: """Ensure that a path exists.""" file = path.normpath(file) if not path.exists(file): @@ -116,7 +116,7 @@ def make_filename_from_project(project: str) -> str: return make_filename(project_suffix_re.sub('', project)).lower() -def relpath(path: StrPath, start: StrPath | None = os.curdir) -> str: +def relpath(path: str | os.PathLike[str], start: str | os.PathLike[str] | None = os.curdir) -> str: """Return a relative filepath to *path* either from the current directory or from an optional *start* directory. @@ -133,16 +133,7 @@ def relpath(path: StrPath, start: StrPath | None = os.curdir) -> str: fs_encoding = sys.getfilesystemencoding() or sys.getdefaultencoding() -def abspath(pathdir: StrPath) -> str: - pathdir = path.abspath(pathdir) - if isinstance(pathdir, bytes): - try: - pathdir = pathdir.decode(fs_encoding) - except UnicodeDecodeError as exc: - raise UnicodeDecodeError('multibyte filename not supported on ' - 'this filesystem encoding ' - '(%r)' % fs_encoding) from exc - return pathdir +abspath = path.abspath class _chdir: diff --git a/tests/conftest.py b/tests/conftest.py index 204c9fd67e5..fa3b1e65d6e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -37,18 +37,3 @@ def pytest_report_header(config): if hasattr(config, '_tmp_path_factory'): header += f"\nbase tmp_path: {config._tmp_path_factory.getbasetemp()}" return header - - -def _initialize_test_directory(session): - if 'SPHINX_TEST_TEMPDIR' in os.environ: - tmp_path = os.path.abspath(os.getenv('SPHINX_TEST_TEMPDIR')) - print(f'Temporary files will be placed in {tmp_path}.') - - if os.path.exists(tmp_path): - shutil.rmtree(tmp_path) - - os.mkdir(tmp_path) - - -def pytest_sessionstart(session): - _initialize_test_directory(session) From 9b16d7cb6737aa74c6192510a7dcf49afacbc91b Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:23:03 +0100 Subject: [PATCH 04/16] Updates --- sphinx/environment/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sphinx/environment/__init__.py b/sphinx/environment/__init__.py index 7931093fa85..116840e5e9e 100644 --- a/sphinx/environment/__init__.py +++ b/sphinx/environment/__init__.py @@ -27,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 StrPath, canon_path, os_path +from sphinx.util.osutil import canon_path, os_path if TYPE_CHECKING: from collections.abc import Generator, Iterator @@ -388,7 +388,7 @@ def merge_info_from(self, docnames: list[str], other: BuildEnvironment, domain.merge_domaindata(docnames, other.domaindata[domainname]) self.events.emit('env-merge-info', self, docnames, other) - def path2doc(self, filename: StrPath) -> str | None: + def path2doc(self, filename: str | os.PathLike[str]) -> str | None: """Return the docname for the filename if the file is document. *filename* should be absolute or relative to the source directory. From 05b71509916468f52985ed0381e0c79e696fd23f Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:25:21 +0100 Subject: [PATCH 05/16] Updates --- sphinx/builders/gettext.py | 10 ++++++---- sphinx/builders/html/__init__.py | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index e8ac150da8b..cdf5846f5eb 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -2,6 +2,7 @@ from __future__ import annotations +import os from codecs import open from collections import defaultdict from datetime import datetime, timedelta, timezone, tzinfo @@ -23,7 +24,7 @@ from sphinx.util.display import status_iterator from sphinx.util.i18n import CatalogInfo, docname_to_domain from sphinx.util.nodes import extract_messages, traverse_translatable_index -from sphinx.util.osutil import StrPath, canon_path, ensuredir, relpath +from sphinx.util.osutil import canon_path, ensuredir, relpath from sphinx.util.tags import Tags from sphinx.util.template import SphinxRenderer @@ -84,11 +85,12 @@ def __init__(self, source: str, line: int) -> None: class GettextRenderer(SphinxRenderer): def __init__( - self, template_path: StrPath | None = None, outdir: StrPath | None = None, + self, template_path: str | os.PathLike[str] | None = None, + outdir: str | os.PathLike[str] | None = None, ) -> None: - self.outdir = str(outdir) if outdir is not None else None + self.outdir = outdir if template_path is None: - template_path = str(path.join(package_dir, 'templates', 'gettext')) + template_path = path.join(package_dir, 'templates', 'gettext') super().__init__(template_path) def escape(s: str) -> str: diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 81ec866e1d7..223fcb153d5 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -786,7 +786,7 @@ def copy_image_files(self) -> None: def copy_download_files(self) -> None: def to_relpath(f: str) -> str: - return relative_path(str(self.srcdir), f) + return relative_path(self.srcdir, f) # copy downloadable files if self.env.dlfiles: From 398a7c288bcd47febd87dcaa8f04b500262c3d31 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:43:56 +0100 Subject: [PATCH 06/16] Updates --- sphinx/testing/util.py | 13 ++----------- tests/test_catalogs.py | 27 ++++++++++++--------------- 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/sphinx/testing/util.py b/sphinx/testing/util.py index 33e61fe70d4..a6838f26b1e 100644 --- a/sphinx/testing/util.py +++ b/sphinx/testing/util.py @@ -6,7 +6,6 @@ import re import sys import warnings -from pathlib import Path from typing import IO, TYPE_CHECKING, Any from xml.etree import ElementTree @@ -18,8 +17,8 @@ from sphinx.pycode import ModuleAnalyzer if TYPE_CHECKING: - from collections.abc import Iterator from io import StringIO + from pathlib import Path __all__ = [ 'Struct', 'SphinxTestApp', 'SphinxTestAppWrapperForSkipBuilding', @@ -137,7 +136,7 @@ def __init__( if v.startswith('visit_')} try: - super().__init__(str(srcdir), str(confdir), str(outdir), str(doctreedir), + super().__init__(srcdir, confdir, outdir, doctreedir, buildername, confoverrides, status, warning, freshenv, warningiserror, tags, parallel=parallel) except Exception: @@ -192,14 +191,6 @@ def build(self, *args: Any, **kwargs: Any) -> None: _unicode_literals_re = re.compile(r'u(".*?")|u(\'.*?\')') -def find_files(root: str, suffix: str) -> Iterator[Path]: - for file, _dirs, files in os.walk(root, followlinks=True): - dir_path = Path(file) - for f in files: - if f.endswith(suffix): - yield (dir_path / f).relative_to(root) - - def strip_escseq(text: str) -> str: return re.sub('\x1b.*?m', '', text) diff --git a/tests/test_catalogs.py b/tests/test_catalogs.py index 6abd0903f1e..b7fd7be6f1c 100644 --- a/tests/test_catalogs.py +++ b/tests/test_catalogs.py @@ -4,8 +4,6 @@ import pytest -from sphinx.testing.util import find_files - @pytest.fixture() def _setup_test(app_params): @@ -14,11 +12,11 @@ def _setup_test(app_params): src_locale_dir = srcdir / 'xx' / 'LC_MESSAGES' dest_locale_dir = srcdir / 'locale' # copy all catalogs into locale layout directory - for po in find_files(src_locale_dir, '.po'): - copy_po = (dest_locale_dir / 'en' / 'LC_MESSAGES' / po) + for po in src_locale_dir.rglob('*.po'): + copy_po = (dest_locale_dir / 'en' / 'LC_MESSAGES' / po.relative_to(src_locale_dir)) if not copy_po.parent.exists(): copy_po.parent.mkdir(parents=True, exist_ok=True) - shutil.copy(src_locale_dir / po, copy_po) + shutil.copy(po, copy_po) yield @@ -37,8 +35,8 @@ def test_compile_all_catalogs(app, status, warning): locale_dir = app.srcdir / 'locale' catalog_dir = locale_dir / app.config.language / 'LC_MESSAGES' - expect = {x.with_suffix('.mo') for x in find_files(catalog_dir, '.po')} - actual = set(find_files(catalog_dir, '.mo')) + expect = {x.with_suffix('.mo') for x in catalog_dir.rglob('*.po')} + actual = set(catalog_dir.rglob('*.mo')) assert actual # not empty assert actual == expect @@ -52,13 +50,12 @@ def test_compile_specific_catalogs(app, status, warning): locale_dir = app.srcdir / 'locale' catalog_dir = locale_dir / app.config.language / 'LC_MESSAGES' - def get_actual(): - return set(find_files(catalog_dir, '.mo')) - - actual_on_boot = get_actual() # sphinx.mo might be included + actual_on_boot = set(catalog_dir.rglob('*.mo')) # sphinx.mo might be included app.builder.compile_specific_catalogs([app.srcdir / 'admonitions.txt']) - actual = get_actual() - actual_on_boot - assert set(map(str, actual)) == {'admonitions.mo'} + actual = {str(x.relative_to(catalog_dir)) + for x in catalog_dir.rglob('*.mo') + if x not in actual_on_boot} + assert actual == {'admonitions.mo'} @pytest.mark.usefixtures('_setup_test') @@ -71,7 +68,7 @@ def test_compile_update_catalogs(app, status, warning): locale_dir = app.srcdir / 'locale' catalog_dir = locale_dir / app.config.language / 'LC_MESSAGES' - expect = {x.with_suffix('.mo') for x in find_files(catalog_dir, '.po')} - actual = set(find_files(catalog_dir, '.mo')) + expect = {x.with_suffix('.mo') for x in set(catalog_dir.rglob('*.po'))} + actual = set(catalog_dir.rglob('*.mo')) assert actual # not empty assert actual == expect From 83f849880d7c6d935d28058ed8b830f9f8e77947 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:44:31 +0100 Subject: [PATCH 07/16] Updates --- sphinx/util/osutil.py | 7 +++---- tests/conftest.py | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index ae5af5a0117..b4adb56009a 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -11,15 +11,13 @@ import unicodedata from io import StringIO from os import path -from typing import TYPE_CHECKING, Any, Union +from typing import TYPE_CHECKING, Any from sphinx.deprecation import _deprecation_warning if TYPE_CHECKING: from collections.abc import Iterator -# StrPath = Union[str, os.PathLike[str]] - # SEP separates path elements in the canonical file names # # Define SEP as a manifest constant, not so much because we expect it to change @@ -116,7 +114,8 @@ def make_filename_from_project(project: str) -> str: return make_filename(project_suffix_re.sub('', project)).lower() -def relpath(path: str | os.PathLike[str], start: str | os.PathLike[str] | None = os.curdir) -> str: +def relpath(path: str | os.PathLike[str], + start: str | os.PathLike[str] | None = os.curdir) -> str: """Return a relative filepath to *path* either from the current directory or from an optional *start* directory. diff --git a/tests/conftest.py b/tests/conftest.py index fa3b1e65d6e..f0cc23e71a8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,3 @@ -import os -import shutil from pathlib import Path import docutils From 06fc13feb18ced24964ba35cbae58f873ea999e3 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 27 Jul 2023 23:59:49 +0100 Subject: [PATCH 08/16] Updates --- sphinx/application.py | 1 - sphinx/builders/_epub_base.py | 5 +---- sphinx/builders/changes.py | 2 +- sphinx/builders/gettext.py | 6 +++--- sphinx/builders/html/__init__.py | 6 +++--- sphinx/config.py | 2 +- sphinx/ext/autosummary/generate.py | 6 ++++-- sphinx/ext/imgmath.py | 3 ++- sphinx/project.py | 2 +- sphinx/util/fileutil.py | 9 ++++++--- sphinx/util/matching.py | 2 +- sphinx/util/osutil.py | 10 +++++----- sphinx/util/template.py | 21 ++++++++++++--------- 13 files changed, 40 insertions(+), 35 deletions(-) diff --git a/sphinx/application.py b/sphinx/application.py index 12b324634f9..44432f8a6e7 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -11,7 +11,6 @@ from collections import deque from io import StringIO from os import path -from os.path import abspath from pathlib import Path from typing import IO, TYPE_CHECKING, Any, Callable diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 6bfa03f981c..44aef75dacb 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -508,9 +508,6 @@ def build_content(self) -> None: metadata = self.content_metadata() # files - if not self.outdir.endswith(os.sep): - self.outdir += os.sep - olen = len(self.outdir) self.files: list[str] = [] self.ignored_files = ['.buildinfo', 'mimetype', 'content.opf', 'toc.ncx', 'META-INF/container.xml', @@ -522,7 +519,7 @@ def build_content(self) -> None: for root, dirs, files in os.walk(self.outdir): dirs.sort() for fn in sorted(files): - filename = path.join(root, fn)[olen:] + filename = path.basename(path.join(root, fn)) if filename in self.ignored_files: continue ext = path.splitext(filename)[-1] diff --git a/sphinx/builders/changes.py b/sphinx/builders/changes.py index 5c118bef933..b4356ec7ce0 100644 --- a/sphinx/builders/changes.py +++ b/sphinx/builders/changes.py @@ -34,7 +34,7 @@ def init(self) -> None: self.templates.init(self, self.theme) def get_outdated_docs(self) -> str: - return self.outdir + return str(self.outdir) typemap = { 'versionadded': 'added', diff --git a/sphinx/builders/gettext.py b/sphinx/builders/gettext.py index cdf5846f5eb..d0acdc77ac2 100644 --- a/sphinx/builders/gettext.py +++ b/sphinx/builders/gettext.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os from codecs import open from collections import defaultdict from datetime import datetime, timedelta, timezone, tzinfo @@ -29,6 +28,7 @@ from sphinx.util.template import SphinxRenderer if TYPE_CHECKING: + import os from collections.abc import Generator, Iterable logger = logging.getLogger(__name__) @@ -85,12 +85,12 @@ def __init__(self, source: str, line: int) -> None: class GettextRenderer(SphinxRenderer): def __init__( - self, template_path: str | os.PathLike[str] | None = None, + self, template_path: list[str | os.PathLike[str]] | None = None, outdir: str | os.PathLike[str] | None = None, ) -> None: self.outdir = outdir if template_path is None: - template_path = path.join(package_dir, 'templates', 'gettext') + template_path = [path.join(package_dir, 'templates', 'gettext')] super().__init__(template_path) def escape(s: str) -> str: diff --git a/sphinx/builders/html/__init__.py b/sphinx/builders/html/__init__.py index 223fcb153d5..832b9a91a3d 100644 --- a/sphinx/builders/html/__init__.py +++ b/sphinx/builders/html/__init__.py @@ -786,7 +786,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(self.srcdir, f) # type: ignore[arg-type] # copy downloadable files if self.env.dlfiles: @@ -1254,9 +1254,9 @@ def js_tag(js: JavaScript) -> str: context['js_tag'] = js_tag -def _file_checksum(outdir: str, filename: str) -> str: +def _file_checksum(outdir: str | os.PathLike[str], filename: str | os.PathLike[str]) -> str: # Don't generate checksums for HTTP URIs - if '://' in filename: + if '://' in str(filename): return '' try: # Ensure universal newline mode is used to avoid checksum differences diff --git a/sphinx/config.py b/sphinx/config.py index de6b7cf64e9..3d27f25e65a 100644 --- a/sphinx/config.py +++ b/sphinx/config.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os import time import traceback import types @@ -22,6 +21,7 @@ from sphinx.util.osutil import _chdir as chdir if TYPE_CHECKING: + import os from collections.abc import Generator, Iterator, Sequence from sphinx.application import Sphinx diff --git a/sphinx/ext/autosummary/generate.py b/sphinx/ext/autosummary/generate.py index e77a39b8fe8..34cfda14b49 100644 --- a/sphinx/ext/autosummary/generate.py +++ b/sphinx/ext/autosummary/generate.py @@ -424,8 +424,10 @@ def _get_modules( return public, items -def generate_autosummary_docs(sources: list[str], output_dir: str | None = None, - suffix: str = '.rst', base_path: str | None = None, +def generate_autosummary_docs(sources: list[str], + output_dir: str | os.PathLike[str] | None = None, + suffix: str = '.rst', + base_path: str | os.PathLike[str] | None = None, imported_members: bool = False, app: Any = None, overwrite: bool = True, encoding: str = 'utf-8') -> None: showed_sources = sorted(sources) diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index b69af8615d9..d5d748da9ca 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -3,6 +3,7 @@ from __future__ import annotations import base64 +import os import re import shutil import subprocess @@ -83,7 +84,7 @@ def write_svg_depth(filename: str, depth: int) -> None: def generate_latex_macro(image_format: str, math: str, config: Config, - confdir: str = '') -> str: + confdir: str | os.PathLike[str] = '') -> str: """Generate LaTeX macro.""" variables = { 'fontsize': config.imgmath_font_size, diff --git a/sphinx/project.py b/sphinx/project.py index aa557b129cb..97e5f50da54 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -69,7 +69,7 @@ def path2doc(self, filename: str | os.PathLike[str]) -> str | None: if str(filename).startswith(str(self.srcdir)): filename = relpath(filename, self.srcdir) for suffix in self.source_suffix: - if filename.endswith(suffix): + if os.path.splitext(filename)[1].endswith(suffix): filename = path_stabilize(filename) return filename[:-len(suffix)] diff --git a/sphinx/util/fileutil.py b/sphinx/util/fileutil.py index 5411cccf5b4..5dae51c2a81 100644 --- a/sphinx/util/fileutil.py +++ b/sphinx/util/fileutil.py @@ -15,7 +15,7 @@ from sphinx.util.template import BaseRenderer -def copy_asset_file(source: str | os.PathLike[str], destination: str, +def copy_asset_file(source: str | os.PathLike[str], destination: str | os.PathLike[str], context: dict | None = None, renderer: BaseRenderer | None = None) -> None: """Copy an asset file to destination. @@ -34,6 +34,8 @@ def copy_asset_file(source: str | os.PathLike[str], destination: str, if os.path.isdir(destination): # Use source filename if destination points a directory destination = os.path.join(destination, os.path.basename(source)) + else: + destination = str(destination) if os.path.splitext(source)[1].lower().endswith('_t') and context is not None: if renderer is None: @@ -49,7 +51,8 @@ def copy_asset_file(source: str | os.PathLike[str], destination: str, copyfile(source, destination) -def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda path: False, +def copy_asset(source: str | os.PathLike[str], destination: str | os.PathLike[str], + excluded: PathMatcher = lambda path: False, context: dict | None = None, renderer: BaseRenderer | None = None, onerror: Callable[[str, Exception], None] | None = None) -> None: """Copy asset files to destination recursively. @@ -77,7 +80,7 @@ def copy_asset(source: str, destination: str, excluded: PathMatcher = lambda pat return for root, dirs, files in os.walk(source, followlinks=True): - reldir = relative_path(source, root) + reldir = relative_path(source, root) # type: ignore[arg-type] for dir in dirs[:]: if excluded(posixpath.join(reldir, dir)): dirs.remove(dir) diff --git a/sphinx/util/matching.py b/sphinx/util/matching.py index 04422ed53ab..dd91905d709 100644 --- a/sphinx/util/matching.py +++ b/sphinx/util/matching.py @@ -110,7 +110,7 @@ def patfilter(names: Iterable[str], pat: str) -> list[str]: def get_matching_files( - dirname: str, + dirname: str | os.PathLike[str], include_patterns: Iterable[str] = ("**",), exclude_patterns: Iterable[str] = (), ) -> Iterator[str]: diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index b4adb56009a..81ac948e705 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -30,12 +30,12 @@ def os_path(canonicalpath: str) -> str: return canonicalpath.replace(SEP, path.sep) -def canon_path(nativepath: str) -> str: +def canon_path(nativepath: str | os.PathLike[str]) -> str: """Return path in OS-independent form""" - return nativepath.replace(path.sep, SEP) + return str(nativepath).replace(path.sep, SEP) -def path_stabilize(filepath: str) -> str: +def path_stabilize(filepath: str | os.PathLike[str]) -> str: "Normalize path separator and unicode string" new_path = canon_path(filepath) return unicodedata.normalize('NFC', new_path) @@ -82,14 +82,14 @@ def mtimes_of_files(dirnames: list[str], suffix: str) -> Iterator[float]: pass -def copytimes(source: str, dest: str) -> None: +def copytimes(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None: """Copy a file's modification times.""" st = os.stat(source) if hasattr(os, 'utime'): os.utime(dest, (st.st_atime, st.st_mtime)) -def copyfile(source: str, dest: str) -> None: +def copyfile(source: str | os.PathLike[str], dest: str | os.PathLike[str]) -> None: """Copy a file and its modification times, if possible. Note: ``copyfile`` skips copying if the file has not been changed""" diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 2bcfd28fc98..89b266cc39c 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -5,7 +5,7 @@ import os from functools import partial from os import path -from typing import Any, Callable +from typing import TYPE_CHECKING, Any, Callable from jinja2 import TemplateNotFound from jinja2.environment import Environment @@ -17,6 +17,8 @@ from sphinx.locale import get_translator from sphinx.util import rst, texescape +if TYPE_CHECKING: + from collections.abc import Sequence class BaseRenderer: def __init__(self, loader: BaseLoader | None = None) -> None: @@ -32,8 +34,8 @@ def render_string(self, source: str, context: dict[str, Any]) -> str: class FileRenderer(BaseRenderer): - def __init__(self, search_path: str | list[str]) -> None: - if isinstance(search_path, str): + def __init__(self, search_path: Sequence[str | os.PathLike[str]]) -> None: + if isinstance(search_path, (str, os.PathLike)): search_path = [search_path] else: # filter "None" paths @@ -50,7 +52,7 @@ def render_from_file(cls, filename: str, context: dict[str, Any]) -> str: class SphinxRenderer(FileRenderer): - def __init__(self, template_path: None | str | list[str] = None) -> None: + 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') super().__init__(template_path) @@ -62,10 +64,10 @@ def render_from_file(cls, filename: str, context: dict[str, Any]) -> str: class LaTeXRenderer(SphinxRenderer): def __init__( - self, template_path: str | None = None, latex_engine: str | None = None, + self, template_path: Sequence[str | os.PathLike[str]] | None = None, latex_engine: str | None = None, ) -> None: if template_path is None: - template_path = os.path.join(package_dir, 'templates', 'latex') + template_path = [os.path.join(package_dir, 'templates', 'latex')] super().__init__(template_path) # use texescape as escape filter @@ -86,7 +88,7 @@ def __init__( class ReSTRenderer(SphinxRenderer): def __init__( - self, template_path: None | str | list[str] = None, language: str | None = None, + self, template_path: Sequence[str | os.PathLike[str]] | None = None, language: str | None = None, ) -> None: super().__init__(template_path) @@ -102,8 +104,9 @@ def __init__( class SphinxTemplateLoader(BaseLoader): """A loader supporting template inheritance""" - def __init__(self, confdir: str, templates_paths: list[str], - system_templates_paths: list[str]) -> None: + def __init__(self, confdir: str | os.PathLike[str], + templates_paths: Sequence[str | os.PathLike[str]], + system_templates_paths: Sequence[str | os.PathLike[str]]) -> None: self.loaders = [] self.sysloaders = [] From 2271d4bb4a68037ad562f38c3c9fae73c6eeb419 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:00:41 +0100 Subject: [PATCH 09/16] Updates --- sphinx/ext/imgmath.py | 6 ++++-- sphinx/util/template.py | 10 ++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sphinx/ext/imgmath.py b/sphinx/ext/imgmath.py index d5d748da9ca..1d590e9ffb7 100644 --- a/sphinx/ext/imgmath.py +++ b/sphinx/ext/imgmath.py @@ -3,7 +3,6 @@ from __future__ import annotations import base64 -import os import re import shutil import subprocess @@ -11,7 +10,7 @@ from hashlib import sha1 from os import path from subprocess import CalledProcessError -from typing import Any +from typing import TYPE_CHECKING, Any from docutils import nodes from docutils.nodes import Element @@ -30,6 +29,9 @@ from sphinx.util.template import LaTeXRenderer from sphinx.writers.html import HTML5Translator +if TYPE_CHECKING: + import os + logger = logging.getLogger(__name__) templates_path = path.join(package_dir, 'templates', 'imgmath') diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 89b266cc39c..10d7f03cfb4 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -63,9 +63,8 @@ def render_from_file(cls, filename: str, context: dict[str, Any]) -> str: class LaTeXRenderer(SphinxRenderer): - def __init__( - self, template_path: Sequence[str | os.PathLike[str]] | None = None, latex_engine: str | None = None, - ) -> None: + def __init__(self, template_path: Sequence[str | os.PathLike[str]] | None = None, + latex_engine: str | None = None) -> None: if template_path is None: template_path = [os.path.join(package_dir, 'templates', 'latex')] super().__init__(template_path) @@ -87,9 +86,8 @@ def __init__( class ReSTRenderer(SphinxRenderer): - def __init__( - self, template_path: Sequence[str | os.PathLike[str]] | None = None, language: str | None = None, - ) -> None: + def __init__(self, template_path: Sequence[str | os.PathLike[str]] | None = None, + language: str | None = None) -> None: super().__init__(template_path) # add language to environment From d33b235dc1778f18aab000438e834b31ed39b8ca Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:00:55 +0100 Subject: [PATCH 10/16] whitespace --- sphinx/util/template.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sphinx/util/template.py b/sphinx/util/template.py index 10d7f03cfb4..25004e448b2 100644 --- a/sphinx/util/template.py +++ b/sphinx/util/template.py @@ -20,6 +20,7 @@ if TYPE_CHECKING: from collections.abc import Sequence + class BaseRenderer: def __init__(self, loader: BaseLoader | None = None) -> None: self.env = SandboxedEnvironment(loader=loader, extensions=['jinja2.ext.i18n']) From 8b3c6f8515e019d56a7358edb4e5d9589765b145 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:12:12 +0100 Subject: [PATCH 11/16] Deprecate ``sphinx.testing.path`` --- CHANGES | 5 +++++ doc/extdev/deprecated.rst | 5 +++++ sphinx/testing/path.py | 7 +++++++ 3 files changed, 17 insertions(+) diff --git a/CHANGES b/CHANGES index 023bb734403..5d2d67d54a8 100644 --- a/CHANGES +++ b/CHANGES @@ -14,10 +14,15 @@ Deprecated * #11512: Deprecate ``sphinx.util.md5`` and ``sphinx.util.sha1``. Use ``hashlib`` instead. +* #xxx: Deprecate ``sphinx.testing.path``. + Use ``os.path`` or ``pathlib`` instead. Features added -------------- +* #xxx: Support ``os.PathLike`` types and ``pathlib.Path`` objects + in many more places. + Bugs fixed ---------- diff --git a/doc/extdev/deprecated.rst b/doc/extdev/deprecated.rst index 1c73a478178..71bfad02e4d 100644 --- a/doc/extdev/deprecated.rst +++ b/doc/extdev/deprecated.rst @@ -22,6 +22,11 @@ The following is a list of deprecated interfaces. - Removed - Alternatives + * - ``sphinx.testing.path`` + - 7.2 + - 9.0 + - ``os.path`` or ``pathlib`` + * - ``sphinx.util.md5`` - 7.2 - 9.0 diff --git a/sphinx/testing/path.py b/sphinx/testing/path.py index 105b786d5cf..0476f2377f8 100644 --- a/sphinx/testing/path.py +++ b/sphinx/testing/path.py @@ -3,11 +3,18 @@ import os import shutil import sys +import warnings from typing import IO, TYPE_CHECKING, Any, Callable +from sphinx.deprecation import RemovedInSphinx90Warning + if TYPE_CHECKING: import builtins +warnings.warn("'sphinx.testing.path' is deprecated. " + "Use 'os.path' or 'pathlib' instead.", + RemovedInSphinx90Warning, stacklevel=2) + FILESYSTEMENCODING = sys.getfilesystemencoding() or sys.getdefaultencoding() From abc5c6f7f72b3b532805a34381a2cbfab1031120 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:27:34 +0100 Subject: [PATCH 12/16] Tests --- sphinx/builders/_epub_base.py | 4 ++-- sphinx/testing/fixtures.py | 2 -- sphinx/util/osutil.py | 4 +--- tests/test_application.py | 4 +--- tests/test_build_html.py | 16 ++++++++-------- tests/test_build_latex.py | 4 ++-- tests/test_config.py | 24 ++++++++++++------------ tests/test_ext_apidoc.py | 6 +++--- tests/test_util_inventory.py | 10 +++++----- 9 files changed, 34 insertions(+), 40 deletions(-) diff --git a/sphinx/builders/_epub_base.py b/sphinx/builders/_epub_base.py index 44aef75dacb..9765f79363c 100644 --- a/sphinx/builders/_epub_base.py +++ b/sphinx/builders/_epub_base.py @@ -21,7 +21,7 @@ from sphinx.util import logging from sphinx.util.display import status_iterator from sphinx.util.fileutil import copy_asset_file -from sphinx.util.osutil import copyfile, ensuredir +from sphinx.util.osutil import copyfile, ensuredir, relpath try: from PIL import Image @@ -519,7 +519,7 @@ def build_content(self) -> None: for root, dirs, files in os.walk(self.outdir): dirs.sort() for fn in sorted(files): - filename = path.basename(path.join(root, fn)) + filename = relpath(path.join(root, fn), self.outdir) if filename in self.ignored_files: continue ext = path.splitext(filename)[-1] diff --git a/sphinx/testing/fixtures.py b/sphinx/testing/fixtures.py index f6ac053ae6b..d8335fd6aae 100644 --- a/sphinx/testing/fixtures.py +++ b/sphinx/testing/fixtures.py @@ -170,8 +170,6 @@ def make_app(test_params: dict, monkeypatch: Any) -> Generator[Callable, None, N if you want to initialize 'app' in your test function. please use this instead of using SphinxTestApp class directory. """ - monkeypatch.setattr('sphinx.application.abspath', lambda x: x) - apps = [] syspath = sys.path[:] diff --git a/sphinx/util/osutil.py b/sphinx/util/osutil.py index 81ac948e705..a2c15c34ca5 100644 --- a/sphinx/util/osutil.py +++ b/sphinx/util/osutil.py @@ -66,9 +66,7 @@ def relative_uri(base: str, to: str) -> str: def ensuredir(file: str | os.PathLike[str]) -> None: """Ensure that a path exists.""" - file = path.normpath(file) - if not path.exists(file): - os.makedirs(file) + os.makedirs(file, exist_ok=True) def mtimes_of_files(dirnames: list[str], suffix: str) -> Iterator[float]: diff --git a/tests/test_application.py b/tests/test_application.py index a7a4e7093c6..b5d12ef3675 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -24,13 +24,11 @@ def test_instantiation(tmp_path_factory, rootdir: str, monkeypatch): if rootdir and not src_dir.exists(): shutil.copytree(Path(str(rootdir)) / 'test-root', src_dir) - monkeypatch.setattr('sphinx.application.abspath', lambda x: x) - syspath = sys.path[:] # When app_ = SphinxTestApp( - srcdir=path(src_dir), + srcdir=src_dir, status=StringIO(), warning=StringIO(), ) diff --git a/tests/test_build_html.py b/tests/test_build_html.py index 4e61c73007e..9a6326c69f5 100644 --- a/tests/test_build_html.py +++ b/tests/test_build_html.py @@ -123,15 +123,15 @@ def test_html_warnings(app, warning): '--- Got:\n' + html_warnings -def test_html4_error(make_app, tempdir): - (tempdir / 'conf.py').write_text('', encoding='utf-8') +def test_html4_error(make_app, tmp_path): + (tmp_path / 'conf.py').write_text('', encoding='utf-8') with pytest.raises( ConfigError, match=r'HTML 4 is no longer supported by Sphinx', ): make_app( buildername='html', - srcdir=tempdir, + srcdir=tmp_path, confoverrides={'html4_writer': True}, ) @@ -1371,7 +1371,7 @@ def test_html_remote_images(app, status, warning): def test_html_encoded_image(app, status, warning): app.builder.build_all() - result = (app.outdir / 'index.html').read_text() + result = (app.outdir / 'index.html').read_text(encoding='utf8') assert ('_images/img_%231.png' in result) assert (app.outdir / '_images/img_#1.png').exists() @@ -1691,7 +1691,7 @@ def test_html_signaturereturn_icon(app): @pytest.mark.sphinx('html', testroot='reST-code-role') def test_html_code_role(app): app.build() - content = (app.outdir / 'index.html').read_text() + content = (app.outdir / 'index.html').read_text(encoding='utf8') common_content = ( 'def foo' @@ -1716,7 +1716,7 @@ def test_html_code_role(app): confoverrides={'option_emphasise_placeholders': True}) def test_option_emphasise_placeholders(app, status, warning): app.build() - content = (app.outdir / 'objects.html').read_text() + content = (app.outdir / 'objects.html').read_text(encoding='utf8') assert 'TYPE' in content assert '{TYPE}' not in content assert ('WHERE' @@ -1730,7 +1730,7 @@ def test_option_emphasise_placeholders(app, status, warning): @pytest.mark.sphinx('html', testroot='root') def test_option_emphasise_placeholders_default(app, status, warning): app.build() - content = (app.outdir / 'objects.html').read_text() + content = (app.outdir / 'objects.html').read_text(encoding='utf8') assert '={TYPE}' in content assert '={WHERE}-{COUNT}' in content assert '{client_name}' in content @@ -1742,7 +1742,7 @@ def test_option_emphasise_placeholders_default(app, status, warning): @pytest.mark.sphinx('html', testroot='root') def test_option_reference_with_value(app, status, warning): app.build() - content = (app.outdir / 'objects.html').read_text() + content = (app.outdir / 'objects.html').read_text(encoding='utf-8') assert ('-mapi' '' in content diff --git a/tests/test_build_latex.py b/tests/test_build_latex.py index 10b0ccbe63f..9257a4413f9 100644 --- a/tests/test_build_latex.py +++ b/tests/test_build_latex.py @@ -1671,7 +1671,7 @@ def test_latex_container(app, status, warning): @pytest.mark.sphinx('latex', testroot='reST-code-role') def test_latex_code_role(app): app.build() - content = (app.outdir / 'python.tex').read_text() + content = (app.outdir / 'python.tex').read_text(encoding='utf8') common_content = ( r'\PYG{k}{def} ' @@ -1715,7 +1715,7 @@ def test_copy_images(app, status, warning): @pytest.mark.sphinx('latex', testroot='latex-labels-before-module') def test_duplicated_labels_before_module(app, status, warning): app.build() - content: str = (app.outdir / 'python.tex').read_text() + content: str = (app.outdir / 'python.tex').read_text(encoding='utf8') def count_label(name): text = r'\phantomsection\label{\detokenize{%s}}' % name diff --git a/tests/test_config.py b/tests/test_config.py index def3dcea7fa..b1f700737a5 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -384,14 +384,14 @@ def test_nitpick_ignore_regex_fullmatch(app, status, warning): assert expected in actual -def test_conf_py_language_none(tempdir): +def test_conf_py_language_none(tmp_path): """Regression test for #10474.""" # Given a conf.py file with language = None - (tempdir / 'conf.py').write_text("language = None", encoding='utf-8') + (tmp_path / 'conf.py').write_text("language = None", encoding='utf-8') # When we load conf.py into a Config object - cfg = Config.read(tempdir, {}, None) + cfg = Config.read(tmp_path, {}, None) cfg.init_values() # Then the language is coerced to English @@ -399,14 +399,14 @@ def test_conf_py_language_none(tempdir): @mock.patch("sphinx.config.logger") -def test_conf_py_language_none_warning(logger, tempdir): +def test_conf_py_language_none_warning(logger, tmp_path): """Regression test for #10474.""" # Given a conf.py file with language = None - (tempdir / 'conf.py').write_text("language = None", encoding='utf-8') + (tmp_path / 'conf.py').write_text("language = None", encoding='utf-8') # When we load conf.py into a Config object - Config.read(tempdir, {}, None) + Config.read(tmp_path, {}, None) # Then a warning is raised assert logger.warning.called @@ -416,28 +416,28 @@ def test_conf_py_language_none_warning(logger, tempdir): "Falling back to 'en' (English).") -def test_conf_py_no_language(tempdir): +def test_conf_py_no_language(tmp_path): """Regression test for #10474.""" # Given a conf.py file with no language attribute - (tempdir / 'conf.py').write_text("", encoding='utf-8') + (tmp_path / 'conf.py').write_text("", encoding='utf-8') # When we load conf.py into a Config object - cfg = Config.read(tempdir, {}, None) + cfg = Config.read(tmp_path, {}, None) cfg.init_values() # Then the language is coerced to English assert cfg.language == "en" -def test_conf_py_nitpick_ignore_list(tempdir): +def test_conf_py_nitpick_ignore_list(tmp_path): """Regression test for #11355.""" # Given a conf.py file with no language attribute - (tempdir / 'conf.py').write_text("", encoding='utf-8') + (tmp_path / 'conf.py').write_text("", encoding='utf-8') # When we load conf.py into a Config object - cfg = Config.read(tempdir, {}, None) + cfg = Config.read(tmp_path, {}, None) cfg.init_values() # Then the default nitpick_ignore[_regex] is an empty list diff --git a/tests/test_ext_apidoc.py b/tests/test_ext_apidoc.py index bf21a5fbddb..0c1f3be046b 100644 --- a/tests/test_ext_apidoc.py +++ b/tests/test_ext_apidoc.py @@ -636,7 +636,7 @@ def test_namespace_package_file(tmp_path): " :show-inheritance:\n") -def test_no_duplicates(rootdir, tempdir): +def test_no_duplicates(rootdir, tmp_path): """Make sure that a ".pyx" and ".so" don't cause duplicate listings. We can't use pytest.mark.apidoc here as we use a different set of arguments @@ -649,8 +649,8 @@ def test_no_duplicates(rootdir, tempdir): sphinx.ext.apidoc.PY_SUFFIXES += ('.so',) package = rootdir / 'test-apidoc-duplicates' / 'fish_licence' - outdir = tempdir / 'out' - apidoc_main(['-o', outdir, "-T", package, "--implicit-namespaces"]) + outdir = tmp_path / 'out' + apidoc_main(['-o', str(outdir), "-T", str(package), "--implicit-namespaces"]) # Ensure the module has been documented assert (outdir / 'fish_licence.rst').isfile() diff --git a/tests/test_util_inventory.py b/tests/test_util_inventory.py index 2833d979e52..2e25f77abea 100644 --- a/tests/test_util_inventory.py +++ b/tests/test_util_inventory.py @@ -1,5 +1,5 @@ """Test inventory util functions.""" - +import os import posixpath import zlib from io import BytesIO @@ -88,7 +88,7 @@ def test_read_inventory_v2_not_having_version(): def _write_appconfig(dir, language, prefix=None): prefix = prefix or language - (dir / prefix).makedirs() + os.makedirs(dir / prefix, exist_ok=True) (dir / prefix / 'conf.py').write_text(f'language = "{language}"', encoding='utf8') (dir / prefix / 'index.rst').write_text('index.rst', encoding='utf8') assert sorted((dir / prefix).listdir()) == ['conf.py', 'index.rst'] @@ -103,13 +103,13 @@ def _build_inventory(srcdir): return (app.outdir / 'objects.inv') -def test_inventory_localization(tempdir): +def test_inventory_localization(tmp_path): # Build an app using Estonian (EE) locale - srcdir_et = _write_appconfig(tempdir, "et") + srcdir_et = _write_appconfig(tmp_path, "et") inventory_et = _build_inventory(srcdir_et) # Build the same app using English (US) locale - srcdir_en = _write_appconfig(tempdir, "en") + srcdir_en = _write_appconfig(tmp_path, "en") inventory_en = _build_inventory(srcdir_en) # Ensure that the inventory contents differ From 8297afa06c7607a994b210dd53dfbe4328f999c1 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:31:02 +0100 Subject: [PATCH 13/16] Tests --- tests/test_ext_apidoc.py | 3 ++- tests/test_util_inventory.py | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/test_ext_apidoc.py b/tests/test_ext_apidoc.py index 0c1f3be046b..1e089a3b696 100644 --- a/tests/test_ext_apidoc.py +++ b/tests/test_ext_apidoc.py @@ -1,5 +1,6 @@ """Test the sphinx.apidoc module.""" +import os.path from collections import namedtuple import pytest @@ -653,7 +654,7 @@ def test_no_duplicates(rootdir, tmp_path): apidoc_main(['-o', str(outdir), "-T", str(package), "--implicit-namespaces"]) # Ensure the module has been documented - assert (outdir / 'fish_licence.rst').isfile() + assert os.path.isfile(outdir / 'fish_licence.rst') # Ensure the submodule only appears once text = (outdir / 'fish_licence.rst').read_text(encoding="utf-8") diff --git a/tests/test_util_inventory.py b/tests/test_util_inventory.py index 2e25f77abea..2c2076384af 100644 --- a/tests/test_util_inventory.py +++ b/tests/test_util_inventory.py @@ -91,9 +91,9 @@ def _write_appconfig(dir, language, prefix=None): os.makedirs(dir / prefix, exist_ok=True) (dir / prefix / 'conf.py').write_text(f'language = "{language}"', encoding='utf8') (dir / prefix / 'index.rst').write_text('index.rst', encoding='utf8') - assert sorted((dir / prefix).listdir()) == ['conf.py', 'index.rst'] + assert sorted(os.listdir(dir / prefix)) == ['conf.py', 'index.rst'] assert (dir / prefix / 'index.rst').exists() - return (dir / prefix) + return dir / prefix def _build_inventory(srcdir): From e58c68e3b9d5e1449dc795bf7b677eaeeedfc315 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:31:50 +0100 Subject: [PATCH 14/16] Tests --- sphinx/project.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/project.py b/sphinx/project.py index 97e5f50da54..b75c684db7f 100644 --- a/sphinx/project.py +++ b/sphinx/project.py @@ -69,7 +69,7 @@ def path2doc(self, filename: str | os.PathLike[str]) -> str | None: if str(filename).startswith(str(self.srcdir)): filename = relpath(filename, self.srcdir) for suffix in self.source_suffix: - if os.path.splitext(filename)[1].endswith(suffix): + if str(filename).endswith(suffix): filename = path_stabilize(filename) return filename[:-len(suffix)] From 95a9b85bf2b3e0b26c1c64c119e7336f078c7f8e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:32:19 +0100 Subject: [PATCH 15/16] Imports --- tests/test_application.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_application.py b/tests/test_application.py index b5d12ef3675..a0fe268fe46 100644 --- a/tests/test_application.py +++ b/tests/test_application.py @@ -11,7 +11,6 @@ import sphinx.application from sphinx.errors import ExtensionError -from sphinx.testing.path import path from sphinx.testing.util import SphinxTestApp, strip_escseq from sphinx.util import logging From d369d3862d70c6ff946449c7bf8162a03f0913ac Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Fri, 28 Jul 2023 00:33:58 +0100 Subject: [PATCH 16/16] Update PR number --- CHANGES | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 5d2d67d54a8..e74ec0e63e9 100644 --- a/CHANGES +++ b/CHANGES @@ -14,13 +14,13 @@ Deprecated * #11512: Deprecate ``sphinx.util.md5`` and ``sphinx.util.sha1``. Use ``hashlib`` instead. -* #xxx: Deprecate ``sphinx.testing.path``. +* #11526: Deprecate ``sphinx.testing.path``. Use ``os.path`` or ``pathlib`` instead. Features added -------------- -* #xxx: Support ``os.PathLike`` types and ``pathlib.Path`` objects +* #11526: Support ``os.PathLike`` types and ``pathlib.Path`` objects in many more places. Bugs fixed