Skip to content

Commit

Permalink
Return the target as a Path object.
Browse files Browse the repository at this point in the history
  • Loading branch information
barneygale committed Aug 7, 2024
1 parent 5ce229b commit 5a94525
Show file tree
Hide file tree
Showing 3 changed files with 44 additions and 27 deletions.
3 changes: 2 additions & 1 deletion Doc/library/pathlib.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1542,7 +1542,8 @@ Copying, renaming and deleting
.. method:: Path.copy(target, *, follow_symlinks=True, dirs_exist_ok=False, \
preserve_metadata=False, ignore=None, on_error=None)

Copy this file or directory tree to the given *target*.
Copy this file or directory tree to the given *target*, and return a new
:class:`!Path` instance pointing to *target*.

If the source is a file, the target will be replaced if it is an existing
file. If the source is a symlink and *follow_symlinks* is true (the
Expand Down
23 changes: 12 additions & 11 deletions Lib/pathlib/_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -852,26 +852,27 @@ def on_error(err):
raise err
stack = [(self, target)]
while stack:
source, target = stack.pop()
src, dst = stack.pop()
try:
if not follow_symlinks and source.is_symlink():
target._symlink_to_target_of(source)
if not follow_symlinks and src.is_symlink():
dst._symlink_to_target_of(src)
if preserve_metadata:
source._copy_metadata(target, follow_symlinks=False)
elif source.is_dir():
children = source.iterdir()
target.mkdir(exist_ok=dirs_exist_ok)
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, target.joinpath(child.name)))
stack.append((child, dst.joinpath(child.name)))
if preserve_metadata:
source._copy_metadata(target)
src._copy_metadata(dst)
else:
source._copy_data(target)
src._copy_data(dst)
if preserve_metadata:
source._copy_metadata(target)
src._copy_metadata(dst)
except OSError as err:
on_error(err)
return target

def rename(self, target):
"""
Expand Down
45 changes: 30 additions & 15 deletions Lib/test/test_pathlib/test_pathlib_abc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1731,7 +1731,8 @@ def test_copy_file(self):
base = self.cls(self.base)
source = base / 'fileA'
target = base / 'copyA'
source.copy(target)
result = source.copy(target)
self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertEqual(source.read_text(), target.read_text())

Expand All @@ -1740,7 +1741,8 @@ def test_copy_symlink_follow_symlinks_true(self):
base = self.cls(self.base)
source = base / 'linkA'
target = base / 'copyA'
source.copy(target)
result = source.copy(target)
self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertFalse(target.is_symlink())
self.assertEqual(source.read_text(), target.read_text())
Expand All @@ -1750,7 +1752,8 @@ def test_copy_symlink_follow_symlinks_false(self):
base = self.cls(self.base)
source = base / 'linkA'
target = base / 'copyA'
source.copy(target, follow_symlinks=False)
result = source.copy(target, follow_symlinks=False)
self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertEqual(source.readlink(), target.readlink())
Expand All @@ -1760,7 +1763,8 @@ def test_copy_directory_symlink_follow_symlinks_false(self):
base = self.cls(self.base)
source = base / 'linkB'
target = base / 'copyA'
source.copy(target, follow_symlinks=False)
result = source.copy(target, follow_symlinks=False)
self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertEqual(source.readlink(), target.readlink())
Expand All @@ -1769,7 +1773,8 @@ def test_copy_file_to_existing_file(self):
base = self.cls(self.base)
source = base / 'fileA'
target = base / 'dirB' / 'fileB'
source.copy(target)
result = source.copy(target)
self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertEqual(source.read_text(), target.read_text())

Expand All @@ -1786,7 +1791,8 @@ def test_copy_file_to_existing_symlink(self):
source = base / 'dirB' / 'fileB'
target = base / 'linkA'
real_target = base / 'fileA'
source.copy(target)
result = source.copy(target)
self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertTrue(real_target.exists())
Expand All @@ -1799,7 +1805,8 @@ def test_copy_file_to_existing_symlink_follow_symlinks_false(self):
source = base / 'dirB' / 'fileB'
target = base / 'linkA'
real_target = base / 'fileA'
source.copy(target, follow_symlinks=False)
result = source.copy(target, follow_symlinks=False)
self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertTrue(target.is_symlink())
self.assertTrue(real_target.exists())
Expand All @@ -1811,15 +1818,17 @@ def test_copy_file_empty(self):
source = base / 'empty'
target = base / 'copyA'
source.write_bytes(b'')
source.copy(target)
result = source.copy(target)
self.assertEqual(result, target)
self.assertTrue(target.exists())
self.assertEqual(target.read_bytes(), b'')

def test_copy_dir_simple(self):
base = self.cls(self.base)
source = base / 'dirC'
target = base / 'copyC'
source.copy(target)
result = source.copy(target)
self.assertEqual(result, target)
self.assertTrue(target.is_dir())
self.assertTrue(target.joinpath('dirD').is_dir())
self.assertTrue(target.joinpath('dirD', 'fileD').is_file())
Expand All @@ -1845,7 +1854,8 @@ def ordered_walk(path):

# Perform the copy
target = base / 'copyC'
source.copy(target, follow_symlinks=follow_symlinks)
result = source.copy(target, follow_symlinks=follow_symlinks)
self.assertEqual(result, target)

# Compare the source and target trees
source_walk = ordered_walk(source)
Expand Down Expand Up @@ -1888,7 +1898,8 @@ def test_copy_dir_to_existing_directory_dirs_exist_ok(self):
target = base / 'copyC'
target.mkdir()
target.joinpath('dirD').mkdir()
source.copy(target, dirs_exist_ok=True)
result = source.copy(target, dirs_exist_ok=True)
self.assertEqual(result, target)
self.assertTrue(target.is_dir())
self.assertTrue(target.joinpath('dirD').is_dir())
self.assertTrue(target.joinpath('dirD', 'fileD').is_file())
Expand All @@ -1903,7 +1914,8 @@ def test_copy_missing_on_error(self):
source = base / 'foo'
target = base / 'copyA'
errors = []
source.copy(target, on_error=errors.append)
result = source.copy(target, on_error=errors.append)
self.assertEqual(result, target)
self.assertEqual(len(errors), 1)
self.assertIsInstance(errors[0], FileNotFoundError)

Expand All @@ -1915,7 +1927,8 @@ def test_copy_dir_ignore_false(self):
def ignore_false(path):
ignores.append(path)
return False
source.copy(target, ignore=ignore_false)
result = source.copy(target, ignore=ignore_false)
self.assertEqual(result, target)
self.assertEqual(set(ignores), {
source / 'dirD',
source / 'dirD' / 'fileD',
Expand All @@ -1939,7 +1952,8 @@ def test_copy_dir_ignore_true(self):
def ignore_true(path):
ignores.append(path)
return True
source.copy(target, ignore=ignore_true)
result = source.copy(target, ignore=ignore_true)
self.assertEqual(result, target)
self.assertEqual(set(ignores), {
source / 'dirD',
source / 'fileC',
Expand All @@ -1962,7 +1976,8 @@ def test_copy_dangling_symlink(self):
self.assertRaises(FileNotFoundError, source.copy, target)

target2 = base / 'target2'
source.copy(target2, follow_symlinks=False)
result = source.copy(target2, follow_symlinks=False)
self.assertEqual(result, target2)
self.assertTrue(target2.joinpath('link').is_symlink())
self.assertEqual(target2.joinpath('link').readlink(), self.cls('nonexistent'))

Expand Down

0 comments on commit 5a94525

Please sign in to comment.