summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
authorBarney Gale <barney.gale@gmail.com>2024-12-09 18:31:22 (GMT)
committerGitHub <noreply@github.com>2024-12-09 18:31:22 (GMT)
commit5c89adf385aaaca97c2ee9074f8b1fda0f57ad26 (patch)
tree21fac62c4e6ca6f0e2e4223ca16b680921261760 /Lib
parente85f2f1703e0f79cfd0d0e3010190b71c0eb18da (diff)
downloadcpython-5c89adf385aaaca97c2ee9074f8b1fda0f57ad26.zip
cpython-5c89adf385aaaca97c2ee9074f8b1fda0f57ad26.tar.gz
cpython-5c89adf385aaaca97c2ee9074f8b1fda0f57ad26.tar.bz2
GH-127456: pathlib ABCs: add protocol for path parser (#127494)
Change the default value of `PurePathBase.parser` from `ParserBase()` to `posixpath`. As a result, user subclasses of `PurePathBase` and `PathBase` use POSIX path syntax by default, which is very often desirable. Move `pathlib._abc.ParserBase` to `pathlib._types.Parser`, and convert it to a runtime-checkable protocol. Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Diffstat (limited to 'Lib')
-rw-r--r--Lib/pathlib/_abc.py56
-rw-r--r--Lib/pathlib/_types.py22
-rw-r--r--Lib/test/test_pathlib/test_pathlib_abc.py61
3 files changed, 32 insertions, 107 deletions
diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py
index 309eab2..f68685f 100644
--- a/Lib/pathlib/_abc.py
+++ b/Lib/pathlib/_abc.py
@@ -13,6 +13,7 @@ resemble pathlib's PurePath and Path respectively.
import functools
import operator
+import posixpath
from errno import EINVAL
from glob import _GlobberBase, _no_recurse_symlinks
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
@@ -33,59 +34,6 @@ def _is_case_sensitive(parser):
return parser.normcase('Aa') == 'Aa'
-
-class ParserBase:
- """Base class for path parsers, which do low-level path manipulation.
-
- Path parsers provide a subset of the os.path API, specifically those
- functions needed to provide PurePathBase functionality. Each PurePathBase
- subclass references its path parser via a 'parser' class attribute.
-
- Every method in this base class raises an UnsupportedOperation exception.
- """
-
- @classmethod
- def _unsupported_msg(cls, attribute):
- return f"{cls.__name__}.{attribute} is unsupported"
-
- @property
- def sep(self):
- """The character used to separate path components."""
- raise UnsupportedOperation(self._unsupported_msg('sep'))
-
- def join(self, path, *paths):
- """Join path segments."""
- raise UnsupportedOperation(self._unsupported_msg('join()'))
-
- def split(self, path):
- """Split the path into a pair (head, tail), where *head* is everything
- before the final path separator, and *tail* is everything after.
- Either part may be empty.
- """
- raise UnsupportedOperation(self._unsupported_msg('split()'))
-
- def splitdrive(self, path):
- """Split the path into a 2-item tuple (drive, tail), where *drive* is
- a device name or mount point, and *tail* is everything after the
- drive. Either part may be empty."""
- raise UnsupportedOperation(self._unsupported_msg('splitdrive()'))
-
- def splitext(self, path):
- """Split the path into a pair (root, ext), where *ext* is empty or
- begins with a period and contains at most one period,
- and *root* is everything before the extension."""
- raise UnsupportedOperation(self._unsupported_msg('splitext()'))
-
- def normcase(self, path):
- """Normalize the case of the path."""
- raise UnsupportedOperation(self._unsupported_msg('normcase()'))
-
- def isabs(self, path):
- """Returns whether the path is absolute, i.e. unaffected by the
- current directory or drive."""
- raise UnsupportedOperation(self._unsupported_msg('isabs()'))
-
-
class PathGlobber(_GlobberBase):
"""
Class providing shell-style globbing for path objects.
@@ -115,7 +63,7 @@ class PurePathBase:
# the `__init__()` method.
'_raw_paths',
)
- parser = ParserBase()
+ parser = posixpath
_globber = PathGlobber
def __init__(self, *args):
diff --git a/Lib/pathlib/_types.py b/Lib/pathlib/_types.py
new file mode 100644
index 0000000..60df94d
--- /dev/null
+++ b/Lib/pathlib/_types.py
@@ -0,0 +1,22 @@
+"""
+Protocols for supporting classes in pathlib.
+"""
+from typing import Protocol, runtime_checkable
+
+
+@runtime_checkable
+class Parser(Protocol):
+ """Protocol for path parsers, which do low-level path manipulation.
+
+ Path parsers provide a subset of the os.path API, specifically those
+ functions needed to provide PurePathBase functionality. Each PurePathBase
+ subclass references its path parser via a 'parser' class attribute.
+ """
+
+ sep: str
+ def join(self, path: str, *paths: str) -> str: ...
+ def split(self, path: str) -> tuple[str, str]: ...
+ def splitdrive(self, path: str) -> tuple[str, str]: ...
+ def splitext(self, path: str) -> tuple[str, str]: ...
+ def normcase(self, path: str) -> str: ...
+ def isabs(self, path: str) -> bool: ...
diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py
index 675abf3..dd9425c 100644
--- a/Lib/test/test_pathlib/test_pathlib_abc.py
+++ b/Lib/test/test_pathlib/test_pathlib_abc.py
@@ -5,7 +5,8 @@ import errno
import stat
import unittest
-from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase
+from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
+from pathlib._types import Parser
import posixpath
from test.support.os_helper import TESTFN
@@ -31,22 +32,6 @@ class UnsupportedOperationTest(unittest.TestCase):
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
self.assertTrue(isinstance(UnsupportedOperation(), NotImplementedError))
-
-class ParserBaseTest(unittest.TestCase):
- cls = ParserBase
-
- def test_unsupported_operation(self):
- m = self.cls()
- e = UnsupportedOperation
- with self.assertRaises(e):
- m.sep
- self.assertRaises(e, m.join, 'foo')
- self.assertRaises(e, m.split, 'foo')
- self.assertRaises(e, m.splitdrive, 'foo')
- self.assertRaises(e, m.splitext, 'foo')
- self.assertRaises(e, m.normcase, 'foo')
- self.assertRaises(e, m.isabs, 'foo')
-
#
# Tests for the pure classes.
#
@@ -55,37 +40,6 @@ class ParserBaseTest(unittest.TestCase):
class PurePathBaseTest(unittest.TestCase):
cls = PurePathBase
- def test_unsupported_operation_pure(self):
- p = self.cls('foo')
- e = UnsupportedOperation
- with self.assertRaises(e):
- p.drive
- with self.assertRaises(e):
- p.root
- with self.assertRaises(e):
- p.anchor
- with self.assertRaises(e):
- p.parts
- with self.assertRaises(e):
- p.parent
- with self.assertRaises(e):
- p.parents
- with self.assertRaises(e):
- p.name
- with self.assertRaises(e):
- p.stem
- with self.assertRaises(e):
- p.suffix
- with self.assertRaises(e):
- p.suffixes
- self.assertRaises(e, p.with_name, 'bar')
- self.assertRaises(e, p.with_stem, 'bar')
- self.assertRaises(e, p.with_suffix, '.txt')
- self.assertRaises(e, p.relative_to, '')
- self.assertRaises(e, p.is_relative_to, '')
- self.assertRaises(e, p.is_absolute)
- self.assertRaises(e, p.match, '*')
-
def test_magic_methods(self):
P = self.cls
self.assertFalse(hasattr(P, '__fspath__'))
@@ -100,12 +54,11 @@ class PurePathBaseTest(unittest.TestCase):
self.assertIs(P.__ge__, object.__ge__)
def test_parser(self):
- self.assertIsInstance(self.cls.parser, ParserBase)
+ self.assertIs(self.cls.parser, posixpath)
class DummyPurePath(PurePathBase):
__slots__ = ()
- parser = posixpath
def __eq__(self, other):
if not isinstance(other, DummyPurePath):
@@ -136,6 +89,9 @@ class DummyPurePathTest(unittest.TestCase):
self.sep = self.parser.sep
self.altsep = self.parser.altsep
+ def test_parser(self):
+ self.assertIsInstance(self.cls.parser, Parser)
+
def test_constructor_common(self):
P = self.cls
p = P('a')
@@ -1359,8 +1315,8 @@ class PathBaseTest(PurePathBaseTest):
self.assertRaises(e, p.write_bytes, b'foo')
self.assertRaises(e, p.write_text, 'foo')
self.assertRaises(e, p.iterdir)
- self.assertRaises(e, p.glob, '*')
- self.assertRaises(e, p.rglob, '*')
+ 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.expanduser)
self.assertRaises(e, p.readlink)
@@ -1411,7 +1367,6 @@ class DummyPath(PathBase):
memory.
"""
__slots__ = ()
- parser = posixpath
_files = {}
_directories = {}