Skip to content

Commit

Permalink
pythonGH-127807: pathlib ABCs: remove a few private attributes
Browse files Browse the repository at this point in the history
From `PurePathBase` delete `_stack` and `_pattern_str`, and from `PathBase`
delete `_glob_selector`.
  • Loading branch information
barneygale committed Dec 12, 2024
1 parent 12b4f1a commit 15614a6
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 52 deletions.
81 changes: 36 additions & 45 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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]:
Expand All @@ -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]:
Expand All @@ -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))
Expand All @@ -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."""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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



Expand Down Expand Up @@ -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):
Expand Down
33 changes: 26 additions & 7 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 @@ -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!
Expand Down Expand Up @@ -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)

Expand Down

0 comments on commit 15614a6

Please sign in to comment.