From c09307d2414e689c2bcabfc946a17e2ab6bf0bdf Mon Sep 17 00:00:00 2001 From: barneygale Date: Sat, 7 Dec 2024 19:25:48 +0000 Subject: [PATCH 1/2] GH-127456: pathlib ABCs: remove `ParserBase.sep` Remove `ParserBase.sep`, which is the class's only non-method member. Instead, we compute the separator from `PathBase.__init_subclass__()` by calling `parser.join('a', 'a').strip('a')`, and store the result on the subclass as `cls._sep`. We also set `cls._case_sensitive` according to `parser.normcase('Aa') == 'Aa'`, which was previously done in a caching function. --- Lib/pathlib/_abc.py | 34 ++++++++++------------- Lib/test/test_pathlib/test_pathlib_abc.py | 2 -- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 820970fcd5889b..a896a456f971b2 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -11,7 +11,6 @@ resemble pathlib's PurePath and Path respectively. """ -import functools import operator from errno import EINVAL from glob import _GlobberBase, _no_recurse_symlinks @@ -28,12 +27,6 @@ class UnsupportedOperation(NotImplementedError): pass -@functools.cache -def _is_case_sensitive(parser): - return parser.normcase('Aa') == 'Aa' - - - class ParserBase: """Base class for path parsers, which do low-level path manipulation. @@ -48,11 +41,6 @@ class ParserBase: 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()')) @@ -116,8 +104,16 @@ class PurePathBase: '_raw_paths', ) parser = ParserBase() + _sep = '/' + _case_sensitive = True _globber = PathGlobber + def __init_subclass__(cls): + super().__init_subclass__() + if 'parser' in cls.__dict__: + cls._sep = cls.parser.join('a', 'a').strip('a') + cls._case_sensitive = cls.parser.normcase('Aa') == 'Aa' + def __init__(self, *args): for arg in args: if not isinstance(arg, str): @@ -152,7 +148,7 @@ def __str__(self): def as_posix(self): """Return the string representation of the path with forward (/) slashes.""" - return str(self).replace(self.parser.sep, '/') + return str(self).replace(self._sep, '/') @property def drive(self): @@ -368,8 +364,8 @@ def match(self, path_pattern, *, case_sensitive=None): if not isinstance(path_pattern, PurePathBase): path_pattern = self.with_segments(path_pattern) if case_sensitive is None: - case_sensitive = _is_case_sensitive(self.parser) - sep = path_pattern.parser.sep + case_sensitive = self._case_sensitive + sep = path_pattern._sep path_parts = self.parts[::-1] pattern_parts = path_pattern.parts[::-1] if not pattern_parts: @@ -393,8 +389,8 @@ def full_match(self, pattern, *, case_sensitive=None): if not isinstance(pattern, PurePathBase): pattern = self.with_segments(pattern) if case_sensitive is None: - case_sensitive = _is_case_sensitive(self.parser) - globber = self._globber(pattern.parser.sep, case_sensitive, recursive=True) + case_sensitive = self._case_sensitive + globber = self._globber(pattern._sep, case_sensitive, recursive=True) match = globber.compile(pattern._pattern_str) return match(self._pattern_str) is not None @@ -639,7 +635,7 @@ def iterdir(self): def _glob_selector(self, parts, case_sensitive, recurse_symlinks): if case_sensitive is None: - case_sensitive = _is_case_sensitive(self.parser) + case_sensitive = self._case_sensitive case_pedantic = False else: # The user has expressed a case sensitivity choice, but we don't @@ -647,7 +643,7 @@ def _glob_selector(self, parts, case_sensitive, recurse_symlinks): # must use scandir() for everything, including non-wildcard parts. case_pedantic = True recursive = True if recurse_symlinks else _no_recurse_symlinks - globber = self._globber(self.parser.sep, case_sensitive, case_pedantic, recursive) + globber = self._globber(self._sep, case_sensitive, case_pedantic, recursive) return globber.selector(parts) def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True): diff --git a/Lib/test/test_pathlib/test_pathlib_abc.py b/Lib/test/test_pathlib/test_pathlib_abc.py index bf9ae6cc8a2433..3b868da4568564 100644 --- a/Lib/test/test_pathlib/test_pathlib_abc.py +++ b/Lib/test/test_pathlib/test_pathlib_abc.py @@ -38,8 +38,6 @@ class ParserBaseTest(unittest.TestCase): 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') From a627050688aef610c2a7e6a3981bb87875682d48 Mon Sep 17 00:00:00 2001 From: barneygale Date: Mon, 9 Dec 2024 20:08:29 +0000 Subject: [PATCH 2/2] Fix dodgy merge. --- Lib/pathlib/_types.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/pathlib/_types.py b/Lib/pathlib/_types.py index 60df94d0b46049..6548c7d4fb6855 100644 --- a/Lib/pathlib/_types.py +++ b/Lib/pathlib/_types.py @@ -13,7 +13,6 @@ class Parser(Protocol): 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]: ...