diff --git a/sphinx/directives/code.py b/sphinx/directives/code.py index e0df09d2b5f..b68f6e1a729 100644 --- a/sphinx/directives/code.py +++ b/sphinx/directives/code.py @@ -13,9 +13,12 @@ from sphinx.locale import __ from sphinx.util import logging from sphinx.util._lines import parse_line_num_spec +from sphinx.util._pathlib import _StrPath from sphinx.util.docutils import SphinxDirective if TYPE_CHECKING: + import os + from docutils.nodes import Element, Node from sphinx.application import Sphinx @@ -197,8 +200,10 @@ class LiteralIncludeReader: ('diff', 'end-at'), ] - def __init__(self, filename: str, options: dict[str, Any], config: Config) -> None: - self.filename = filename + def __init__( + self, filename: str | os.PathLike[str], options: dict[str, Any], config: Config + ) -> None: + self.filename = _StrPath(filename) self.options = options self.encoding = options.get('encoding', config.source_encoding) self.lineno_start = self.options.get('lineno-start', 1) @@ -212,8 +217,9 @@ def parse_options(self) -> None: raise ValueError(msg) def read_file( - self, filename: str, location: tuple[str, int] | None = None + self, filename: str | os.PathLike[str], location: tuple[str, int] | None = None ) -> list[str]: + filename = _StrPath(filename) try: with open(filename, encoding=self.encoding, errors='strict') as f: text = f.read() @@ -222,11 +228,11 @@ def read_file( return text.splitlines(True) except OSError as exc: - msg = __('Include file %r not found or reading it failed') % filename + msg = __("Include file '%s' not found or reading it failed") % filename raise OSError(msg) from exc except UnicodeError as exc: msg = __( - 'Encoding %r used for reading included file %r seems to ' + "Encoding %r used for reading included file '%s' seems to " 'be wrong, try giving an :encoding: option' ) % (self.encoding, filename) raise UnicodeError(msg) from exc diff --git a/sphinx/pycode/__init__.py b/sphinx/pycode/__init__.py index da078b2f0f0..4094d7a9b8b 100644 --- a/sphinx/pycode/__init__.py +++ b/sphinx/pycode/__init__.py @@ -2,13 +2,15 @@ from __future__ import annotations +import os import tokenize from importlib import import_module from os import path -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal from sphinx.errors import PycodeError from sphinx.pycode.parser import Parser +from sphinx.util._pathlib import _StrPath if TYPE_CHECKING: from inspect import Signature @@ -23,7 +25,7 @@ class ModuleAnalyzer: tags: dict[str, tuple[str, int, int]] # cache for analyzer objects -- caches both by module and file name - cache: dict[tuple[str, str], Any] = {} + cache: dict[tuple[Literal['file', 'module'], str | _StrPath], Any] = {} @staticmethod def get_module_source(modname: str) -> tuple[str | None, str | None]: @@ -81,8 +83,9 @@ def for_string( @classmethod def for_file( - cls: type[ModuleAnalyzer], filename: str, modname: str + cls: type[ModuleAnalyzer], filename: str | os.PathLike[str], modname: str ) -> ModuleAnalyzer: + filename = _StrPath(filename) if ('file', filename) in cls.cache: return cls.cache['file', filename] try: @@ -114,9 +117,11 @@ def for_module(cls: type[ModuleAnalyzer], modname: str) -> ModuleAnalyzer: cls.cache['module', modname] = obj return obj - def __init__(self, source: str, modname: str, srcname: str) -> None: + def __init__( + self, source: str, modname: str, srcname: str | os.PathLike[str] + ) -> None: self.modname = modname # name of the module - self.srcname = srcname # name of the source file + self.srcname = str(srcname) # name of the source file # cache the source code as well self.code = source diff --git a/tests/roots/test-apidoc-toc/mypackage/main.py b/tests/roots/test-apidoc-toc/mypackage/main.py index f532813a722..1f6d1376cbb 100755 --- a/tests/roots/test-apidoc-toc/mypackage/main.py +++ b/tests/roots/test-apidoc-toc/mypackage/main.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import os +from pathlib import Path import mod_resource import mod_something @@ -8,8 +8,6 @@ if __name__ == "__main__": print(f"Hello, world! -> something returns: {mod_something.something()}") - res_path = \ - os.path.join(os.path.dirname(mod_resource.__file__), 'resource.txt') - with open(res_path, encoding='utf-8') as f: - text = f.read() + res_path = Path(mod_resource.__file__).parent / 'resource.txt' + text = res_path.read_text(encoding='utf-8') print(f"From mod_resource:resource.txt -> {text}") diff --git a/tests/roots/test-theming/test_theme/__init__.py b/tests/roots/test-theming/test_theme/__init__.py index 13bdc4b2c13..e69de29bb2d 100644 --- a/tests/roots/test-theming/test_theme/__init__.py +++ b/tests/roots/test-theming/test_theme/__init__.py @@ -1,5 +0,0 @@ -import os - - -def get_path(): - return os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/test_builders/test_build_epub.py b/tests/test_builders/test_build_epub.py index d54903507a8..3c2be6323d4 100644 --- a/tests/test_builders/test_build_epub.py +++ b/tests/test_builders/test_build_epub.py @@ -450,8 +450,8 @@ def test_run_epubcheck(app): if not runnable(['java', '-version']): pytest.skip('Unable to run Java; skipping test') - epubcheck = os.environ.get('EPUBCHECK_PATH', '/usr/share/java/epubcheck.jar') - if not os.path.exists(epubcheck): + epubcheck = Path(os.environ.get('EPUBCHECK_PATH', '/usr/share/java/epubcheck.jar')) + if not epubcheck.exists(): pytest.skip('Could not find epubcheck; skipping test') try: diff --git a/tests/test_builders/test_build_gettext.py b/tests/test_builders/test_build_gettext.py index e9ed4afb90f..00d7f826ecc 100644 --- a/tests/test_builders/test_build_gettext.py +++ b/tests/test_builders/test_build_gettext.py @@ -1,10 +1,10 @@ """Test the build process with gettext builder with the test root.""" import gettext -import os import re import subprocess from contextlib import chdir +from pathlib import Path from subprocess import CalledProcessError import pytest @@ -94,7 +94,7 @@ def test_msgfmt(app): 'msgfmt', 'en_US.po', '-o', - os.path.join('en', 'LC_MESSAGES', 'test_root.mo'), + Path('en', 'LC_MESSAGES', 'test_root.mo'), ] subprocess.run(args, capture_output=True, check=True) except OSError: diff --git a/tests/test_command_line.py b/tests/test_command_line.py index 63fb9e036d3..e346784b5dd 100644 --- a/tests/test_command_line.py +++ b/tests/test_command_line.py @@ -1,7 +1,7 @@ from __future__ import annotations -import os.path import sys +from pathlib import Path from typing import Any import pytest @@ -52,8 +52,8 @@ EXPECTED_MAKE_MODE = { 'builder': 'html', 'sourcedir': 'source_dir', - 'outputdir': os.path.join('build_dir', 'html'), - 'doctreedir': os.path.join('build_dir', 'doctrees'), + 'outputdir': str(Path('build_dir', 'html')), + 'doctreedir': str(Path('build_dir', 'doctrees')), 'filenames': ['filename1', 'filename2'], 'freshenv': True, 'noconfig': True, diff --git a/tests/test_directives/test_directive_code.py b/tests/test_directives/test_directive_code.py index 65e16b805bd..dacf8a3b334 100644 --- a/tests/test_directives/test_directive_code.py +++ b/tests/test_directives/test_directive_code.py @@ -1,7 +1,5 @@ """Test the code-block directive.""" -import os.path - import pytest from docutils import nodes @@ -257,12 +255,13 @@ def test_LiteralIncludeReader_tabwidth_dedent(testroot): def test_LiteralIncludeReader_diff(testroot, literal_inc_path): - options = {'diff': testroot / 'literal-diff.inc'} + literal_diff_path = testroot / 'literal-diff.inc' + options = {'diff': literal_diff_path} reader = LiteralIncludeReader(literal_inc_path, options, DUMMY_CONFIG) content, lines = reader.read() assert content == ( - '--- ' + os.path.join(testroot, 'literal-diff.inc') + '\n' - '+++ ' + os.path.join(testroot, 'literal.inc') + '\n' + f'--- {literal_diff_path}\n' + f'+++ {literal_inc_path}\n' '@@ -6,8 +6,8 @@\n' ' pass\n' ' \n' diff --git a/tests/test_environment/test_environment.py b/tests/test_environment/test_environment.py index b711fd879c4..64487b8c3f6 100644 --- a/tests/test_environment/test_environment.py +++ b/tests/test_environment/test_environment.py @@ -1,6 +1,5 @@ """Test the BuildEnvironment class.""" -import os import shutil from pathlib import Path @@ -41,8 +40,8 @@ def test_config_status(make_app, app_params): # incremental build (config entry changed) app3 = make_app(*args, confoverrides={'root_doc': 'indexx'}, **kwargs) - fname = os.path.join(app3.srcdir, 'index.rst') - assert os.path.isfile(fname) + fname = app3.srcdir / 'index.rst' + assert fname.is_file() shutil.move(fname, fname[:-4] + 'x.rst') assert app3.env.config_status == CONFIG_CHANGED app3.build() diff --git a/tests/test_extensions/test_ext_apidoc.py b/tests/test_extensions/test_ext_apidoc.py index 6440da1f6ce..3886617c742 100644 --- a/tests/test_extensions/test_ext_apidoc.py +++ b/tests/test_extensions/test_ext_apidoc.py @@ -1,6 +1,5 @@ """Test the sphinx.apidoc module.""" -import os.path from collections import namedtuple from pathlib import Path @@ -732,7 +731,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 os.path.isfile(outdir / 'fish_licence.rst') + assert (outdir / 'fish_licence.rst').is_file() # Ensure the submodule only appears once text = (outdir / 'fish_licence.rst').read_text(encoding='utf-8') diff --git a/tests/test_extensions/test_ext_autodoc_configs.py b/tests/test_extensions/test_ext_autodoc_configs.py index 58e90eba42c..c3911b8533c 100644 --- a/tests/test_extensions/test_ext_autodoc_configs.py +++ b/tests/test_extensions/test_ext_autodoc_configs.py @@ -2,7 +2,9 @@ import platform import sys +from collections.abc import Iterator from contextlib import contextmanager +from pathlib import Path import pytest @@ -19,7 +21,7 @@ @contextmanager -def overwrite_file(path, content): +def overwrite_file(path: Path, content: str) -> Iterator[None]: current_content = path.read_bytes() if path.exists() else None try: path.write_text(content, encoding='utf-8') diff --git a/tests/test_extensions/test_ext_inheritance_diagram.py b/tests/test_extensions/test_ext_inheritance_diagram.py index a3e0b9238fa..2aa4ff99188 100644 --- a/tests/test_extensions/test_ext_inheritance_diagram.py +++ b/tests/test_extensions/test_ext_inheritance_diagram.py @@ -1,9 +1,9 @@ """Test sphinx.ext.inheritance_diagram extension.""" -import os import re import sys import zlib +from pathlib import Path import pytest @@ -26,7 +26,7 @@ def test_inheritance_diagram(app): def new_run(self): result = orig_run(self) node = result[0] - source = os.path.basename(node.document.current_source).replace('.rst', '') + source = Path(node.document.current_source).stem graphs[source] = node['graph'] return result diff --git a/tests/test_intl/test_intl.py b/tests/test_intl/test_intl.py index b53a07f75e3..077516b0a05 100644 --- a/tests/test_intl/test_intl.py +++ b/tests/test_intl/test_intl.py @@ -4,7 +4,6 @@ """ import os -import os.path import re import shutil import time diff --git a/tests/test_pycode/test_pycode.py b/tests/test_pycode/test_pycode.py index 87e5ca57dff..3a34d6f3a0f 100644 --- a/tests/test_pycode/test_pycode.py +++ b/tests/test_pycode/test_pycode.py @@ -1,7 +1,7 @@ """Test pycode.""" -import os import sys +from pathlib import Path import pytest @@ -9,7 +9,7 @@ from sphinx.errors import PycodeError from sphinx.pycode import ModuleAnalyzer -SPHINX_MODULE_PATH = os.path.splitext(sphinx.__file__)[0] + '.py' +SPHINX_MODULE_PATH = Path(sphinx.__file__).resolve().with_suffix('.py') def test_ModuleAnalyzer_get_module_source(): @@ -40,7 +40,7 @@ def test_ModuleAnalyzer_for_file(): def test_ModuleAnalyzer_for_module(rootdir): analyzer = ModuleAnalyzer.for_module('sphinx') assert analyzer.modname == 'sphinx' - assert analyzer.srcname in {SPHINX_MODULE_PATH, os.path.abspath(SPHINX_MODULE_PATH)} + assert analyzer.srcname == str(SPHINX_MODULE_PATH) saved_path = sys.path.copy() sys.path.insert(0, str(rootdir / 'test-pycode')) diff --git a/tests/test_quickstart.py b/tests/test_quickstart.py index 7225775d19b..07a61689575 100644 --- a/tests/test_quickstart.py +++ b/tests/test_quickstart.py @@ -3,7 +3,6 @@ import time from collections.abc import Callable from io import StringIO -from os import path from pathlib import Path from typing import Any @@ -260,10 +259,7 @@ def test_extensions(tmp_path): def test_exits_when_existing_confpy(monkeypatch): # 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 - - monkeypatch.setattr(path, 'isfile', mock_isfile) + monkeypatch.setattr('os.path.isfile', lambda path: True) qs.term_input = mock_input({ 'Please enter a new root path (or just Enter to exit)': '', diff --git a/tests/test_util/test_util.py b/tests/test_util/test_util.py index b36ad0ee93c..b04c30e7aa8 100644 --- a/tests/test_util/test_util.py +++ b/tests/test_util/test_util.py @@ -1,16 +1,12 @@ """Tests util functions.""" -import os -import tempfile - from sphinx.util.osutil import ensuredir -def test_ensuredir(): - with tempfile.TemporaryDirectory() as tmp_path: - # Does not raise an exception for an existing directory. - ensuredir(tmp_path) +def test_ensuredir(tmp_path): + # Does not raise an exception for an existing directory. + ensuredir(tmp_path) - path = os.path.join(tmp_path, 'a', 'b', 'c') - ensuredir(path) - assert os.path.isdir(path) + path = tmp_path / 'a' / 'b' / 'c' + ensuredir(path) + assert path.is_dir() diff --git a/tests/test_util/test_util_logging.py b/tests/test_util/test_util_logging.py index 9d3e5023aea..5a9e0be5ada 100644 --- a/tests/test_util/test_util_logging.py +++ b/tests/test_util/test_util_logging.py @@ -2,12 +2,12 @@ import codecs import os -import os.path +from pathlib import Path import pytest from docutils import nodes -from sphinx.util import logging, osutil +from sphinx.util import logging from sphinx.util.console import colorize, strip_colors from sphinx.util.logging import is_suppressed_warning, prefixed_warnings from sphinx.util.parallel import ParallelTasks @@ -360,15 +360,15 @@ def test_get_node_location_abspath(): # Ensure that node locations are reported as an absolute path, # even if the source attribute is a relative path. - relative_filename = os.path.join('relative', 'path.txt') - absolute_filename = osutil.abspath(relative_filename) + relative_filename = Path('relative', 'path.txt') + absolute_filename = relative_filename.resolve() n = nodes.Node() - n.source = relative_filename + n.source = str(relative_filename) location = logging.get_node_location(n) - assert location == absolute_filename + ':' + assert location == f'{absolute_filename}:' @pytest.mark.sphinx('html', testroot='root', confoverrides={'show_warning_types': True})