mirror of
https://github.com/python/cpython.git
synced 2025-07-24 03:35:53 +00:00
GH-73991: Rework pathlib.Path.copytree()
into copy()
(#122369)
Rename `pathlib.Path.copy()` to `_copy_file()` (i.e. make it private.) Rename `pathlib.Path.copytree()` to `copy()`, and add support for copying non-directories. This simplifies the interface for users, and nicely complements the upcoming `move()` and `delete()` methods (which will also accept any type of file.) Co-authored-by: Adam Turner <9087854+AA-Turner@users.noreply.github.com>
This commit is contained in:
parent
ea70439bd2
commit
a6644d4464
10 changed files with 141 additions and 197 deletions
|
@ -5,8 +5,7 @@ import errno
|
|||
import stat
|
||||
import unittest
|
||||
|
||||
from pathlib._os import UnsupportedOperation
|
||||
from pathlib._abc import ParserBase, PurePathBase, PathBase
|
||||
from pathlib._abc import UnsupportedOperation, ParserBase, PurePathBase, PathBase
|
||||
import posixpath
|
||||
|
||||
from test.support import is_wasi
|
||||
|
@ -1732,23 +1731,18 @@ class DummyPathTest(DummyPurePathTest):
|
|||
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())
|
||||
|
||||
def test_copy_directory(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'dirA'
|
||||
target = base / 'copyA'
|
||||
with self.assertRaises(OSError):
|
||||
source.copy(target)
|
||||
|
||||
@needs_symlinks
|
||||
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())
|
||||
|
@ -1758,7 +1752,8 @@ class DummyPathTest(DummyPurePathTest):
|
|||
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())
|
||||
|
@ -1768,20 +1763,22 @@ class DummyPathTest(DummyPurePathTest):
|
|||
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())
|
||||
|
||||
def test_copy_to_existing_file(self):
|
||||
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())
|
||||
|
||||
def test_copy_to_existing_directory(self):
|
||||
def test_copy_file_to_existing_directory(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'fileA'
|
||||
target = base / 'dirA'
|
||||
|
@ -1789,12 +1786,13 @@ class DummyPathTest(DummyPurePathTest):
|
|||
source.copy(target)
|
||||
|
||||
@needs_symlinks
|
||||
def test_copy_to_existing_symlink(self):
|
||||
def test_copy_file_to_existing_symlink(self):
|
||||
base = self.cls(self.base)
|
||||
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())
|
||||
|
@ -1802,32 +1800,35 @@ class DummyPathTest(DummyPurePathTest):
|
|||
self.assertEqual(source.read_text(), real_target.read_text())
|
||||
|
||||
@needs_symlinks
|
||||
def test_copy_to_existing_symlink_follow_symlinks_false(self):
|
||||
def test_copy_file_to_existing_symlink_follow_symlinks_false(self):
|
||||
base = self.cls(self.base)
|
||||
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())
|
||||
self.assertFalse(real_target.is_symlink())
|
||||
self.assertEqual(source.read_text(), real_target.read_text())
|
||||
|
||||
def test_copy_empty(self):
|
||||
def test_copy_file_empty(self):
|
||||
base = self.cls(self.base)
|
||||
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_copytree_simple(self):
|
||||
def test_copy_dir_simple(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'dirC'
|
||||
target = base / 'copyC'
|
||||
source.copytree(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())
|
||||
|
@ -1837,7 +1838,7 @@ class DummyPathTest(DummyPurePathTest):
|
|||
self.assertTrue(target.joinpath('fileC').read_text(),
|
||||
"this is file C\n")
|
||||
|
||||
def test_copytree_complex(self, follow_symlinks=True):
|
||||
def test_copy_dir_complex(self, follow_symlinks=True):
|
||||
def ordered_walk(path):
|
||||
for dirpath, dirnames, filenames in path.walk(follow_symlinks=follow_symlinks):
|
||||
dirnames.sort()
|
||||
|
@ -1853,7 +1854,8 @@ class DummyPathTest(DummyPurePathTest):
|
|||
|
||||
# Perform the copy
|
||||
target = base / 'copyC'
|
||||
source.copytree(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)
|
||||
|
@ -1879,24 +1881,25 @@ class DummyPathTest(DummyPurePathTest):
|
|||
self.assertEqual(source_file.read_bytes(), target_file.read_bytes())
|
||||
self.assertEqual(source_file.readlink(), target_file.readlink())
|
||||
|
||||
def test_copytree_complex_follow_symlinks_false(self):
|
||||
self.test_copytree_complex(follow_symlinks=False)
|
||||
def test_copy_dir_complex_follow_symlinks_false(self):
|
||||
self.test_copy_dir_complex(follow_symlinks=False)
|
||||
|
||||
def test_copytree_to_existing_directory(self):
|
||||
def test_copy_dir_to_existing_directory(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'dirC'
|
||||
target = base / 'copyC'
|
||||
target.mkdir()
|
||||
target.joinpath('dirD').mkdir()
|
||||
self.assertRaises(FileExistsError, source.copytree, target)
|
||||
self.assertRaises(FileExistsError, source.copy, target)
|
||||
|
||||
def test_copytree_to_existing_directory_dirs_exist_ok(self):
|
||||
def test_copy_dir_to_existing_directory_dirs_exist_ok(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'dirC'
|
||||
target = base / 'copyC'
|
||||
target.mkdir()
|
||||
target.joinpath('dirD').mkdir()
|
||||
source.copytree(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())
|
||||
|
@ -1906,22 +1909,17 @@ class DummyPathTest(DummyPurePathTest):
|
|||
self.assertTrue(target.joinpath('fileC').read_text(),
|
||||
"this is file C\n")
|
||||
|
||||
def test_copytree_file(self):
|
||||
def test_copy_missing_on_error(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'fileA'
|
||||
target = base / 'copyA'
|
||||
self.assertRaises(NotADirectoryError, source.copytree, target)
|
||||
|
||||
def test_copytree_file_on_error(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'fileA'
|
||||
source = base / 'foo'
|
||||
target = base / 'copyA'
|
||||
errors = []
|
||||
source.copytree(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], NotADirectoryError)
|
||||
self.assertIsInstance(errors[0], FileNotFoundError)
|
||||
|
||||
def test_copytree_ignore_false(self):
|
||||
def test_copy_dir_ignore_false(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'dirC'
|
||||
target = base / 'copyC'
|
||||
|
@ -1929,7 +1927,8 @@ class DummyPathTest(DummyPurePathTest):
|
|||
def ignore_false(path):
|
||||
ignores.append(path)
|
||||
return False
|
||||
source.copytree(target, ignore=ignore_false)
|
||||
result = source.copy(target, ignore=ignore_false)
|
||||
self.assertEqual(result, target)
|
||||
self.assertEqual(set(ignores), {
|
||||
source / 'dirD',
|
||||
source / 'dirD' / 'fileD',
|
||||
|
@ -1945,7 +1944,7 @@ class DummyPathTest(DummyPurePathTest):
|
|||
self.assertTrue(target.joinpath('fileC').read_text(),
|
||||
"this is file C\n")
|
||||
|
||||
def test_copytree_ignore_true(self):
|
||||
def test_copy_dir_ignore_true(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'dirC'
|
||||
target = base / 'copyC'
|
||||
|
@ -1953,7 +1952,8 @@ class DummyPathTest(DummyPurePathTest):
|
|||
def ignore_true(path):
|
||||
ignores.append(path)
|
||||
return True
|
||||
source.copytree(target, ignore=ignore_true)
|
||||
result = source.copy(target, ignore=ignore_true)
|
||||
self.assertEqual(result, target)
|
||||
self.assertEqual(set(ignores), {
|
||||
source / 'dirD',
|
||||
source / 'fileC',
|
||||
|
@ -1965,7 +1965,7 @@ class DummyPathTest(DummyPurePathTest):
|
|||
self.assertFalse(target.joinpath('novel.txt').exists())
|
||||
|
||||
@needs_symlinks
|
||||
def test_copytree_dangling_symlink(self):
|
||||
def test_copy_dangling_symlink(self):
|
||||
base = self.cls(self.base)
|
||||
source = base / 'source'
|
||||
target = base / 'target'
|
||||
|
@ -1973,10 +1973,11 @@ class DummyPathTest(DummyPurePathTest):
|
|||
source.mkdir()
|
||||
source.joinpath('link').symlink_to('nonexistent')
|
||||
|
||||
self.assertRaises(FileNotFoundError, source.copytree, target)
|
||||
self.assertRaises(FileNotFoundError, source.copy, target)
|
||||
|
||||
target2 = base / 'target2'
|
||||
source.copytree(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'))
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue