mirror of
https://github.com/python/cpython.git
synced 2025-10-23 07:02:24 +00:00
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>
This commit is contained in:
parent
e85f2f1703
commit
5c89adf385
3 changed files with 32 additions and 107 deletions
|
@ -13,6 +13,7 @@ resemble pathlib's PurePath and Path respectively.
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
import operator
|
import operator
|
||||||
|
import posixpath
|
||||||
from errno import EINVAL
|
from errno import EINVAL
|
||||||
from glob import _GlobberBase, _no_recurse_symlinks
|
from glob import _GlobberBase, _no_recurse_symlinks
|
||||||
from stat import S_ISDIR, S_ISLNK, S_ISREG, S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
|
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'
|
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 PathGlobber(_GlobberBase):
|
||||||
"""
|
"""
|
||||||
Class providing shell-style globbing for path objects.
|
Class providing shell-style globbing for path objects.
|
||||||
|
@ -115,7 +63,7 @@ class PurePathBase:
|
||||||
# the `__init__()` method.
|
# the `__init__()` method.
|
||||||
'_raw_paths',
|
'_raw_paths',
|
||||||
)
|
)
|
||||||
parser = ParserBase()
|
parser = posixpath
|
||||||
_globber = PathGlobber
|
_globber = PathGlobber
|
||||||
|
|
||||||
def __init__(self, *args):
|
def __init__(self, *args):
|
||||||
|
|
22
Lib/pathlib/_types.py
Normal file
22
Lib/pathlib/_types.py
Normal file
|
@ -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: ...
|
|
@ -5,7 +5,8 @@ import errno
|
||||||
import stat
|
import stat
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase
|
from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
|
||||||
|
from pathlib._types import Parser
|
||||||
import posixpath
|
import posixpath
|
||||||
|
|
||||||
from test.support.os_helper import TESTFN
|
from test.support.os_helper import TESTFN
|
||||||
|
@ -31,22 +32,6 @@ class UnsupportedOperationTest(unittest.TestCase):
|
||||||
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
|
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
|
||||||
self.assertTrue(isinstance(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.
|
# Tests for the pure classes.
|
||||||
#
|
#
|
||||||
|
@ -55,37 +40,6 @@ class ParserBaseTest(unittest.TestCase):
|
||||||
class PurePathBaseTest(unittest.TestCase):
|
class PurePathBaseTest(unittest.TestCase):
|
||||||
cls = PurePathBase
|
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):
|
def test_magic_methods(self):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
self.assertFalse(hasattr(P, '__fspath__'))
|
self.assertFalse(hasattr(P, '__fspath__'))
|
||||||
|
@ -100,12 +54,11 @@ class PurePathBaseTest(unittest.TestCase):
|
||||||
self.assertIs(P.__ge__, object.__ge__)
|
self.assertIs(P.__ge__, object.__ge__)
|
||||||
|
|
||||||
def test_parser(self):
|
def test_parser(self):
|
||||||
self.assertIsInstance(self.cls.parser, ParserBase)
|
self.assertIs(self.cls.parser, posixpath)
|
||||||
|
|
||||||
|
|
||||||
class DummyPurePath(PurePathBase):
|
class DummyPurePath(PurePathBase):
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
parser = posixpath
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, DummyPurePath):
|
if not isinstance(other, DummyPurePath):
|
||||||
|
@ -136,6 +89,9 @@ class DummyPurePathTest(unittest.TestCase):
|
||||||
self.sep = self.parser.sep
|
self.sep = self.parser.sep
|
||||||
self.altsep = self.parser.altsep
|
self.altsep = self.parser.altsep
|
||||||
|
|
||||||
|
def test_parser(self):
|
||||||
|
self.assertIsInstance(self.cls.parser, Parser)
|
||||||
|
|
||||||
def test_constructor_common(self):
|
def test_constructor_common(self):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
p = P('a')
|
p = P('a')
|
||||||
|
@ -1359,8 +1315,8 @@ class PathBaseTest(PurePathBaseTest):
|
||||||
self.assertRaises(e, p.write_bytes, b'foo')
|
self.assertRaises(e, p.write_bytes, b'foo')
|
||||||
self.assertRaises(e, p.write_text, 'foo')
|
self.assertRaises(e, p.write_text, 'foo')
|
||||||
self.assertRaises(e, p.iterdir)
|
self.assertRaises(e, p.iterdir)
|
||||||
self.assertRaises(e, p.glob, '*')
|
self.assertRaises(e, lambda: list(p.glob('*')))
|
||||||
self.assertRaises(e, p.rglob, '*')
|
self.assertRaises(e, lambda: list(p.rglob('*')))
|
||||||
self.assertRaises(e, lambda: list(p.walk()))
|
self.assertRaises(e, lambda: list(p.walk()))
|
||||||
self.assertRaises(e, p.expanduser)
|
self.assertRaises(e, p.expanduser)
|
||||||
self.assertRaises(e, p.readlink)
|
self.assertRaises(e, p.readlink)
|
||||||
|
@ -1411,7 +1367,6 @@ class DummyPath(PathBase):
|
||||||
memory.
|
memory.
|
||||||
"""
|
"""
|
||||||
__slots__ = ()
|
__slots__ = ()
|
||||||
parser = posixpath
|
|
||||||
|
|
||||||
_files = {}
|
_files = {}
|
||||||
_directories = {}
|
_directories = {}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue