From cba36a395e609bda423a71effe2115ca52d0b255 Mon Sep 17 00:00:00 2001 From: barneygale Date: Fri, 23 Jun 2023 18:37:43 +0100 Subject: [PATCH] GH-106037: Allow subclasses of `pathlib.PurePath` to opt into `os.PathLike` We made it possible to subclass `pathlib.PurePath` in a68e585, which landed in 3.12. However, user subclasses automatically inherit an `__fspath__()` method, which may not be appropriate. For example, a user subclass may implement a "virtual" filesystem to provide access to a `.zip` file or FTP server. But it would be highly surprising if `open(FTPPath(...))` attempted to open a *local* file. This patch makes the `os.PathLike` interface opt-in. In pathlib itself, we opt into the `os.PathLike` interface for `PurePosixPath`, `PureWindowsPath` and `Path`. As `PurePath` is not instantiable (you always get a `PurePosixPath` or `PureWindowsPath` object back), this is backwards compatible with 3.11, but not with earlier 3.12 betas. --- Lib/pathlib.py | 26 ++++++++++++------- Lib/test/test_pathlib.py | 9 +++++++ ...-06-23-18-23-18.gh-issue-106037.hLb-E3.rst | 3 +++ 3 files changed, 29 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-06-23-18-23-18.gh-issue-106037.hLb-E3.rst diff --git a/Lib/pathlib.py b/Lib/pathlib.py index a36ffdd73d8af1..b4a872e2e05feb 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -399,9 +399,6 @@ def __str__(self): self._tail) or '.' return self._str - def __fspath__(self): - return str(self) - def as_posix(self): """Return the string representation of the path with forward (/) slashes.""" @@ -411,7 +408,7 @@ def as_posix(self): def __bytes__(self): """Return the bytes representation of the path. This is only recommended to use under Unix.""" - return os.fsencode(self) + return os.fsencode(str(self)) def __repr__(self): return "{}({!r})".format(self.__class__.__name__, self.as_posix()) @@ -743,11 +740,6 @@ def match(self, path_pattern, *, case_sensitive=None): raise ValueError("empty pattern") -# Subclassing os.PathLike makes isinstance() checks slower, -# which in turn makes Path construction slower. Register instead! -os.PathLike.register(PurePath) - - class PurePosixPath(PurePath): """PurePath subclass for non-Windows systems. @@ -757,6 +749,8 @@ class PurePosixPath(PurePath): _flavour = posixpath __slots__ = () + def __fspath__(self): + return str(self) class PureWindowsPath(PurePath): """PurePath subclass for Windows systems. @@ -767,6 +761,13 @@ class PureWindowsPath(PurePath): _flavour = ntpath __slots__ = () + def __fspath__(self): + return str(self) + + +# These classes implement os.PathLike for backwards compatibility. +os.PathLike.register(PurePosixPath) +os.PathLike.register(PureWindowsPath) # Filesystem-accessing classes @@ -1174,6 +1175,9 @@ def __new__(cls, *args, **kwargs): cls = WindowsPath if os.name == 'nt' else PosixPath return object.__new__(cls) + def __fspath__(self): + return str(self) + @classmethod def cwd(cls): """Return a new path pointing to the current working directory.""" @@ -1398,6 +1402,10 @@ def expanduser(self): return self +# Subclassing os.PathLike makes isinstance() checks slower, +# which in turn makes Path construction slower. Register instead! +os.PathLike.register(Path) + class PosixPath(Path, PurePosixPath): """Path subclass for non-Windows systems. diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index f9356909cb0982..a3db944364994d 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -1545,6 +1545,15 @@ class cls(pathlib.PurePath): # repr() roundtripping is not supported in custom subclass. test_repr_roundtrips = None + # PurePath subclass is not automatically path-like + def test_fspath_common(self): + P = self.cls + p = P() + self.assertFalse(issubclass(P, os.PathLike)) + self.assertFalse(isinstance(p, os.PathLike)) + with self.assertRaises(TypeError): + os.fspath(p) + @only_posix class PosixPathAsPureTest(PurePosixPathTest): diff --git a/Misc/NEWS.d/next/Library/2023-06-23-18-23-18.gh-issue-106037.hLb-E3.rst b/Misc/NEWS.d/next/Library/2023-06-23-18-23-18.gh-issue-106037.hLb-E3.rst new file mode 100644 index 00000000000000..69b1a0335eaa95 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-23-18-23-18.gh-issue-106037.hLb-E3.rst @@ -0,0 +1,3 @@ +Fix misidentification of :class:`pathlib.PurePath` user subclasses as +:class:`os.PathLike`. Subclasses are free to define an +:meth:`~os.PathLike.__fspath__` method, but do not inherit one.