GH-130614: pathlib ABCs: improve support for receiving path metadata (#131259)

In the private pathlib ABCs, replace `_WritablePath._write_info()` with
`_WritablePath._copy_from()`. This provides the target path object with
more control over the copying process, including support for querying and
setting metadata *before* the path is created.

Adjust `_ReadablePath.copy()` so that it forwards its keyword arguments to
`_WritablePath._copy_from()` of the target path object. This allows us to
remove the unimplemented *preserve_metadata* argument in the ABC method,
making it a `Path` exclusive.
This commit is contained in:
Barney Gale 2025-03-16 06:11:20 +00:00 committed by GitHub
parent e82c2ca2a5
commit 563ab5cefe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 90 additions and 62 deletions

View file

@ -12,8 +12,8 @@
from abc import ABC, abstractmethod
from glob import _PathGlobber
from pathlib._os import magic_open, ensure_distinct_paths, ensure_different_files, copyfileobj
from pathlib import PurePath, Path
from pathlib._os import magic_open, ensure_distinct_paths, copy_file
from typing import Optional, Protocol, runtime_checkable
@ -332,18 +332,21 @@ def readlink(self):
"""
raise NotImplementedError
def copy(self, target, follow_symlinks=True, preserve_metadata=False):
def copy(self, target, **kwargs):
"""
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)
copy_file(self, target, follow_symlinks, preserve_metadata)
try:
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.
def copy_into(self, target_dir, *, follow_symlinks=True,
preserve_metadata=False):
def copy_into(self, target_dir, **kwargs):
"""
Copy this file or directory tree into the given existing directory.
"""
@ -354,8 +357,7 @@ def copy_into(self, target_dir, *, follow_symlinks=True,
target = target_dir / name
else:
target = self.with_segments(target_dir, name)
return self.copy(target, follow_symlinks=follow_symlinks,
preserve_metadata=preserve_metadata)
return self.copy(target, **kwargs)
class _WritablePath(_JoinablePath):
@ -409,11 +411,25 @@ def write_text(self, data, encoding=None, errors=None, newline=None):
with magic_open(self, mode='w', encoding=encoding, errors=errors, newline=newline) as f:
return f.write(data)
def _write_info(self, info, follow_symlinks=True):
def _copy_from(self, source, follow_symlinks=True):
"""
Write the given PathInfo to this path.
Recursively copy the given path to this path.
"""
pass
stack = [(source, self)]
while stack:
src, dst = stack.pop()
if not follow_symlinks and src.info.is_symlink():
dst.symlink_to(str(src.readlink()), src.info.is_dir())
elif src.info.is_dir():
children = src.iterdir()
dst.mkdir()
for child in children:
stack.append((child, dst.joinpath(child.name)))
else:
ensure_different_files(src, dst)
with magic_open(src, 'rb') as source_f:
with magic_open(dst, 'wb') as target_f:
copyfileobj(source_f, target_f)
_JoinablePath.register(PurePath)