mirror of
https://github.com/python/cpython.git
synced 2025-07-23 03:05:38 +00:00
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:
parent
6239d41527
commit
88fc0655d4
5 changed files with 187 additions and 11 deletions
|
@ -781,7 +781,32 @@ class PathBase(PurePathBase):
|
|||
"""
|
||||
raise UnsupportedOperation(self._unsupported_msg('mkdir()'))
|
||||
|
||||
def copy(self, target, follow_symlinks=True):
|
||||
# Metadata keys supported by this path type.
|
||||
_readable_metadata = _writable_metadata = frozenset()
|
||||
|
||||
def _read_metadata(self, keys=None, *, follow_symlinks=True):
|
||||
"""
|
||||
Returns path metadata as a dict with string keys.
|
||||
"""
|
||||
raise UnsupportedOperation(self._unsupported_msg('_read_metadata()'))
|
||||
|
||||
def _write_metadata(self, metadata, *, follow_symlinks=True):
|
||||
"""
|
||||
Sets path metadata from the given dict with string keys.
|
||||
"""
|
||||
raise UnsupportedOperation(self._unsupported_msg('_write_metadata()'))
|
||||
|
||||
def _copy_metadata(self, target, *, follow_symlinks=True):
|
||||
"""
|
||||
Copies metadata (permissions, timestamps, etc) from this path to target.
|
||||
"""
|
||||
# Metadata types supported by both source and target.
|
||||
keys = self._readable_metadata & target._writable_metadata
|
||||
if keys:
|
||||
metadata = self._read_metadata(keys, follow_symlinks=follow_symlinks)
|
||||
target._write_metadata(metadata, follow_symlinks=follow_symlinks)
|
||||
|
||||
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
|
||||
|
@ -793,6 +818,8 @@ class PathBase(PurePathBase):
|
|||
raise OSError(f"{self!r} and {target!r} are the same file")
|
||||
if not follow_symlinks and self.is_symlink():
|
||||
target.symlink_to(self.readlink())
|
||||
if preserve_metadata:
|
||||
self._copy_metadata(target, follow_symlinks=False)
|
||||
return
|
||||
with self.open('rb') as source_f:
|
||||
try:
|
||||
|
@ -805,6 +832,8 @@ class PathBase(PurePathBase):
|
|||
f'Directory does not exist: {target}') from e
|
||||
else:
|
||||
raise
|
||||
if preserve_metadata:
|
||||
self._copy_metadata(target)
|
||||
|
||||
def copytree(self, target, *, follow_symlinks=True, dirs_exist_ok=False,
|
||||
ignore=None, on_error=None):
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue