GH-125413: Add private pathlib.Path method to write metadata (#130238)

Replace `WritablePath._copy_writer` with a new `_write_info()` method. This
method allows the target of a `copy()` to preserve metadata.

Replace `pathlib._os.CopyWriter` and `LocalCopyWriter` classes with new
`copy_file()` and `copy_info()` functions. The `copy_file()` function uses
`source_path.info` wherever possible to save on `stat()`s.
This commit is contained in:
Barney Gale 2025-02-26 21:07:27 +00:00 committed by GitHub
parent 5ba69e747f
commit b251d409f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 122 additions and 174 deletions

View file

@ -19,7 +19,11 @@ try:
except ImportError:
grp = None
from pathlib._os import LocalCopyWriter, PathInfo, DirEntryInfo, ensure_different_files
from pathlib._os import (
PathInfo, DirEntryInfo,
ensure_different_files, ensure_distinct_paths,
copy_file, copy_info,
)
__all__ = [
@ -799,6 +803,12 @@ class Path(PurePath):
with self.open(mode='w', encoding=encoding, errors=errors, newline=newline) as f:
return f.write(data)
def _write_info(self, info, follow_symlinks=True):
"""
Write the given PathInfo to this path.
"""
copy_info(info, self, follow_symlinks=follow_symlinks)
_remove_leading_dot = operator.itemgetter(slice(2, None))
_remove_trailing_slash = operator.itemgetter(slice(-1))
@ -1083,8 +1093,6 @@ class Path(PurePath):
target = self.with_segments(target)
return target
_copy_writer = property(LocalCopyWriter)
def copy(self, target, follow_symlinks=True, dirs_exist_ok=False,
preserve_metadata=False):
"""
@ -1092,13 +1100,8 @@ class Path(PurePath):
"""
if not hasattr(target, 'with_segments'):
target = self.with_segments(target)
# Delegate to the target path's CopyWriter object.
try:
create = target._copy_writer._create
except AttributeError:
raise TypeError(f"Target is not writable: {target}") from None
create(self, follow_symlinks, dirs_exist_ok, preserve_metadata)
ensure_distinct_paths(self, target)
copy_file(self, target, follow_symlinks, dirs_exist_ok, preserve_metadata)
return target.joinpath() # Empty join to ensure fresh metadata.
def copy_into(self, target_dir, *, follow_symlinks=True,