mirror of
https://github.com/python/cpython.git
synced 2025-10-17 12:18:23 +00:00
GH-127807: pathlib ABCs: remove PurePathBase._raw_paths
(#127883)
Remove the `PurePathBase` initializer, and make `with_segments()` and `__str__()` abstract. This allows us to drop the `_raw_paths` attribute, and also the `Parser.join()` protocol method.
This commit is contained in:
parent
2a66dd33df
commit
a959ea1b0a
5 changed files with 92 additions and 96 deletions
|
@ -44,49 +44,25 @@ class PurePathBase:
|
||||||
"""Base class for pure path objects.
|
"""Base class for pure path objects.
|
||||||
|
|
||||||
This class *does not* provide several magic methods that are defined in
|
This class *does not* provide several magic methods that are defined in
|
||||||
its subclass PurePath. They are: __fspath__, __bytes__, __reduce__,
|
its subclass PurePath. They are: __init__, __fspath__, __bytes__,
|
||||||
__hash__, __eq__, __lt__, __le__, __gt__, __ge__. Its initializer and path
|
__reduce__, __hash__, __eq__, __lt__, __le__, __gt__, __ge__.
|
||||||
joining methods accept only strings, not os.PathLike objects more broadly.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = ()
|
||||||
# The `_raw_paths` slot stores unjoined string paths. This is set in
|
|
||||||
# the `__init__()` method.
|
|
||||||
'_raw_paths',
|
|
||||||
)
|
|
||||||
parser = posixpath
|
parser = posixpath
|
||||||
_globber = PathGlobber
|
_globber = PathGlobber
|
||||||
|
|
||||||
def __init__(self, *args):
|
|
||||||
for arg in args:
|
|
||||||
if not isinstance(arg, str):
|
|
||||||
raise TypeError(
|
|
||||||
f"argument should be a str, not {type(arg).__name__!r}")
|
|
||||||
self._raw_paths = list(args)
|
|
||||||
|
|
||||||
def with_segments(self, *pathsegments):
|
def with_segments(self, *pathsegments):
|
||||||
"""Construct a new path object from any number of path-like objects.
|
"""Construct a new path object from any number of path-like objects.
|
||||||
Subclasses may override this method to customize how new path objects
|
Subclasses may override this method to customize how new path objects
|
||||||
are created from methods like `iterdir()`.
|
are created from methods like `iterdir()`.
|
||||||
"""
|
"""
|
||||||
return type(self)(*pathsegments)
|
raise NotImplementedError
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
"""Return the string representation of the path, suitable for
|
"""Return the string representation of the path, suitable for
|
||||||
passing to system calls."""
|
passing to system calls."""
|
||||||
paths = self._raw_paths
|
raise NotImplementedError
|
||||||
if len(paths) == 1:
|
|
||||||
return paths[0]
|
|
||||||
elif paths:
|
|
||||||
# Join path segments from the initializer.
|
|
||||||
path = self.parser.join(*paths)
|
|
||||||
# Cache the joined path.
|
|
||||||
paths.clear()
|
|
||||||
paths.append(path)
|
|
||||||
return path
|
|
||||||
else:
|
|
||||||
paths.append('')
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def as_posix(self):
|
def as_posix(self):
|
||||||
"""Return the string representation of the path with forward (/)
|
"""Return the string representation of the path with forward (/)
|
||||||
|
@ -234,17 +210,17 @@ class PurePathBase:
|
||||||
paths) or a totally different path (if one of the arguments is
|
paths) or a totally different path (if one of the arguments is
|
||||||
anchored).
|
anchored).
|
||||||
"""
|
"""
|
||||||
return self.with_segments(*self._raw_paths, *pathsegments)
|
return self.with_segments(str(self), *pathsegments)
|
||||||
|
|
||||||
def __truediv__(self, key):
|
def __truediv__(self, key):
|
||||||
try:
|
try:
|
||||||
return self.with_segments(*self._raw_paths, key)
|
return self.with_segments(str(self), key)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __rtruediv__(self, key):
|
def __rtruediv__(self, key):
|
||||||
try:
|
try:
|
||||||
return self.with_segments(key, *self._raw_paths)
|
return self.with_segments(key, str(self))
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
|
|
|
@ -77,6 +77,10 @@ class PurePath(PurePathBase):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
|
# The `_raw_paths` slot stores unjoined string paths. This is set in
|
||||||
|
# the `__init__()` method.
|
||||||
|
'_raw_paths',
|
||||||
|
|
||||||
# The `_drv`, `_root` and `_tail_cached` slots store parsed and
|
# The `_drv`, `_root` and `_tail_cached` slots store parsed and
|
||||||
# normalized parts of the path. They are set when any of the `drive`,
|
# normalized parts of the path. They are set when any of the `drive`,
|
||||||
# `root` or `_tail` properties are accessed for the first time. The
|
# `root` or `_tail` properties are accessed for the first time. The
|
||||||
|
@ -140,9 +144,15 @@ class PurePath(PurePathBase):
|
||||||
"object where __fspath__ returns a str, "
|
"object where __fspath__ returns a str, "
|
||||||
f"not {type(path).__name__!r}")
|
f"not {type(path).__name__!r}")
|
||||||
paths.append(path)
|
paths.append(path)
|
||||||
# Avoid calling super().__init__, as an optimisation
|
|
||||||
self._raw_paths = paths
|
self._raw_paths = paths
|
||||||
|
|
||||||
|
def with_segments(self, *pathsegments):
|
||||||
|
"""Construct a new path object from any number of path-like objects.
|
||||||
|
Subclasses may override this method to customize how new path objects
|
||||||
|
are created from methods like `iterdir()`.
|
||||||
|
"""
|
||||||
|
return type(self)(*pathsegments)
|
||||||
|
|
||||||
def joinpath(self, *pathsegments):
|
def joinpath(self, *pathsegments):
|
||||||
"""Combine this path with one or several arguments, and return a
|
"""Combine this path with one or several arguments, and return a
|
||||||
new path representing either a subpath (if all arguments are relative
|
new path representing either a subpath (if all arguments are relative
|
||||||
|
@ -304,14 +314,29 @@ class PurePath(PurePathBase):
|
||||||
parts.append('')
|
parts.append('')
|
||||||
return parts
|
return parts
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _raw_path(self):
|
||||||
|
paths = self._raw_paths
|
||||||
|
if len(paths) == 1:
|
||||||
|
return paths[0]
|
||||||
|
elif paths:
|
||||||
|
# Join path segments from the initializer.
|
||||||
|
path = self.parser.join(*paths)
|
||||||
|
# Cache the joined path.
|
||||||
|
paths.clear()
|
||||||
|
paths.append(path)
|
||||||
|
return path
|
||||||
|
else:
|
||||||
|
paths.append('')
|
||||||
|
return ''
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def drive(self):
|
def drive(self):
|
||||||
"""The drive prefix (letter or UNC path), if any."""
|
"""The drive prefix (letter or UNC path), if any."""
|
||||||
try:
|
try:
|
||||||
return self._drv
|
return self._drv
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raw_path = PurePathBase.__str__(self)
|
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
|
||||||
self._drv, self._root, self._tail_cached = self._parse_path(raw_path)
|
|
||||||
return self._drv
|
return self._drv
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -320,8 +345,7 @@ class PurePath(PurePathBase):
|
||||||
try:
|
try:
|
||||||
return self._root
|
return self._root
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raw_path = PurePathBase.__str__(self)
|
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
|
||||||
self._drv, self._root, self._tail_cached = self._parse_path(raw_path)
|
|
||||||
return self._root
|
return self._root
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -329,8 +353,7 @@ class PurePath(PurePathBase):
|
||||||
try:
|
try:
|
||||||
return self._tail_cached
|
return self._tail_cached
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raw_path = PurePathBase.__str__(self)
|
self._drv, self._root, self._tail_cached = self._parse_path(self._raw_path)
|
||||||
self._drv, self._root, self._tail_cached = self._parse_path(raw_path)
|
|
||||||
return self._tail_cached
|
return self._tail_cached
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
|
|
@ -14,7 +14,6 @@ class Parser(Protocol):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
sep: str
|
sep: str
|
||||||
def join(self, path: str, *paths: str) -> str: ...
|
|
||||||
def split(self, path: str) -> tuple[str, str]: ...
|
def split(self, path: str) -> tuple[str, str]: ...
|
||||||
def splitdrive(self, path: str) -> tuple[str, str]: ...
|
def splitdrive(self, path: str) -> tuple[str, str]: ...
|
||||||
def splitext(self, path: str) -> tuple[str, str]: ...
|
def splitext(self, path: str) -> tuple[str, str]: ...
|
||||||
|
|
|
@ -229,6 +229,31 @@ class PurePathTest(test_pathlib_abc.DummyPurePathTest):
|
||||||
self._check_str(p.__fspath__(), ('a/b',))
|
self._check_str(p.__fspath__(), ('a/b',))
|
||||||
self._check_str(os.fspath(p), ('a/b',))
|
self._check_str(os.fspath(p), ('a/b',))
|
||||||
|
|
||||||
|
def test_bytes(self):
|
||||||
|
P = self.cls
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P(b'a')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P(b'a', 'b')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P('a', b'b')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P('a').joinpath(b'b')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P('a') / b'b'
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
b'a' / P('b')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P('a').match(b'b')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P('a').relative_to(b'b')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P('a').with_name(b'b')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P('a').with_stem(b'b')
|
||||||
|
with self.assertRaises(TypeError):
|
||||||
|
P('a').with_suffix(b'b')
|
||||||
|
|
||||||
def test_bytes_exc_message(self):
|
def test_bytes_exc_message(self):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
message = (r"argument should be a str or an os\.PathLike object "
|
message = (r"argument should be a str or an os\.PathLike object "
|
||||||
|
|
|
@ -53,7 +53,15 @@ class PurePathBaseTest(unittest.TestCase):
|
||||||
|
|
||||||
|
|
||||||
class DummyPurePath(PurePathBase):
|
class DummyPurePath(PurePathBase):
|
||||||
__slots__ = ()
|
__slots__ = ('_segments',)
|
||||||
|
|
||||||
|
def __init__(self, *segments):
|
||||||
|
self._segments = segments
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self._segments:
|
||||||
|
return self.parser.join(*self._segments)
|
||||||
|
return ''
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, DummyPurePath):
|
if not isinstance(other, DummyPurePath):
|
||||||
|
@ -66,6 +74,9 @@ class DummyPurePath(PurePathBase):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
|
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
|
||||||
|
|
||||||
|
def with_segments(self, *pathsegments):
|
||||||
|
return type(self)(*pathsegments)
|
||||||
|
|
||||||
|
|
||||||
class DummyPurePathTest(unittest.TestCase):
|
class DummyPurePathTest(unittest.TestCase):
|
||||||
cls = DummyPurePath
|
cls = DummyPurePath
|
||||||
|
@ -97,30 +108,11 @@ class DummyPurePathTest(unittest.TestCase):
|
||||||
P('a/b/c')
|
P('a/b/c')
|
||||||
P('/a/b/c')
|
P('/a/b/c')
|
||||||
|
|
||||||
def test_bytes(self):
|
def test_fspath_common(self):
|
||||||
P = self.cls
|
self.assertRaises(TypeError, os.fspath, self.cls(''))
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P(b'a')
|
def test_as_bytes_common(self):
|
||||||
with self.assertRaises(TypeError):
|
self.assertRaises(TypeError, bytes, self.cls(''))
|
||||||
P(b'a', 'b')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P('a', b'b')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P('a').joinpath(b'b')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P('a') / b'b'
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
b'a' / P('b')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P('a').match(b'b')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P('a').relative_to(b'b')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P('a').with_name(b'b')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P('a').with_stem(b'b')
|
|
||||||
with self.assertRaises(TypeError):
|
|
||||||
P('a').with_suffix(b'b')
|
|
||||||
|
|
||||||
def _check_str_subclass(self, *args):
|
def _check_str_subclass(self, *args):
|
||||||
# Issue #21127: it should be possible to construct a PurePath object
|
# Issue #21127: it should be possible to construct a PurePath object
|
||||||
|
@ -1286,36 +1278,6 @@ class DummyPurePathTest(unittest.TestCase):
|
||||||
# Tests for the virtual classes.
|
# Tests for the virtual classes.
|
||||||
#
|
#
|
||||||
|
|
||||||
class PathBaseTest(PurePathBaseTest):
|
|
||||||
cls = PathBase
|
|
||||||
|
|
||||||
def test_not_implemented_error(self):
|
|
||||||
p = self.cls('')
|
|
||||||
e = NotImplementedError
|
|
||||||
self.assertRaises(e, p.stat)
|
|
||||||
self.assertRaises(e, p.exists)
|
|
||||||
self.assertRaises(e, p.is_dir)
|
|
||||||
self.assertRaises(e, p.is_file)
|
|
||||||
self.assertRaises(e, p.is_symlink)
|
|
||||||
self.assertRaises(e, p.open)
|
|
||||||
self.assertRaises(e, p.read_bytes)
|
|
||||||
self.assertRaises(e, p.read_text)
|
|
||||||
self.assertRaises(e, p.write_bytes, b'foo')
|
|
||||||
self.assertRaises(e, p.write_text, 'foo')
|
|
||||||
self.assertRaises(e, p.iterdir)
|
|
||||||
self.assertRaises(e, lambda: list(p.glob('*')))
|
|
||||||
self.assertRaises(e, lambda: list(p.rglob('*')))
|
|
||||||
self.assertRaises(e, lambda: list(p.walk()))
|
|
||||||
self.assertRaises(e, p.readlink)
|
|
||||||
self.assertRaises(e, p.symlink_to, 'foo')
|
|
||||||
self.assertRaises(e, p.mkdir)
|
|
||||||
|
|
||||||
def test_fspath_common(self):
|
|
||||||
self.assertRaises(TypeError, os.fspath, self.cls(''))
|
|
||||||
|
|
||||||
def test_as_bytes_common(self):
|
|
||||||
self.assertRaises(TypeError, bytes, self.cls(''))
|
|
||||||
|
|
||||||
|
|
||||||
class DummyPathIO(io.BytesIO):
|
class DummyPathIO(io.BytesIO):
|
||||||
"""
|
"""
|
||||||
|
@ -1342,11 +1304,19 @@ class DummyPath(PathBase):
|
||||||
Simple implementation of PathBase that keeps files and directories in
|
Simple implementation of PathBase that keeps files and directories in
|
||||||
memory.
|
memory.
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
__slots__ = ('_segments')
|
||||||
|
|
||||||
_files = {}
|
_files = {}
|
||||||
_directories = {}
|
_directories = {}
|
||||||
|
|
||||||
|
def __init__(self, *segments):
|
||||||
|
self._segments = segments
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
if self._segments:
|
||||||
|
return self.parser.join(*self._segments)
|
||||||
|
return ''
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, DummyPath):
|
if not isinstance(other, DummyPath):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
@ -1358,6 +1328,9 @@ class DummyPath(PathBase):
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
|
return "{}({!r})".format(self.__class__.__name__, self.as_posix())
|
||||||
|
|
||||||
|
def with_segments(self, *pathsegments):
|
||||||
|
return type(self)(*pathsegments)
|
||||||
|
|
||||||
def stat(self, *, follow_symlinks=True):
|
def stat(self, *, follow_symlinks=True):
|
||||||
path = str(self).rstrip('/')
|
path = str(self).rstrip('/')
|
||||||
if path in self._files:
|
if path in self._files:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue