mirror of
https://github.com/python/cpython.git
synced 2025-12-23 09:19:18 +00:00
Merge 3e62ecf99c into f9704f1d84
This commit is contained in:
commit
db0266790a
5 changed files with 72 additions and 10 deletions
|
|
@ -1590,16 +1590,23 @@ Copying, moving and deleting
|
|||
.. versionadded:: 3.14
|
||||
|
||||
|
||||
.. method:: Path.copy_into(target_dir, *, follow_symlinks=True, \
|
||||
.. method:: Path.copy_into(target_dir, *, exist_ok=True, follow_symlinks=True, \
|
||||
preserve_metadata=False)
|
||||
|
||||
Copy this file or directory tree into the given *target_dir*, which should
|
||||
be an existing directory. Other arguments are handled identically to
|
||||
:meth:`Path.copy`. Returns a new :class:`!Path` instance pointing to the
|
||||
be an existing directory. If *exist_ok* is true (the default), copying a file
|
||||
overwrites an existing file with the same name and copying a directory merges
|
||||
its contents into an existing directory with the same name. If *exist_ok* is
|
||||
false, :exc:`FileExistsError` is raised if *target_dir* already contains an
|
||||
entry with the same name as the source. Other arguments are handled identically
|
||||
to :meth:`Path.copy`. Returns a new :class:`!Path` instance pointing to the
|
||||
copy.
|
||||
|
||||
.. versionadded:: 3.14
|
||||
|
||||
.. versionchanged:: 3.15
|
||||
The *exist_ok* parameter is added to allow copying duplicate directories.
|
||||
|
||||
|
||||
.. method:: Path.rename(target)
|
||||
|
||||
|
|
|
|||
|
|
@ -1301,18 +1301,36 @@ class Path(PurePath):
|
|||
target._copy_from(self, **kwargs)
|
||||
return target.joinpath() # Empty join to ensure fresh metadata.
|
||||
|
||||
def copy_into(self, target_dir, **kwargs):
|
||||
def copy_into(self, target_dir, exist_ok=True, **kwargs):
|
||||
"""
|
||||
Copy this file or directory tree into the given existing directory.
|
||||
"""
|
||||
name = self.name
|
||||
if not name:
|
||||
raise ValueError(f"{self!r} has an empty name")
|
||||
elif hasattr(target_dir, 'with_segments'):
|
||||
target = target_dir / name
|
||||
else:
|
||||
target = self.with_segments(target_dir, name)
|
||||
return self.copy(target, **kwargs)
|
||||
|
||||
parent = target_dir if hasattr(target_dir, "with_segments") else self.with_segments(target_dir)
|
||||
|
||||
is_dir = getattr(parent, "is_dir", None)
|
||||
if callable(is_dir) and not is_dir():
|
||||
raise ValueError(f"{parent!r} is not a directory")
|
||||
|
||||
dest = parent / name
|
||||
|
||||
if not exist_ok and dest.exists():
|
||||
raise FileExistsError(EEXIST, "File exists", str(dest))
|
||||
|
||||
if self.is_dir():
|
||||
if dest.exists():
|
||||
if not dest.is_dir():
|
||||
raise ValueError(f"{dest!r} is not a directory")
|
||||
else:
|
||||
dest.mkdir()
|
||||
for child in self.iterdir():
|
||||
child.copy_into(dest, exist_ok=exist_ok, **kwargs)
|
||||
return dest.joinpath()
|
||||
|
||||
return self.copy(dest, **kwargs)
|
||||
|
||||
def _copy_from(self, source, follow_symlinks=True, preserve_metadata=False):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -386,13 +386,22 @@ class _ReadablePath(_JoinablePath):
|
|||
target._copy_from(self, **kwargs)
|
||||
return target.joinpath() # Empty join to ensure fresh metadata.
|
||||
|
||||
def copy_into(self, target_dir, **kwargs):
|
||||
def copy_into(self, target_dir, exist_ok=True, **kwargs):
|
||||
"""
|
||||
Copy this file or directory tree into the given existing directory.
|
||||
"""
|
||||
name = self.name
|
||||
if not name:
|
||||
raise ValueError(f"{self!r} has an empty name")
|
||||
|
||||
target = target_dir / name
|
||||
if hasattr(target, 'info') and self.info.is_dir() and target.info.exists() and target.info.is_dir():
|
||||
if not exist_ok:
|
||||
raise FileExistsError("File exists", str(target))
|
||||
for child in self.iterdir():
|
||||
child.copy_into(target, exist_ok=exist_ok, **kwargs)
|
||||
return target.joinpath()
|
||||
|
||||
return self.copy(target_dir / name, **kwargs)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -146,6 +146,33 @@ class CopyTestBase:
|
|||
self.target_ground.create_dir(target_dir)
|
||||
self.assertRaises(ValueError, source.copy_into, target_dir)
|
||||
|
||||
def test_copy_dir_into_existing_dir_merges_when_exist_ok_true(self):
|
||||
if isinstance(self.target_root, WritableZipPath):
|
||||
self.skipTest('needs local target')
|
||||
source = self.source_root / 'dirC'
|
||||
target_dir = self.target_root
|
||||
preexisting = target_dir / 'dirC'
|
||||
self.target_ground.create_dir(preexisting)
|
||||
self.target_ground.create_file(preexisting / 'pre.txt', b'pre\n')
|
||||
|
||||
result = source.copy_into(target_dir)
|
||||
self.assertEqual(result, preexisting)
|
||||
|
||||
self.assertTrue(self.target_ground.isfile(preexisting / 'pre.txt'))
|
||||
|
||||
self.assertTrue(self.target_ground.isfile(preexisting / 'fileC'))
|
||||
self.assertTrue(self.target_ground.isdir(preexisting / 'dirD'))
|
||||
self.assertTrue(self.target_ground.isfile(preexisting / 'dirD' / 'fileD'))
|
||||
|
||||
def test_copy_dir_into_existing_dir_exist_ok_false(self):
|
||||
if isinstance(self.target_root, WritableZipPath):
|
||||
self.skipTest('needs local target')
|
||||
source = self.source_root / 'dirC'
|
||||
target_dir = self.target_root
|
||||
self.target_ground.create_dir(target_dir / 'dirC')
|
||||
with self.assertRaises(FileExistsError):
|
||||
source.copy_into(target_dir, exist_ok=False)
|
||||
|
||||
|
||||
class ZipToZipPathCopyTest(CopyTestBase, unittest.TestCase):
|
||||
source_ground = ZipPathGround(ReadableZipPath)
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
Add ``exist_ok`` parameter to :meth:`!pathlib.Path.copy_into`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue