GH-73991: Prune pathlib.Path.copy() and copy_into() arguments (#123337)

Remove *ignore* and *on_error* arguments from `pathlib.Path.copy[_into]()`,
because these arguments are under-designed. Specifically:

- *ignore* is appropriated from `shutil.copytree()`, but it's not clear
  how it should apply when the user copies a non-directory. We've changed
  the callback signature from the `shutil` version, but I'm not confident
  the new signature is as good as it can be.
- *on_error* is a generalisation of `shutil.copytree()`'s error handling,
  which is to accumulate exceptions and raise a single `shutil.Error` at
  the end. It's not obvious which solution is better.

Additionally, this arguments may be challenging to implement in future user
subclasses of `PathBase`, which might utilise a native recursive copying
method.
This commit is contained in:
Barney Gale 2024-08-26 17:05:34 +01:00 committed by GitHub
parent 033d537cd4
commit 7bd6ebf696
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 21 additions and 108 deletions

View file

@ -865,48 +865,35 @@ class PathBase(PurePathBase):
raise
def copy(self, target, *, follow_symlinks=True, dirs_exist_ok=False,
preserve_metadata=False, ignore=None, on_error=None):
preserve_metadata=False):
"""
Recursively copy this file or directory tree to the given destination.
"""
if not isinstance(target, PathBase):
target = self.with_segments(target)
try:
self._ensure_distinct_path(target)
except OSError as err:
if on_error is None:
raise
on_error(err)
return
self._ensure_distinct_path(target)
stack = [(self, target)]
while stack:
src, dst = stack.pop()
try:
if not follow_symlinks and src.is_symlink():
dst._symlink_to_target_of(src)
if preserve_metadata:
src._copy_metadata(dst, follow_symlinks=False)
elif src.is_dir():
children = src.iterdir()
dst.mkdir(exist_ok=dirs_exist_ok)
for child in children:
if not (ignore and ignore(child)):
stack.append((child, dst.joinpath(child.name)))
if preserve_metadata:
src._copy_metadata(dst)
else:
src._copy_file(dst)
if preserve_metadata:
src._copy_metadata(dst)
except OSError as err:
if on_error is None:
raise
on_error(err)
if not follow_symlinks and src.is_symlink():
dst._symlink_to_target_of(src)
if preserve_metadata:
src._copy_metadata(dst, follow_symlinks=False)
elif src.is_dir():
children = src.iterdir()
dst.mkdir(exist_ok=dirs_exist_ok)
stack.extend((child, dst.joinpath(child.name))
for child in children)
if preserve_metadata:
src._copy_metadata(dst)
else:
src._copy_file(dst)
if preserve_metadata:
src._copy_metadata(dst)
return target
def copy_into(self, target_dir, *, follow_symlinks=True,
dirs_exist_ok=False, preserve_metadata=False, ignore=None,
on_error=None):
dirs_exist_ok=False, preserve_metadata=False):
"""
Copy this file or directory tree into the given existing directory.
"""
@ -919,8 +906,7 @@ class PathBase(PurePathBase):
target = self.with_segments(target_dir, name)
return self.copy(target, follow_symlinks=follow_symlinks,
dirs_exist_ok=dirs_exist_ok,
preserve_metadata=preserve_metadata, ignore=ignore,
on_error=on_error)
preserve_metadata=preserve_metadata)
def rename(self, target):
"""