GH-128520: Subclass abc.ABC in pathlib._abc (#128745)

Convert `JoinablePath`, `ReadablePath` and `WritablePath` to real ABCs
derived from `abc.ABC`.

Make `JoinablePath.parser` abstract, rather than defaulting to `posixpath`.

Register `PurePath` and `Path` as virtual subclasses of the ABCs rather
than deriving. This avoids a hit to path object instantiation performance.

No change of behaviour in the public (non-abstract) classes.
This commit is contained in:
Barney Gale 2025-02-16 00:37:26 +00:00 committed by GitHub
parent 359c7dde3b
commit a7d41a8947
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 125 additions and 49 deletions

View file

@ -31,29 +31,11 @@ def needs_windows(fn):
#
class JoinablePathTest(unittest.TestCase):
cls = JoinablePath
def test_magic_methods(self):
P = self.cls
self.assertFalse(hasattr(P, '__fspath__'))
self.assertFalse(hasattr(P, '__bytes__'))
self.assertIs(P.__reduce__, object.__reduce__)
self.assertIs(P.__repr__, object.__repr__)
self.assertIs(P.__hash__, object.__hash__)
self.assertIs(P.__eq__, object.__eq__)
self.assertIs(P.__lt__, object.__lt__)
self.assertIs(P.__le__, object.__le__)
self.assertIs(P.__gt__, object.__gt__)
self.assertIs(P.__ge__, object.__ge__)
def test_parser(self):
self.assertIs(self.cls.parser, posixpath)
class DummyJoinablePath(JoinablePath):
__slots__ = ('_segments',)
parser = posixpath
def __init__(self, *segments):
self._segments = segments
@ -77,7 +59,7 @@ def with_segments(self, *pathsegments):
return type(self)(*pathsegments)
class DummyJoinablePathTest(unittest.TestCase):
class JoinablePathTest(unittest.TestCase):
cls = DummyJoinablePath
# Use a base path that's unrelated to any real filesystem path.
@ -94,6 +76,10 @@ def setUp(self):
self.sep = self.parser.sep
self.altsep = self.parser.altsep
def test_is_joinable(self):
p = self.cls(self.base)
self.assertIsInstance(p, JoinablePath)
def test_parser(self):
self.assertIsInstance(self.cls.parser, _PathParser)
@ -878,6 +864,7 @@ class DummyReadablePath(ReadablePath, DummyJoinablePath):
_files = {}
_directories = {}
parser = posixpath
def __init__(self, *segments):
super().__init__(*segments)
@ -909,6 +896,9 @@ def iterdir(self):
else:
raise FileNotFoundError(errno.ENOENT, "File not found", path)
def readlink(self):
raise NotImplementedError
class DummyWritablePath(WritablePath, DummyJoinablePath):
__slots__ = ()
@ -942,8 +932,11 @@ def mkdir(self, mode=0o777, parents=False, exist_ok=False):
self.parent.mkdir(parents=True, exist_ok=True)
self.mkdir(mode, parents=False, exist_ok=exist_ok)
def symlink_to(self, target, target_is_directory=False):
raise NotImplementedError
class DummyReadablePathTest(DummyJoinablePathTest):
class ReadablePathTest(JoinablePathTest):
"""Tests for ReadablePathTest methods that use stat(), open() and iterdir()."""
cls = DummyReadablePath
@ -1010,6 +1003,10 @@ def assertEqualNormCase(self, path_a, path_b):
normcase = self.parser.normcase
self.assertEqual(normcase(path_a), normcase(path_b))
def test_is_readable(self):
p = self.cls(self.base)
self.assertIsInstance(p, ReadablePath)
def test_exists(self):
P = self.cls
p = P(self.base)
@ -1378,15 +1375,19 @@ def test_is_symlink(self):
self.assertIs((P / 'linkA\x00').is_file(), False)
class DummyWritablePathTest(DummyJoinablePathTest):
class WritablePathTest(JoinablePathTest):
cls = DummyWritablePath
def test_is_writable(self):
p = self.cls(self.base)
self.assertIsInstance(p, WritablePath)
class DummyRWPath(DummyWritablePath, DummyReadablePath):
__slots__ = ()
class DummyRWPathTest(DummyWritablePathTest, DummyReadablePathTest):
class RWPathTest(WritablePathTest, ReadablePathTest):
cls = DummyRWPath
can_symlink = False
@ -1598,9 +1599,9 @@ def test_copy_into_empty_name(self):
self.assertRaises(ValueError, source.copy_into, target_dir)
class DummyReadablePathWalkTest(unittest.TestCase):
class ReadablePathWalkTest(unittest.TestCase):
cls = DummyReadablePath
base = DummyReadablePathTest.base
base = ReadablePathTest.base
can_symlink = False
def setUp(self):