From 15614a63dc497dd50bb8e557716c83f6dbacbeb4 Mon Sep 17 00:00:00 2001 From: barneygale Date: Thu, 12 Dec 2024 04:34:59 +0000 Subject: [PATCH] GH-127807: pathlib ABCs: remove a few private attributes From `PurePathBase` delete `_stack` and `_pattern_str`, and from `PathBase` delete `_glob_selector`. --- Lib/pathlib/_abc.py | 81 +++++++++++++++++++------------------------ Lib/pathlib/_local.py | 33 ++++++++++++++---- 2 files changed, 62 insertions(+), 52 deletions(-) diff --git a/Lib/pathlib/_abc.py b/Lib/pathlib/_abc.py index 02c6e0500617aa..3340d1b514e9d5 100644 --- a/Lib/pathlib/_abc.py +++ b/Lib/pathlib/_abc.py @@ -34,6 +34,23 @@ def _is_case_sensitive(parser): return parser.normcase('Aa') == 'Aa' +def _parse_path(path): + """ + Split the path into a 2-tuple (anchor, parts), where *anchor* is the + uppermost parent of the path (equivalent to path.parents[-1]), and + *parts* is a reversed list of parts following the anchor. + """ + split = path.parser.split + path = str(path) + parent, name = split(path) + names = [] + while path != parent: + names.append(name) + path = parent + parent, name = split(path) + return path, names + + class PathGlobber(_GlobberBase): """ Class providing shell-style globbing for path objects. @@ -115,7 +132,7 @@ def root(self): @property def anchor(self): """The concatenation of the drive and root, or ''.""" - return self._stack[0] + return _parse_path(self)[0] @property def name(self): @@ -193,8 +210,8 @@ def relative_to(self, other, *, walk_up=False): """ if not isinstance(other, PurePathBase): other = self.with_segments(other) - anchor0, parts0 = self._stack - anchor1, parts1 = other._stack + anchor0, parts0 = _parse_path(self) + anchor1, parts1 = _parse_path(other) if anchor0 != anchor1: raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors") while parts0 and parts1 and parts0[-1] == parts1[-1]: @@ -216,8 +233,8 @@ def is_relative_to(self, other): """ if not isinstance(other, PurePathBase): other = self.with_segments(other) - anchor0, parts0 = self._stack - anchor1, parts1 = other._stack + anchor0, parts0 = _parse_path(self) + anchor1, parts1 = _parse_path(other) if anchor0 != anchor1: return False while parts0 and parts1 and parts0[-1] == parts1[-1]: @@ -232,7 +249,7 @@ def is_relative_to(self, other): def parts(self): """An object providing sequence-like access to the components in the filesystem path.""" - anchor, parts = self._stack + anchor, parts = _parse_path(self) if anchor: parts.append(anchor) return tuple(reversed(parts)) @@ -257,23 +274,6 @@ def __rtruediv__(self, key): except TypeError: return NotImplemented - @property - def _stack(self): - """ - Split the path into a 2-tuple (anchor, parts), where *anchor* is the - uppermost parent of the path (equivalent to path.parents[-1]), and - *parts* is a reversed list of parts following the anchor. - """ - split = self.parser.split - path = str(self) - parent, name = split(path) - names = [] - while path != parent: - names.append(name) - path = parent - parent, name = split(path) - return path, names - @property def parent(self): """The logical parent of the path.""" @@ -301,11 +301,6 @@ def is_absolute(self): a drive).""" return self.parser.isabs(str(self)) - @property - def _pattern_str(self): - """The path expressed as a string, for use in pattern-matching.""" - return str(self) - def match(self, path_pattern, *, case_sensitive=None): """ Return True if this path matches the given pattern. If the pattern is @@ -343,8 +338,8 @@ def full_match(self, pattern, *, case_sensitive=None): if case_sensitive is None: case_sensitive = _is_case_sensitive(self.parser) globber = self._globber(pattern.parser.sep, case_sensitive, recursive=True) - match = globber.compile(pattern._pattern_str) - return match(self._pattern_str) is not None + match = globber.compile(str(pattern)) + return match(str(self)) is not None @@ -500,29 +495,25 @@ def iterdir(self): """ raise UnsupportedOperation(self._unsupported_msg('iterdir()')) - def _glob_selector(self, parts, case_sensitive, recurse_symlinks): - if case_sensitive is None: - case_sensitive = _is_case_sensitive(self.parser) - case_pedantic = False - else: - # The user has expressed a case sensitivity choice, but we don't - # know the case sensitivity of the underlying filesystem, so we - # 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) - return globber.selector(parts) - def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True): """Iterate over this subtree and yield all existing files (of any kind, including directories) matching the given relative pattern. """ if not isinstance(pattern, PurePathBase): pattern = self.with_segments(pattern) - anchor, parts = pattern._stack + anchor, parts = _parse_path(pattern) if anchor: raise NotImplementedError("Non-relative patterns are unsupported") - select = self._glob_selector(parts, case_sensitive, recurse_symlinks) + if case_sensitive is None: + case_sensitive = _is_case_sensitive(self.parser) + case_pedantic = False + elif case_sensitive == _is_case_sensitive(self.parser): + case_pedantic = False + else: + case_pedantic = True + recursive = True if recurse_symlinks else _no_recurse_symlinks + globber = self._globber(self.parser.sep, case_sensitive, case_pedantic, recursive) + select = globber.selector(parts) return select(self) def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=True): diff --git a/Lib/pathlib/_local.py b/Lib/pathlib/_local.py index 85437ec80bfcc4..af090248b21d54 100644 --- a/Lib/pathlib/_local.py +++ b/Lib/pathlib/_local.py @@ -5,7 +5,7 @@ import posixpath import sys from errno import EINVAL, EXDEV -from glob import _StringGlobber +from glob import _StringGlobber, _no_recurse_symlinks from itertools import chain from stat import S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO from _collections_abc import Sequence @@ -483,13 +483,22 @@ def as_uri(self): from urllib.parse import quote_from_bytes return prefix + quote_from_bytes(os.fsencode(path)) - @property - def _pattern_str(self): - """The path expressed as a string, for use in pattern-matching.""" + def full_match(self, pattern, *, case_sensitive=None): + """ + Return True if this path matches the given glob-style pattern. The + pattern is matched against the entire path. + """ + if not isinstance(pattern, PurePathBase): + pattern = self.with_segments(pattern) + if case_sensitive is None: + case_sensitive = self.parser is posixpath + # The string representation of an empty path is a single dot ('.'). Empty # paths shouldn't match wildcards, so we change it to the empty string. - path_str = str(self) - return '' if path_str == '.' else path_str + path = str(self) if self.parts else '' + pattern = str(pattern) if pattern.parts else '' + globber = self._globber(self.parser.sep, case_sensitive, recursive=True) + return globber.compile(pattern)(path) is not None # Subclassing os.PathLike makes isinstance() checks slower, # which in turn makes Path construction slower. Register instead! @@ -724,8 +733,18 @@ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False): kind, including directories) matching the given relative pattern. """ sys.audit("pathlib.Path.glob", self, pattern) + if case_sensitive is None: + case_sensitive = self.parser is posixpath + case_pedantic = False + else: + # The user has expressed a case sensitivity choice, but we don't + # know the case sensitivity of the underlying filesystem, so we + # must use scandir() for everything, including non-wildcard parts. + case_pedantic = True parts = self._parse_pattern(pattern) - select = self._glob_selector(parts[::-1], case_sensitive, recurse_symlinks) + recursive = True if recurse_symlinks else _no_recurse_symlinks + globber = self._globber(self.parser.sep, case_sensitive, case_pedantic, recursive) + select = globber.selector(parts[::-1]) root = str(self) paths = select(root)