Skip to content

GH-128520: More consistent type-checking behaviour in pathlib #130199

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 6 commits into from
Feb 21, 2025
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
8 changes: 4 additions & 4 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,7 @@ 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, JoinablePath):
if not hasattr(pattern, 'with_segments'):
pattern = self.with_segments(pattern)
if case_sensitive is None:
case_sensitive = self.parser.normcase('Aa') == 'Aa'
Expand Down Expand Up @@ -286,7 +286,7 @@ 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, JoinablePath):
if not hasattr(pattern, 'with_segments'):
pattern = self.with_segments(pattern)
anchor, parts = _explode_path(pattern)
if anchor:
Expand Down Expand Up @@ -348,7 +348,7 @@ def copy(self, target, follow_symlinks=True, dirs_exist_ok=False,
"""
Recursively copy this file or directory tree to the given destination.
"""
if not hasattr(target, '_copy_writer'):
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)

# Delegate to the target path's CopyWriter object.
Expand All @@ -366,7 +366,7 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
name = self.name
if not name:
raise ValueError(f"{self!r} has an empty name")
elif hasattr(target_dir, '_copy_writer'):
elif hasattr(target_dir, 'with_segments'):
target = target_dir / name
else:
target = self.with_segments(target_dir, name)
Expand Down
29 changes: 15 additions & 14 deletions Lib/pathlib/_local.py
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ def relative_to(self, other, *, walk_up=False):
The *walk_up* parameter controls whether `..` may be used to resolve
the path.
"""
if not isinstance(other, PurePath):
if not hasattr(other, 'with_segments'):
other = self.with_segments(other)
for step, path in enumerate(chain([other], other.parents)):
if path == self or path in self.parents:
Expand All @@ -492,7 +492,7 @@ def relative_to(self, other, *, walk_up=False):
def is_relative_to(self, other):
"""Return True if the path is relative to another path or False.
"""
if not isinstance(other, PurePath):
if not hasattr(other, 'with_segments'):
other = self.with_segments(other)
return other == self or other in self.parents

Expand Down Expand Up @@ -545,7 +545,7 @@ 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, PurePath):
if not hasattr(pattern, 'with_segments'):
pattern = self.with_segments(pattern)
if case_sensitive is None:
case_sensitive = self.parser is posixpath
Expand All @@ -564,7 +564,7 @@ def match(self, path_pattern, *, case_sensitive=None):
is matched. The recursive wildcard '**' is *not* supported by this
method.
"""
if not isinstance(path_pattern, PurePath):
if not hasattr(path_pattern, 'with_segments'):
path_pattern = self.with_segments(path_pattern)
if case_sensitive is None:
case_sensitive = self.parser is posixpath
Expand Down Expand Up @@ -1064,7 +1064,9 @@ def rename(self, target):
Returns the new Path instance pointing to the target path.
"""
os.rename(self, target)
return self.with_segments(target)
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)
return target

def replace(self, target):
"""
Expand All @@ -1077,7 +1079,9 @@ def replace(self, target):
Returns the new Path instance pointing to the target path.
"""
os.replace(self, target)
return self.with_segments(target)
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)
return target

_copy_writer = property(LocalCopyWriter)

Expand All @@ -1086,7 +1090,7 @@ def copy(self, target, follow_symlinks=True, dirs_exist_ok=False,
"""
Recursively copy this file or directory tree to the given destination.
"""
if not hasattr(target, '_copy_writer'):
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)

# Delegate to the target path's CopyWriter object.
Expand All @@ -1104,7 +1108,7 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
name = self.name
if not name:
raise ValueError(f"{self!r} has an empty name")
elif hasattr(target_dir, '_copy_writer'):
elif hasattr(target_dir, 'with_segments'):
target = target_dir / name
else:
target = self.with_segments(target_dir, name)
Expand All @@ -1118,16 +1122,13 @@ def move(self, target):
"""
# Use os.replace() if the target is os.PathLike and on the same FS.
try:
target_str = os.fspath(target)
target = self.with_segments(target)
except TypeError:
pass
else:
if not hasattr(target, '_copy_writer'):
target = self.with_segments(target_str)
ensure_different_files(self, target)
try:
os.replace(self, target_str)
return target
return self.replace(target)
except OSError as err:
if err.errno != EXDEV:
raise
Expand All @@ -1143,7 +1144,7 @@ def move_into(self, target_dir):
name = self.name
if not name:
raise ValueError(f"{self!r} has an empty name")
elif hasattr(target_dir, '_copy_writer'):
elif hasattr(target_dir, 'with_segments'):
target = target_dir / name
else:
target = self.with_segments(target_dir, name)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Apply type conversion consistently in :class:`pathlib.PurePath` and
:class:`~pathlib.Path` methods can accept a path object as an argument, such
as :meth:`~pathlib.PurePath.match` and :meth:`~pathlib.Path.rename`. The
argument is now converted to path object if it lacks a
:meth:`~pathlib.PurePath.with_segments` attribute, and not otherwise.
Loading