From b297eda38bd7e9a781bbd1dbc54774f23b18c813 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 20 Jul 2024 17:45:32 +0100 Subject: [PATCH] Remove ``_StrPath`` --- CHANGES.rst | 4 + sphinx/application.py | 10 +-- sphinx/util/_pathlib.py | 120 ------------------------- tests/test_builders/test_build_html.py | 7 +- 4 files changed, 11 insertions(+), 130 deletions(-) delete mode 100644 sphinx/util/_pathlib.py diff --git a/CHANGES.rst b/CHANGES.rst index b3991f8cc3e..7a2b32dc10b 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -68,6 +68,10 @@ Incompatible changes * #12096: Do not overwrite user-supplied files when copying assets unless forced with ``force=True``. Patch by Adam Turner. +* #12650: Remove support for string methods on :py:class:`~pathlib.Path` objects. + Use :py:func:`os.fspath` to convert :py:class:`~pathlib.Path` objects to strings, + or :py:class:`~pathlib.Path`'s methods to work with path objects. + Patch by Adam Turner. Deprecated ---------- diff --git a/sphinx/application.py b/sphinx/application.py index a1589fb230c..70ec427c209 100644 --- a/sphinx/application.py +++ b/sphinx/application.py @@ -13,6 +13,7 @@ from collections.abc import Callable, Collection, Sequence # NoQA: TCH003 from io import StringIO from os import path +from pathlib import Path from typing import IO, TYPE_CHECKING, Any, Literal from docutils.nodes import TextElement # NoQA: TCH002 @@ -31,7 +32,6 @@ from sphinx.project import Project from sphinx.registry import SphinxComponentRegistry from sphinx.util import docutils, logging -from sphinx.util._pathlib import _StrPath from sphinx.util.build_phase import BuildPhase from sphinx.util.console import bold from sphinx.util.display import progress_message @@ -173,9 +173,9 @@ def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[st self.registry = SphinxComponentRegistry() # validate provided directories - self.srcdir = _StrPath(srcdir).resolve() - self.outdir = _StrPath(outdir).resolve() - self.doctreedir = _StrPath(doctreedir).resolve() + 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)') % @@ -231,7 +231,7 @@ def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[st self.confdir = self.srcdir self.config = Config({}, confoverrides or {}) else: - self.confdir = _StrPath(confdir).resolve() + self.confdir = Path(confdir).resolve() self.config = Config.read(self.confdir, confoverrides or {}, self.tags) # set up translation infrastructure diff --git a/sphinx/util/_pathlib.py b/sphinx/util/_pathlib.py deleted file mode 100644 index 628e649c7f9..00000000000 --- a/sphinx/util/_pathlib.py +++ /dev/null @@ -1,120 +0,0 @@ -"""What follows is awful and will be gone in Sphinx 8""" - -from __future__ import annotations - -import sys -import warnings -from pathlib import Path, PosixPath, PurePath, WindowsPath -from typing import Any - -from sphinx.deprecation import RemovedInSphinx80Warning - -_STR_METHODS = frozenset(str.__dict__) -_PATH_NAME = Path().__class__.__name__ - -_MSG = ( - 'Sphinx 8 will drop support for representing paths as strings. ' - 'Use "pathlib.Path" or "os.fspath" instead.' -) - -# https://docs.python.org/3/library/stdtypes.html#typesseq-common -# https://docs.python.org/3/library/stdtypes.html#string-methods - -if sys.platform == 'win32': - class _StrPath(WindowsPath): - def replace( # type: ignore[override] - self, old: str, new: str, count: int = -1, /, - ) -> str: - # replace exists in both Path and str; - # in Path it makes filesystem changes, so we use the safer str version - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return self.__str__().replace(old, new, count) # NoQA: PLC2801 - - def __getattr__(self, item: str) -> Any: - if item in _STR_METHODS: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return getattr(self.__str__(), item) - msg = f'{_PATH_NAME!r} has no attribute {item!r}' - raise AttributeError(msg) - - def __add__(self, other: str) -> str: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return self.__str__() + other - - def __bool__(self) -> bool: - if not self.__str__(): - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return False - return True - - def __contains__(self, item: str) -> bool: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return item in self.__str__() - - def __eq__(self, other: object) -> bool: - if isinstance(other, PurePath): - return super().__eq__(other) - if isinstance(other, str): - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return self.__str__() == other - return NotImplemented - - def __hash__(self) -> int: - return super().__hash__() - - def __getitem__(self, item: int | slice) -> str: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return self.__str__()[item] - - def __len__(self) -> int: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return len(self.__str__()) -else: - class _StrPath(PosixPath): - def replace( # type: ignore[override] - self, old: str, new: str, count: int = -1, /, - ) -> str: - # replace exists in both Path and str; - # in Path it makes filesystem changes, so we use the safer str version - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return self.__str__().replace(old, new, count) # NoQA: PLC2801 - - def __getattr__(self, item: str) -> Any: - if item in _STR_METHODS: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return getattr(self.__str__(), item) - msg = f'{_PATH_NAME!r} has no attribute {item!r}' - raise AttributeError(msg) - - def __add__(self, other: str) -> str: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return self.__str__() + other - - def __bool__(self) -> bool: - if not self.__str__(): - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return False - return True - - def __contains__(self, item: str) -> bool: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return item in self.__str__() - - def __eq__(self, other: object) -> bool: - if isinstance(other, PurePath): - return super().__eq__(other) - if isinstance(other, str): - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return self.__str__() == other - return NotImplemented - - def __hash__(self) -> int: - return super().__hash__() - - def __getitem__(self, item: int | slice) -> str: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return self.__str__()[item] - - def __len__(self) -> int: - warnings.warn(_MSG, RemovedInSphinx80Warning, stacklevel=2) - return len(self.__str__()) diff --git a/tests/test_builders/test_build_html.py b/tests/test_builders/test_build_html.py index 3efdbf616e4..b8ea36b4f2e 100644 --- a/tests/test_builders/test_build_html.py +++ b/tests/test_builders/test_build_html.py @@ -7,7 +7,6 @@ import pytest from sphinx.builders.html import validate_html_extra_path, validate_html_static_path -from sphinx.deprecation import RemovedInSphinx80Warning from sphinx.errors import ConfigError from sphinx.util.console import strip_colors from sphinx.util.inventory import InventoryFile @@ -324,8 +323,7 @@ def test_validate_html_extra_path(app): app.outdir, # outdir app.outdir / '_static', # inside outdir ] - with pytest.warns(RemovedInSphinx80Warning, match='Use "pathlib.Path" or "os.fspath" instead'): - validate_html_extra_path(app, app.config) + validate_html_extra_path(app, app.config) assert app.config.html_extra_path == ['_static'] @@ -338,8 +336,7 @@ def test_validate_html_static_path(app): app.outdir, # outdir app.outdir / '_static', # inside outdir ] - with pytest.warns(RemovedInSphinx80Warning, match='Use "pathlib.Path" or "os.fspath" instead'): - validate_html_static_path(app, app.config) + validate_html_static_path(app, app.config) assert app.config.html_static_path == ['_static']