GH-73991: Support preserving metadata in pathlib.Path.copy() (#120806)

Add *preserve_metadata* keyword-only argument to `pathlib.Path.copy()`, defaulting to false. When set to true, we copy timestamps, permissions, extended attributes and flags where available, like `shutil.copystat()`. The argument has no effect on Windows, where metadata is always copied.

Internally (in the pathlib ABCs), path types gain `_readable_metadata` and `_writable_metadata` attributes. These sets of strings describe what kinds of metadata can be retrieved and stored. We take an intersection of `source._readable_metadata` and `target._writable_metadata` to minimise reads/writes. A new `_read_metadata()` method accepts a set of metadata keys and returns a dict with those keys, and a new `_write_metadata()` method accepts a dict of metadata. We *might* make these public in future, but it's hard to justify while the ABCs are still private.
This commit is contained in:
Barney Gale 2024-07-06 17:18:39 +01:00 committed by GitHub
parent 6239d41527
commit 88fc0655d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 187 additions and 11 deletions

View file

@ -17,7 +17,8 @@ try:
except ImportError:
grp = None
from ._os import UnsupportedOperation, copyfile
from ._os import (UnsupportedOperation, copyfile, file_metadata_keys,
read_file_metadata, write_file_metadata)
from ._abc import PurePathBase, PathBase
@ -781,8 +782,12 @@ class Path(PathBase, PurePath):
if not exist_ok or not self.is_dir():
raise
_readable_metadata = _writable_metadata = file_metadata_keys
_read_metadata = read_file_metadata
_write_metadata = write_file_metadata
if copyfile:
def copy(self, target, follow_symlinks=True):
def copy(self, target, *, follow_symlinks=True, preserve_metadata=False):
"""
Copy the contents of this file to the given target. If this file is a
symlink and follow_symlinks is false, a symlink will be created at the
@ -799,7 +804,8 @@ class Path(PathBase, PurePath):
return
except UnsupportedOperation:
pass # Fall through to generic code.
PathBase.copy(self, target, follow_symlinks=follow_symlinks)
PathBase.copy(self, target, follow_symlinks=follow_symlinks,
preserve_metadata=preserve_metadata)
def chmod(self, mode, *, follow_symlinks=True):
"""