Fixed #1540112: now shutil.copytree will let you provide your own copy() function

This commit is contained in:
Tarek Ziadé 2010-04-19 22:30:51 +00:00
parent 5fb313bb04
commit 5340db3803
4 changed files with 51 additions and 27 deletions

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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.