Skip to content

Commit f5ba74b

Browse files
authored
GH-127807: pathlib ABCs: remove a few private attributes (#127851)
From `PurePathBase` delete `_globber`, `_stack` and `_pattern_str`, and from `PathBase` delete `_glob_selector`. This helps avoid an unpleasant surprise for a users who try to use these names.
1 parent a959ea1 commit f5ba74b

File tree

2 files changed

+64
-56
lines changed

2 files changed

+64
-56
lines changed

Lib/pathlib/_abc.py

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,23 @@ def _is_case_sensitive(parser):
2525
return parser.normcase('Aa') == 'Aa'
2626

2727

28+
def _explode_path(path):
29+
"""
30+
Split the path into a 2-tuple (anchor, parts), where *anchor* is the
31+
uppermost parent of the path (equivalent to path.parents[-1]), and
32+
*parts* is a reversed list of parts following the anchor.
33+
"""
34+
split = path.parser.split
35+
path = str(path)
36+
parent, name = split(path)
37+
names = []
38+
while path != parent:
39+
names.append(name)
40+
path = parent
41+
parent, name = split(path)
42+
return path, names
43+
44+
2845
class PathGlobber(_GlobberBase):
2946
"""
3047
Class providing shell-style globbing for path objects.
@@ -50,7 +67,6 @@ class PurePathBase:
5067

5168
__slots__ = ()
5269
parser = posixpath
53-
_globber = PathGlobber
5470

5571
def with_segments(self, *pathsegments):
5672
"""Construct a new path object from any number of path-like objects.
@@ -82,7 +98,7 @@ def root(self):
8298
@property
8399
def anchor(self):
84100
"""The concatenation of the drive and root, or ''."""
85-
return self._stack[0]
101+
return _explode_path(self)[0]
86102

87103
@property
88104
def name(self):
@@ -160,8 +176,8 @@ def relative_to(self, other, *, walk_up=False):
160176
"""
161177
if not isinstance(other, PurePathBase):
162178
other = self.with_segments(other)
163-
anchor0, parts0 = self._stack
164-
anchor1, parts1 = other._stack
179+
anchor0, parts0 = _explode_path(self)
180+
anchor1, parts1 = _explode_path(other)
165181
if anchor0 != anchor1:
166182
raise ValueError(f"{str(self)!r} and {str(other)!r} have different anchors")
167183
while parts0 and parts1 and parts0[-1] == parts1[-1]:
@@ -183,8 +199,8 @@ def is_relative_to(self, other):
183199
"""
184200
if not isinstance(other, PurePathBase):
185201
other = self.with_segments(other)
186-
anchor0, parts0 = self._stack
187-
anchor1, parts1 = other._stack
202+
anchor0, parts0 = _explode_path(self)
203+
anchor1, parts1 = _explode_path(other)
188204
if anchor0 != anchor1:
189205
return False
190206
while parts0 and parts1 and parts0[-1] == parts1[-1]:
@@ -199,7 +215,7 @@ def is_relative_to(self, other):
199215
def parts(self):
200216
"""An object providing sequence-like access to the
201217
components in the filesystem path."""
202-
anchor, parts = self._stack
218+
anchor, parts = _explode_path(self)
203219
if anchor:
204220
parts.append(anchor)
205221
return tuple(reversed(parts))
@@ -224,23 +240,6 @@ def __rtruediv__(self, key):
224240
except TypeError:
225241
return NotImplemented
226242

227-
@property
228-
def _stack(self):
229-
"""
230-
Split the path into a 2-tuple (anchor, parts), where *anchor* is the
231-
uppermost parent of the path (equivalent to path.parents[-1]), and
232-
*parts* is a reversed list of parts following the anchor.
233-
"""
234-
split = self.parser.split
235-
path = str(self)
236-
parent, name = split(path)
237-
names = []
238-
while path != parent:
239-
names.append(name)
240-
path = parent
241-
parent, name = split(path)
242-
return path, names
243-
244243
@property
245244
def parent(self):
246245
"""The logical parent of the path."""
@@ -268,11 +267,6 @@ def is_absolute(self):
268267
a drive)."""
269268
return self.parser.isabs(str(self))
270269

271-
@property
272-
def _pattern_str(self):
273-
"""The path expressed as a string, for use in pattern-matching."""
274-
return str(self)
275-
276270
def match(self, path_pattern, *, case_sensitive=None):
277271
"""
278272
Return True if this path matches the given pattern. If the pattern is
@@ -293,7 +287,7 @@ def match(self, path_pattern, *, case_sensitive=None):
293287
return False
294288
if len(path_parts) > len(pattern_parts) and path_pattern.anchor:
295289
return False
296-
globber = self._globber(sep, case_sensitive)
290+
globber = PathGlobber(sep, case_sensitive)
297291
for path_part, pattern_part in zip(path_parts, pattern_parts):
298292
match = globber.compile(pattern_part)
299293
if match(path_part) is None:
@@ -309,9 +303,9 @@ def full_match(self, pattern, *, case_sensitive=None):
309303
pattern = self.with_segments(pattern)
310304
if case_sensitive is None:
311305
case_sensitive = _is_case_sensitive(self.parser)
312-
globber = self._globber(pattern.parser.sep, case_sensitive, recursive=True)
313-
match = globber.compile(pattern._pattern_str)
314-
return match(self._pattern_str) is not None
306+
globber = PathGlobber(pattern.parser.sep, case_sensitive, recursive=True)
307+
match = globber.compile(str(pattern))
308+
return match(str(self)) is not None
315309

316310

317311

@@ -463,29 +457,25 @@ def iterdir(self):
463457
"""
464458
raise NotImplementedError
465459

466-
def _glob_selector(self, parts, case_sensitive, recurse_symlinks):
467-
if case_sensitive is None:
468-
case_sensitive = _is_case_sensitive(self.parser)
469-
case_pedantic = False
470-
else:
471-
# The user has expressed a case sensitivity choice, but we don't
472-
# know the case sensitivity of the underlying filesystem, so we
473-
# must use scandir() for everything, including non-wildcard parts.
474-
case_pedantic = True
475-
recursive = True if recurse_symlinks else _no_recurse_symlinks
476-
globber = self._globber(self.parser.sep, case_sensitive, case_pedantic, recursive)
477-
return globber.selector(parts)
478-
479460
def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):
480461
"""Iterate over this subtree and yield all existing files (of any
481462
kind, including directories) matching the given relative pattern.
482463
"""
483464
if not isinstance(pattern, PurePathBase):
484465
pattern = self.with_segments(pattern)
485-
anchor, parts = pattern._stack
466+
anchor, parts = _explode_path(pattern)
486467
if anchor:
487468
raise NotImplementedError("Non-relative patterns are unsupported")
488-
select = self._glob_selector(parts, case_sensitive, recurse_symlinks)
469+
if case_sensitive is None:
470+
case_sensitive = _is_case_sensitive(self.parser)
471+
case_pedantic = False
472+
elif case_sensitive == _is_case_sensitive(self.parser):
473+
case_pedantic = False
474+
else:
475+
case_pedantic = True
476+
recursive = True if recurse_symlinks else _no_recurse_symlinks
477+
globber = PathGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
478+
select = globber.selector(parts)
489479
return select(self)
490480

491481
def rglob(self, pattern, *, case_sensitive=None, recurse_symlinks=True):

Lib/pathlib/_local.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import posixpath
66
import sys
77
from errno import EINVAL, EXDEV
8-
from glob import _StringGlobber
8+
from glob import _StringGlobber, _no_recurse_symlinks
99
from itertools import chain
1010
from stat import S_ISSOCK, S_ISBLK, S_ISCHR, S_ISFIFO
1111
from _collections_abc import Sequence
@@ -112,7 +112,6 @@ class PurePath(PurePathBase):
112112
'_hash',
113113
)
114114
parser = os.path
115-
_globber = _StringGlobber
116115

117116
def __new__(cls, *args, **kwargs):
118117
"""Construct a PurePath from one or several strings and or existing
@@ -513,13 +512,22 @@ def as_uri(self):
513512
from urllib.parse import quote_from_bytes
514513
return prefix + quote_from_bytes(os.fsencode(path))
515514

516-
@property
517-
def _pattern_str(self):
518-
"""The path expressed as a string, for use in pattern-matching."""
515+
def full_match(self, pattern, *, case_sensitive=None):
516+
"""
517+
Return True if this path matches the given glob-style pattern. The
518+
pattern is matched against the entire path.
519+
"""
520+
if not isinstance(pattern, PurePathBase):
521+
pattern = self.with_segments(pattern)
522+
if case_sensitive is None:
523+
case_sensitive = self.parser is posixpath
524+
519525
# The string representation of an empty path is a single dot ('.'). Empty
520526
# paths shouldn't match wildcards, so we change it to the empty string.
521-
path_str = str(self)
522-
return '' if path_str == '.' else path_str
527+
path = str(self) if self.parts else ''
528+
pattern = str(pattern) if pattern.parts else ''
529+
globber = _StringGlobber(self.parser.sep, case_sensitive, recursive=True)
530+
return globber.compile(pattern)(path) is not None
523531

524532
# Subclassing os.PathLike makes isinstance() checks slower,
525533
# which in turn makes Path construction slower. Register instead!
@@ -749,8 +757,18 @@ def glob(self, pattern, *, case_sensitive=None, recurse_symlinks=False):
749757
kind, including directories) matching the given relative pattern.
750758
"""
751759
sys.audit("pathlib.Path.glob", self, pattern)
760+
if case_sensitive is None:
761+
case_sensitive = self.parser is posixpath
762+
case_pedantic = False
763+
else:
764+
# The user has expressed a case sensitivity choice, but we don't
765+
# know the case sensitivity of the underlying filesystem, so we
766+
# must use scandir() for everything, including non-wildcard parts.
767+
case_pedantic = True
752768
parts = self._parse_pattern(pattern)
753-
select = self._glob_selector(parts[::-1], case_sensitive, recurse_symlinks)
769+
recursive = True if recurse_symlinks else _no_recurse_symlinks
770+
globber = _StringGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
771+
select = globber.selector(parts[::-1])
754772
root = str(self)
755773
paths = select(root)
756774

0 commit comments

Comments
 (0)