mirror of https://github.com/python/cpython
Fixed #1540112: now shutil.copytree will let you provide your own copy() function
This commit is contained in:
parent
5fb313bb04
commit
5340db3803
|
@ -87,7 +87,7 @@ Directory and files operations
|
||||||
match one of the glob-style *patterns* provided. See the example below.
|
match one of the glob-style *patterns* provided. See the example below.
|
||||||
|
|
||||||
|
|
||||||
.. function:: copytree(src, dst, symlinks=False, ignore=None)
|
.. function:: copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2)
|
||||||
|
|
||||||
Recursively copy an entire directory tree rooted at *src*. The destination
|
Recursively copy an entire directory tree rooted at *src*. The destination
|
||||||
directory, named by *dst*, must not already exist; it will be created as well
|
directory, named by *dst*, must not already exist; it will be created as well
|
||||||
|
@ -111,9 +111,14 @@ Directory and files operations
|
||||||
|
|
||||||
If exception(s) occur, an :exc:`Error` is raised with a list of reasons.
|
If exception(s) occur, an :exc:`Error` is raised with a list of reasons.
|
||||||
|
|
||||||
The source code for this should be considered an example rather than the
|
If *copy_function* is given, it must be a callable that will be used
|
||||||
ultimate tool.
|
to copy each file. It will be called with the source path and the
|
||||||
|
destination path as arguments. By default, :func:`copy2` is used, but any
|
||||||
|
function that supports the same signature (like :func:`copy`) can be used.
|
||||||
|
|
||||||
|
.. versionchanged:: 3.2
|
||||||
|
Added the *copy_function* argument to be able to provide a custom copy
|
||||||
|
function.
|
||||||
|
|
||||||
.. function:: rmtree(path, ignore_errors=False, onerror=None)
|
.. function:: rmtree(path, ignore_errors=False, onerror=None)
|
||||||
|
|
||||||
|
|
|
@ -147,8 +147,8 @@ def ignore_patterns(*patterns):
|
||||||
return set(ignored_names)
|
return set(ignored_names)
|
||||||
return _ignore_patterns
|
return _ignore_patterns
|
||||||
|
|
||||||
def copytree(src, dst, symlinks=False, ignore=None):
|
def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2):
|
||||||
"""Recursively copy a directory tree using copy2().
|
"""Recursively copy a directory tree.
|
||||||
|
|
||||||
The destination directory must not already exist.
|
The destination directory must not already exist.
|
||||||
If exception(s) occur, an Error is raised with a list of reasons.
|
If exception(s) occur, an Error is raised with a list of reasons.
|
||||||
|
@ -170,7 +170,10 @@ def copytree(src, dst, symlinks=False, ignore=None):
|
||||||
list of names relative to the `src` directory that should
|
list of names relative to the `src` directory that should
|
||||||
not be copied.
|
not be copied.
|
||||||
|
|
||||||
XXX Consider this example code rather than the ultimate tool.
|
The optional copy_function argument is a callable that will be used
|
||||||
|
to copy each file. It will be called with the source path and the
|
||||||
|
destination path as arguments. By default, copy2() is used, but any
|
||||||
|
function that supports the same signature (like copy()) can be used.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
names = os.listdir(src)
|
names = os.listdir(src)
|
||||||
|
@ -191,10 +194,10 @@ def copytree(src, dst, symlinks=False, ignore=None):
|
||||||
linkto = os.readlink(srcname)
|
linkto = os.readlink(srcname)
|
||||||
os.symlink(linkto, dstname)
|
os.symlink(linkto, dstname)
|
||||||
elif os.path.isdir(srcname):
|
elif os.path.isdir(srcname):
|
||||||
copytree(srcname, dstname, symlinks, ignore)
|
copytree(srcname, dstname, symlinks, ignore, copy_function)
|
||||||
else:
|
else:
|
||||||
# Will raise a SpecialFileError for unsupported file types
|
# Will raise a SpecialFileError for unsupported file types
|
||||||
copy2(srcname, dstname)
|
copy_function(srcname, dstname)
|
||||||
# catch the Error from the recursive copytree so that we can
|
# catch the Error from the recursive copytree so that we can
|
||||||
# continue with other files
|
# continue with other files
|
||||||
except Error as err:
|
except Error as err:
|
||||||
|
|
|
@ -74,6 +74,7 @@ class TestShutil(unittest.TestCase):
|
||||||
d = tempfile.mkdtemp()
|
d = tempfile.mkdtemp()
|
||||||
self.tempdirs.append(d)
|
self.tempdirs.append(d)
|
||||||
return d
|
return d
|
||||||
|
|
||||||
def test_rmtree_errors(self):
|
def test_rmtree_errors(self):
|
||||||
# filename is guaranteed not to exist
|
# filename is guaranteed not to exist
|
||||||
filename = tempfile.mktemp()
|
filename = tempfile.mktemp()
|
||||||
|
@ -140,11 +141,12 @@ class TestShutil(unittest.TestCase):
|
||||||
self.assertRaises(OSError, shutil.rmtree, path)
|
self.assertRaises(OSError, shutil.rmtree, path)
|
||||||
os.remove(path)
|
os.remove(path)
|
||||||
|
|
||||||
|
def _write_data(self, path, data):
|
||||||
|
f = open(path, "w")
|
||||||
|
f.write(data)
|
||||||
|
f.close()
|
||||||
|
|
||||||
def test_copytree_simple(self):
|
def test_copytree_simple(self):
|
||||||
def write_data(path, data):
|
|
||||||
f = open(path, "w")
|
|
||||||
f.write(data)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def read_data(path):
|
def read_data(path):
|
||||||
f = open(path)
|
f = open(path)
|
||||||
|
@ -154,11 +156,9 @@ class TestShutil(unittest.TestCase):
|
||||||
|
|
||||||
src_dir = tempfile.mkdtemp()
|
src_dir = tempfile.mkdtemp()
|
||||||
dst_dir = os.path.join(tempfile.mkdtemp(), 'destination')
|
dst_dir = os.path.join(tempfile.mkdtemp(), 'destination')
|
||||||
|
self._write_data(os.path.join(src_dir, 'test.txt'), '123')
|
||||||
write_data(os.path.join(src_dir, 'test.txt'), '123')
|
|
||||||
|
|
||||||
os.mkdir(os.path.join(src_dir, 'test_dir'))
|
os.mkdir(os.path.join(src_dir, 'test_dir'))
|
||||||
write_data(os.path.join(src_dir, 'test_dir', 'test.txt'), '456')
|
self._write_data(os.path.join(src_dir, 'test_dir', 'test.txt'), '456')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
shutil.copytree(src_dir, dst_dir)
|
shutil.copytree(src_dir, dst_dir)
|
||||||
|
@ -187,11 +187,6 @@ class TestShutil(unittest.TestCase):
|
||||||
|
|
||||||
def test_copytree_with_exclude(self):
|
def test_copytree_with_exclude(self):
|
||||||
|
|
||||||
def write_data(path, data):
|
|
||||||
f = open(path, "w")
|
|
||||||
f.write(data)
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
def read_data(path):
|
def read_data(path):
|
||||||
f = open(path)
|
f = open(path)
|
||||||
data = f.read()
|
data = f.read()
|
||||||
|
@ -204,16 +199,18 @@ class TestShutil(unittest.TestCase):
|
||||||
src_dir = tempfile.mkdtemp()
|
src_dir = tempfile.mkdtemp()
|
||||||
try:
|
try:
|
||||||
dst_dir = join(tempfile.mkdtemp(), 'destination')
|
dst_dir = join(tempfile.mkdtemp(), 'destination')
|
||||||
write_data(join(src_dir, 'test.txt'), '123')
|
self._write_data(join(src_dir, 'test.txt'), '123')
|
||||||
write_data(join(src_dir, 'test.tmp'), '123')
|
self._write_data(join(src_dir, 'test.tmp'), '123')
|
||||||
os.mkdir(join(src_dir, 'test_dir'))
|
os.mkdir(join(src_dir, 'test_dir'))
|
||||||
write_data(join(src_dir, 'test_dir', 'test.txt'), '456')
|
self._write_data(join(src_dir, 'test_dir', 'test.txt'), '456')
|
||||||
os.mkdir(join(src_dir, 'test_dir2'))
|
os.mkdir(join(src_dir, 'test_dir2'))
|
||||||
write_data(join(src_dir, 'test_dir2', 'test.txt'), '456')
|
self._write_data(join(src_dir, 'test_dir2', 'test.txt'), '456')
|
||||||
os.mkdir(join(src_dir, 'test_dir2', 'subdir'))
|
os.mkdir(join(src_dir, 'test_dir2', 'subdir'))
|
||||||
os.mkdir(join(src_dir, 'test_dir2', 'subdir2'))
|
os.mkdir(join(src_dir, 'test_dir2', 'subdir2'))
|
||||||
write_data(join(src_dir, 'test_dir2', 'subdir', 'test.txt'), '456')
|
self._write_data(join(src_dir, 'test_dir2', 'subdir', 'test.txt'),
|
||||||
write_data(join(src_dir, 'test_dir2', 'subdir2', 'test.py'), '456')
|
'456')
|
||||||
|
self._write_data(join(src_dir, 'test_dir2', 'subdir2', 'test.py'),
|
||||||
|
'456')
|
||||||
|
|
||||||
|
|
||||||
# testing glob-like patterns
|
# testing glob-like patterns
|
||||||
|
@ -339,6 +336,21 @@ class TestShutil(unittest.TestCase):
|
||||||
shutil.rmtree(TESTFN, ignore_errors=True)
|
shutil.rmtree(TESTFN, ignore_errors=True)
|
||||||
shutil.rmtree(TESTFN2, ignore_errors=True)
|
shutil.rmtree(TESTFN2, ignore_errors=True)
|
||||||
|
|
||||||
|
def test_copytree_special_func(self):
|
||||||
|
|
||||||
|
src_dir = self.mkdtemp()
|
||||||
|
dst_dir = os.path.join(self.mkdtemp(), 'destination')
|
||||||
|
self._write_data(os.path.join(src_dir, 'test.txt'), '123')
|
||||||
|
os.mkdir(os.path.join(src_dir, 'test_dir'))
|
||||||
|
self._write_data(os.path.join(src_dir, 'test_dir', 'test.txt'), '456')
|
||||||
|
|
||||||
|
copied = []
|
||||||
|
def _copy(src, dst):
|
||||||
|
copied.append((src, dst))
|
||||||
|
|
||||||
|
shutil.copytree(src_dir, dst_dir, copy_function=_copy)
|
||||||
|
self.assertEquals(len(copied), 2)
|
||||||
|
|
||||||
@unittest.skipUnless(zlib, "requires zlib")
|
@unittest.skipUnless(zlib, "requires zlib")
|
||||||
def test_make_tarball(self):
|
def test_make_tarball(self):
|
||||||
# creating something to tar
|
# creating something to tar
|
||||||
|
@ -728,6 +740,7 @@ class TestMove(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(TESTFN, ignore_errors=True)
|
shutil.rmtree(TESTFN, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
def test_main():
|
def test_main():
|
||||||
support.run_unittest(TestShutil, TestMove)
|
support.run_unittest(TestShutil, TestMove)
|
||||||
|
|
||||||
|
|
|
@ -318,6 +318,9 @@ C-API
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #1540112: Now allowing the choice of a copy function in
|
||||||
|
shutil.copytree.
|
||||||
|
|
||||||
- Issue #4814: timeout parameter is now applied also for connections resulting
|
- Issue #4814: timeout parameter is now applied also for connections resulting
|
||||||
from PORT/EPRT commands.
|
from PORT/EPRT commands.
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue