Skip to content

GH-127807: pathlib ABCs: remove a few private attributes #127851

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 38 additions & 48 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,23 @@ def _is_case_sensitive(parser):
return parser.normcase('Aa') == 'Aa'


def _explode_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.
Expand All @@ -50,7 +67,6 @@ class PurePathBase:

__slots__ = ()
parser = posixpath
_globber = PathGlobber

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

@property
def name(self):
Expand Down Expand Up @@ -160,8 +176,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 = _explode_path(self)
anchor1, parts1 = _explode_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]:
Expand All @@ -183,8 +199,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 = _explode_path(self)
anchor1, parts1 = _explode_path(other)
if anchor0 != anchor1:
return False
while parts0 and parts1 and parts0[-1] == parts1[-1]:
Expand All @@ -199,7 +215,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 = _explode_path(self)
if anchor:
parts.append(anchor)
return tuple(reversed(parts))
Expand All @@ -224,23 +240,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."""
Expand Down Expand Up @@ -268,11 +267,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
Expand All @@ -293,7 +287,7 @@ def match(self, path_pattern, *, case_sensitive=None):
return False
if len(path_parts) > len(pattern_parts) and path_pattern.anchor:
return False
globber = self._globber(sep, case_sensitive)
globber = PathGlobber(sep, case_sensitive)
for path_part, pattern_part in zip(path_parts, pattern_parts):
match = globber.compile(pattern_part)
if match(path_part) is None:
Expand All @@ -309,9 +303,9 @@ def full_match(self, pattern, *, case_sensitive=None):
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)
match = globber.compile(pattern._pattern_str)
return match(self._pattern_str) is not None
globber = PathGlobber(pattern.parser.sep, case_sensitive, recursive=True)
match = globber.compile(str(pattern))
return match(str(self)) is not None



Expand Down Expand Up @@ -463,29 +457,25 @@ def iterdir(self):
"""
raise NotImplementedError

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 = _explode_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 = PathGlobber(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):
Expand Down
34 changes: 26 additions & 8 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -112,7 +112,6 @@ class PurePath(PurePathBase):
'_hash',
)
parser = os.path
_globber = _StringGlobber

def __new__(cls, *args, **kwargs):
"""Construct a PurePath from one or several strings and or existing
Expand Down Expand Up @@ -513,13 +512,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 = _StringGlobber(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!
Expand Down Expand Up @@ -749,8 +757,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 = _StringGlobber(self.parser.sep, case_sensitive, case_pedantic, recursive)
select = globber.selector(parts[::-1])
root = str(self)
paths = select(root)

Expand Down
Loading