GH-128520: pathlib ABCs: tighten up argument types (#131621)

In `JoinablePath.full_match()` and `ReadablePath.glob()`, accept a `str`
pattern argument rather than `JoinablePath | str`.

In `ReadablePath.copy()` and `copy_into()`, accept a `WritablePath` target
argument rather than `WritablePath | str`.
This commit is contained in:
Barney Gale 2025-03-24 15:39:08 +00:00 committed by GitHub
parent d2d886215c
commit d372472896
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 9 additions and 29 deletions

View file

@ -1105,11 +1105,7 @@ def copy(self, target, **kwargs):
if not hasattr(target, 'with_segments'): if not hasattr(target, 'with_segments'):
target = self.with_segments(target) target = self.with_segments(target)
ensure_distinct_paths(self, target) ensure_distinct_paths(self, target)
try: target._copy_from(self, **kwargs)
copy_to_target = target._copy_from
except AttributeError:
raise TypeError(f"Target path is not writable: {target!r}") from None
copy_to_target(self, **kwargs)
return target.joinpath() # Empty join to ensure fresh metadata. return target.joinpath() # Empty join to ensure fresh metadata.
def copy_into(self, target_dir, **kwargs): def copy_into(self, target_dir, **kwargs):

View file

@ -17,14 +17,12 @@
from typing import Optional, Protocol, runtime_checkable from typing import Optional, Protocol, runtime_checkable
def _explode_path(path): def _explode_path(path, split):
""" """
Split the path into a 2-tuple (anchor, parts), where *anchor* is the Split the path into a 2-tuple (anchor, parts), where *anchor* is the
uppermost parent of the path (equivalent to path.parents[-1]), and uppermost parent of the path (equivalent to path.parents[-1]), and
*parts* is a reversed list of parts following the anchor. *parts* is a reversed list of parts following the anchor.
""" """
split = path.parser.split
path = str(path)
parent, name = split(path) parent, name = split(path)
names = [] names = []
while path != parent: while path != parent:
@ -95,7 +93,7 @@ def __str__(self):
@property @property
def anchor(self): def anchor(self):
"""The concatenation of the drive and root, or ''.""" """The concatenation of the drive and root, or ''."""
return _explode_path(self)[0] return _explode_path(str(self), self.parser.split)[0]
@property @property
def name(self): def name(self):
@ -169,7 +167,7 @@ def with_suffix(self, suffix):
def parts(self): def parts(self):
"""An object providing sequence-like access to the """An object providing sequence-like access to the
components in the filesystem path.""" components in the filesystem path."""
anchor, parts = _explode_path(self) anchor, parts = _explode_path(str(self), self.parser.split)
if anchor: if anchor:
parts.append(anchor) parts.append(anchor)
return tuple(reversed(parts)) return tuple(reversed(parts))
@ -221,11 +219,9 @@ def full_match(self, pattern):
Return True if this path matches the given glob-style pattern. The Return True if this path matches the given glob-style pattern. The
pattern is matched against the entire path. pattern is matched against the entire path.
""" """
if not hasattr(pattern, 'with_segments'):
pattern = self.with_segments(pattern)
case_sensitive = self.parser.normcase('Aa') == 'Aa' case_sensitive = self.parser.normcase('Aa') == 'Aa'
globber = _PathGlobber(pattern.parser.sep, case_sensitive, recursive=True) globber = _PathGlobber(self.parser.sep, case_sensitive, recursive=True)
match = globber.compile(str(pattern), altsep=pattern.parser.altsep) match = globber.compile(pattern, altsep=self.parser.altsep)
return match(str(self)) is not None return match(str(self)) is not None
@ -282,9 +278,7 @@ def glob(self, pattern, *, recurse_symlinks=True):
"""Iterate over this subtree and yield all existing files (of any """Iterate over this subtree and yield all existing files (of any
kind, including directories) matching the given relative pattern. kind, including directories) matching the given relative pattern.
""" """
if not hasattr(pattern, 'with_segments'): anchor, parts = _explode_path(pattern, self.parser.split)
pattern = self.with_segments(pattern)
anchor, parts = _explode_path(pattern)
if anchor: if anchor:
raise NotImplementedError("Non-relative patterns are unsupported") raise NotImplementedError("Non-relative patterns are unsupported")
elif not parts: elif not parts:
@ -338,14 +332,8 @@ def copy(self, target, **kwargs):
""" """
Recursively copy this file or directory tree to the given destination. Recursively copy this file or directory tree to the given destination.
""" """
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)
ensure_distinct_paths(self, target) ensure_distinct_paths(self, target)
try: target._copy_from(self, **kwargs)
copy_to_target = target._copy_from
except AttributeError:
raise TypeError(f"Target path is not writable: {target!r}") from None
copy_to_target(self, **kwargs)
return target.joinpath() # Empty join to ensure fresh metadata. return target.joinpath() # Empty join to ensure fresh metadata.
def copy_into(self, target_dir, **kwargs): def copy_into(self, target_dir, **kwargs):
@ -355,11 +343,7 @@ def copy_into(self, target_dir, **kwargs):
name = self.name name = self.name
if not name: if not name:
raise ValueError(f"{self!r} has an empty name") raise ValueError(f"{self!r} has an empty name")
elif hasattr(target_dir, 'with_segments'): return self.copy(target_dir / name, **kwargs)
target = target_dir / name
else:
target = self.with_segments(target_dir, name)
return self.copy(target, **kwargs)
class _WritablePath(_JoinablePath): class _WritablePath(_JoinablePath):