Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 5c89adf

Browse filesBrowse files
barneygalepicnixz
andauthored
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>
1 parent e85f2f1 commit 5c89adf
Copy full SHA for 5c89adf

File tree

3 files changed

+32
-107
lines changed
Filter options

3 files changed

+32
-107
lines changed

‎Lib/pathlib/_abc.py

Copy file name to clipboardExpand all lines: Lib/pathlib/_abc.py
+2-54Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import functools
1515
import operator
16+
import posixpath
1617
from errno import EINVAL
1718
from glob import _GlobberBase, _no_recurse_symlinks
1819
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):
3334
return parser.normcase('Aa') == 'Aa'
3435

3536

36-
37-
class ParserBase:
38-
"""Base class for path parsers, which do low-level path manipulation.
39-
40-
Path parsers provide a subset of the os.path API, specifically those
41-
functions needed to provide PurePathBase functionality. Each PurePathBase
42-
subclass references its path parser via a 'parser' class attribute.
43-
44-
Every method in this base class raises an UnsupportedOperation exception.
45-
"""
46-
47-
@classmethod
48-
def _unsupported_msg(cls, attribute):
49-
return f"{cls.__name__}.{attribute} is unsupported"
50-
51-
@property
52-
def sep(self):
53-
"""The character used to separate path components."""
54-
raise UnsupportedOperation(self._unsupported_msg('sep'))
55-
56-
def join(self, path, *paths):
57-
"""Join path segments."""
58-
raise UnsupportedOperation(self._unsupported_msg('join()'))
59-
60-
def split(self, path):
61-
"""Split the path into a pair (head, tail), where *head* is everything
62-
before the final path separator, and *tail* is everything after.
63-
Either part may be empty.
64-
"""
65-
raise UnsupportedOperation(self._unsupported_msg('split()'))
66-
67-
def splitdrive(self, path):
68-
"""Split the path into a 2-item tuple (drive, tail), where *drive* is
69-
a device name or mount point, and *tail* is everything after the
70-
drive. Either part may be empty."""
71-
raise UnsupportedOperation(self._unsupported_msg('splitdrive()'))
72-
73-
def splitext(self, path):
74-
"""Split the path into a pair (root, ext), where *ext* is empty or
75-
begins with a period and contains at most one period,
76-
and *root* is everything before the extension."""
77-
raise UnsupportedOperation(self._unsupported_msg('splitext()'))
78-
79-
def normcase(self, path):
80-
"""Normalize the case of the path."""
81-
raise UnsupportedOperation(self._unsupported_msg('normcase()'))
82-
83-
def isabs(self, path):
84-
"""Returns whether the path is absolute, i.e. unaffected by the
85-
current directory or drive."""
86-
raise UnsupportedOperation(self._unsupported_msg('isabs()'))
87-
88-
8937
class PathGlobber(_GlobberBase):
9038
"""
9139
Class providing shell-style globbing for path objects.
@@ -115,7 +63,7 @@ class PurePathBase:
11563
# the `__init__()` method.
11664
'_raw_paths',
11765
)
118-
parser = ParserBase()
66+
parser = posixpath
11967
_globber = PathGlobber
12068

12169
def __init__(self, *args):

‎Lib/pathlib/_types.py

Copy file name to clipboard
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""
2+
Protocols for supporting classes in pathlib.
3+
"""
4+
from typing import Protocol, runtime_checkable
5+
6+
7+
@runtime_checkable
8+
class Parser(Protocol):
9+
"""Protocol for path parsers, which do low-level path manipulation.
10+
11+
Path parsers provide a subset of the os.path API, specifically those
12+
functions needed to provide PurePathBase functionality. Each PurePathBase
13+
subclass references its path parser via a 'parser' class attribute.
14+
"""
15+
16+
sep: str
17+
def join(self, path: str, *paths: str) -> str: ...
18+
def split(self, path: str) -> tuple[str, str]: ...
19+
def splitdrive(self, path: str) -> tuple[str, str]: ...
20+
def splitext(self, path: str) -> tuple[str, str]: ...
21+
def normcase(self, path: str) -> str: ...
22+
def isabs(self, path: str) -> bool: ...

‎Lib/test/test_pathlib/test_pathlib_abc.py

Copy file name to clipboardExpand all lines: Lib/test/test_pathlib/test_pathlib_abc.py
+8-53Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,8 @@
55
import stat
66
import unittest
77

8-
from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase
8+
from pathlib._abc import UnsupportedOperation, PurePathBase, PathBase
9+
from pathlib._types import Parser
910
import posixpath
1011

1112
from test.support.os_helper import TESTFN
@@ -31,22 +32,6 @@ def test_is_notimplemented(self):
3132
self.assertTrue(issubclass(UnsupportedOperation, NotImplementedError))
3233
self.assertTrue(isinstance(UnsupportedOperation(), NotImplementedError))
3334

34-
35-
class ParserBaseTest(unittest.TestCase):
36-
cls = ParserBase
37-
38-
def test_unsupported_operation(self):
39-
m = self.cls()
40-
e = UnsupportedOperation
41-
with self.assertRaises(e):
42-
m.sep
43-
self.assertRaises(e, m.join, 'foo')
44-
self.assertRaises(e, m.split, 'foo')
45-
self.assertRaises(e, m.splitdrive, 'foo')
46-
self.assertRaises(e, m.splitext, 'foo')
47-
self.assertRaises(e, m.normcase, 'foo')
48-
self.assertRaises(e, m.isabs, 'foo')
49-
5035
#
5136
# Tests for the pure classes.
5237
#
@@ -55,37 +40,6 @@ def test_unsupported_operation(self):
5540
class PurePathBaseTest(unittest.TestCase):
5641
cls = PurePathBase
5742

58-
def test_unsupported_operation_pure(self):
59-
p = self.cls('foo')
60-
e = UnsupportedOperation
61-
with self.assertRaises(e):
62-
p.drive
63-
with self.assertRaises(e):
64-
p.root
65-
with self.assertRaises(e):
66-
p.anchor
67-
with self.assertRaises(e):
68-
p.parts
69-
with self.assertRaises(e):
70-
p.parent
71-
with self.assertRaises(e):
72-
p.parents
73-
with self.assertRaises(e):
74-
p.name
75-
with self.assertRaises(e):
76-
p.stem
77-
with self.assertRaises(e):
78-
p.suffix
79-
with self.assertRaises(e):
80-
p.suffixes
81-
self.assertRaises(e, p.with_name, 'bar')
82-
self.assertRaises(e, p.with_stem, 'bar')
83-
self.assertRaises(e, p.with_suffix, '.txt')
84-
self.assertRaises(e, p.relative_to, '')
85-
self.assertRaises(e, p.is_relative_to, '')
86-
self.assertRaises(e, p.is_absolute)
87-
self.assertRaises(e, p.match, '*')
88-
8943
def test_magic_methods(self):
9044
P = self.cls
9145
self.assertFalse(hasattr(P, '__fspath__'))
@@ -100,12 +54,11 @@ def test_magic_methods(self):
10054
self.assertIs(P.__ge__, object.__ge__)
10155

10256
def test_parser(self):
103-
self.assertIsInstance(self.cls.parser, ParserBase)
57+
self.assertIs(self.cls.parser, posixpath)
10458

10559

10660
class DummyPurePath(PurePathBase):
10761
__slots__ = ()
108-
parser = posixpath
10962

11063
def __eq__(self, other):
11164
if not isinstance(other, DummyPurePath):
@@ -136,6 +89,9 @@ def setUp(self):
13689
self.sep = self.parser.sep
13790
self.altsep = self.parser.altsep
13891

92+
def test_parser(self):
93+
self.assertIsInstance(self.cls.parser, Parser)
94+
13995
def test_constructor_common(self):
14096
P = self.cls
14197
p = P('a')
@@ -1359,8 +1315,8 @@ def test_unsupported_operation(self):
13591315
self.assertRaises(e, p.write_bytes, b'foo')
13601316
self.assertRaises(e, p.write_text, 'foo')
13611317
self.assertRaises(e, p.iterdir)
1362-
self.assertRaises(e, p.glob, '*')
1363-
self.assertRaises(e, p.rglob, '*')
1318+
self.assertRaises(e, lambda: list(p.glob('*')))
1319+
self.assertRaises(e, lambda: list(p.rglob('*')))
13641320
self.assertRaises(e, lambda: list(p.walk()))
13651321
self.assertRaises(e, p.expanduser)
13661322
self.assertRaises(e, p.readlink)
@@ -1411,7 +1367,6 @@ class DummyPath(PathBase):
14111367
memory.
14121368
"""
14131369
__slots__ = ()
1414-
parser = posixpath
14151370

14161371
_files = {}
14171372
_directories = {}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.