Skip to content

Commit

Permalink
Support string methods on path objects (#11619)
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner authored Aug 23, 2023
1 parent a73fb59 commit 6b17dd1
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 15 deletions.
4 changes: 4 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Bugs fixed
* Fixed a type error in ``SingleFileHTMLBuilder._get_local_toctree``,
``includehidden`` may be passed as a string or a boolean.
* Fix ``:noindex:`` for ``PyModule`` and JSModule``.
* Restore support string methods on path objects.
This is deprecated and will be removed in Sphinx 8.
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.

Release 7.2.1 (released Aug 17, 2023)
=====================================
Expand Down
10 changes: 5 additions & 5 deletions sphinx/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from collections.abc import Sequence # NoQA: TCH003
from io import StringIO
from os import path
from pathlib import Path
from typing import IO, TYPE_CHECKING, Any, Callable

from docutils.nodes import TextElement # NoQA: TCH002
Expand All @@ -32,6 +31,7 @@
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 # type: ignore[attr-defined]
from sphinx.util.display import progress_message
Expand Down Expand Up @@ -149,9 +149,9 @@ def __init__(self, srcdir: str | os.PathLike[str], confdir: str | os.PathLike[st
self.registry = SphinxComponentRegistry()

# validate provided directories
self.srcdir = Path(srcdir).resolve()
self.outdir = Path(outdir).resolve()
self.doctreedir = Path(doctreedir).resolve()
self.srcdir = _StrPath(srcdir).resolve()
self.outdir = _StrPath(outdir).resolve()
self.doctreedir = _StrPath(doctreedir).resolve()

if not path.isdir(self.srcdir):
raise ApplicationError(__('Cannot find source directory (%s)') %
Expand Down Expand Up @@ -207,7 +207,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 = Path(confdir).resolve()
self.confdir = _StrPath(confdir).resolve()
self.config = Config.read(self.confdir, confoverrides or {}, self.tags)

# initialize some limited config variables before initialize i18n and loading
Expand Down
4 changes: 2 additions & 2 deletions sphinx/builders/html/_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from sphinx.errors import ThemeError

if TYPE_CHECKING:
from pathlib import Path
from sphinx.util._pathlib import _StrPath


class _CascadingStyleSheet:
Expand Down Expand Up @@ -124,7 +124,7 @@ def __getitem__(self, key):
return os.fspath(self.filename)[key]


def _file_checksum(outdir: Path, filename: str | os.PathLike[str]) -> str:
def _file_checksum(outdir: _StrPath, filename: str | os.PathLike[str]) -> str:
filename = os.fspath(filename)
# Don't generate checksums for HTTP URIs
if '://' in filename:
Expand Down
6 changes: 3 additions & 3 deletions sphinx/environment/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@

if TYPE_CHECKING:
from collections.abc import Generator, Iterator
from pathlib import Path

from docutils import nodes
from docutils.nodes import Node
Expand All @@ -35,6 +34,7 @@
from sphinx.domains import Domain
from sphinx.events import EventManager
from sphinx.project import Project
from sphinx.util._pathlib import _StrPath

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -148,8 +148,8 @@ class BuildEnvironment:

def __init__(self, app: Sphinx):
self.app: Sphinx = app
self.doctreedir: Path = app.doctreedir
self.srcdir: Path = app.srcdir
self.doctreedir: _StrPath = app.doctreedir
self.srcdir: _StrPath = app.srcdir
self.config: Config = None # type: ignore[assignment]
self.config_status: int = CONFIG_UNSET
self.config_status_extra: str = ''
Expand Down
3 changes: 1 addition & 2 deletions sphinx/jinja2glue.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import annotations

import pathlib
from os import path
from pprint import pformat
from typing import TYPE_CHECKING, Any, Callable
Expand Down Expand Up @@ -122,7 +121,7 @@ class SphinxFileSystemLoader(FileSystemLoader):

def get_source(self, environment: Environment, template: str) -> tuple[str, str, Callable]:
for searchpath in self.searchpath:
filename = str(pathlib.Path(searchpath, template))
filename = path.join(searchpath, template)
f = open_if_exists(filename)
if f is not None:
break
Expand Down
7 changes: 4 additions & 3 deletions sphinx/testing/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@

if TYPE_CHECKING:
from io import StringIO
from pathlib import Path

from docutils.nodes import Node

from sphinx.util._pathlib import _StrPath

__all__ = 'SphinxTestApp', 'SphinxTestAppWrapperForSkipBuilding'


Expand Down Expand Up @@ -81,8 +82,8 @@ class SphinxTestApp(application.Sphinx):
def __init__(
self,
buildername: str = 'html',
srcdir: Path | None = None,
builddir: Path | None = None,
srcdir: _StrPath | None = None,
builddir: _StrPath | None = None,
freshenv: bool = False,
confoverrides: dict | None = None,
status: IO | None = None,
Expand Down
48 changes: 48 additions & 0 deletions sphinx/util/_pathlib.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""What follows is awful and will be gone in Sphinx 8"""

from __future__ import annotations

import sys
import warnings
from pathlib import Path, PosixPath, WindowsPath

from sphinx.deprecation import RemovedInSphinx80Warning

_STR_METHODS = frozenset(str.__dict__)
_PATH_NAME = Path().__class__.__name__


if sys.platform == 'win32':
class _StrPath(WindowsPath):
def replace(self, old, new, count=-1, /):
# replace exists in both Path and str;
# in Path it makes filesystem changes, so we use the safer str version
warnings.warn('Sphinx 8 will drop support for representing paths as strings. '
'Use "pathlib.Path" or "os.fspath" instead.',
RemovedInSphinx80Warning, stacklevel=2)
return str(self).replace(old, new, count)

def __getattr__(self, item):
if item in _STR_METHODS:
warnings.warn('Sphinx 8 will drop support for representing paths as strings. '
'Use "pathlib.Path" or "os.fspath" instead.',
RemovedInSphinx80Warning, stacklevel=2)
return getattr(str(self), item)
msg = f'{_PATH_NAME!r} has no attribute {item!r}'
raise AttributeError(msg)
else:
class _StrPath(PosixPath):
def replace(self, old, new, count=-1, /):
warnings.warn('Sphinx 8 will drop support for representing paths as strings. '
'Use "pathlib.Path" or "os.fspath" instead.',
RemovedInSphinx80Warning, stacklevel=2)
return str(self).replace(old, new, count)

def __getattr__(self, item):
if item in _STR_METHODS:
warnings.warn('Sphinx 8 will drop support for representing paths as strings. '
'Use "pathlib.Path" or "os.fspath" instead.',
RemovedInSphinx80Warning, stacklevel=2)
return getattr(str(self), item)
msg = f'{_PATH_NAME!r} has no attribute {item!r}'
raise AttributeError(msg)

0 comments on commit 6b17dd1

Please sign in to comment.