bpo-20849: add dirs_exist_ok arg to shutil.copytree (patch by Josh Bronson)

This commit is contained in:
jab 2018-12-28 13:03:40 -05:00 committed by Giampaolo Rodola
parent ed57e13df6
commit 9e00d9e88f
5 changed files with 60 additions and 17 deletions

View File

@ -209,14 +209,16 @@ Directory and files operations
.. function:: copytree(src, dst, symlinks=False, ignore=None, \
copy_function=copy2, ignore_dangling_symlinks=False)
copy_function=copy2, ignore_dangling_symlinks=False, \
dirs_exist_ok=False)
Recursively copy an entire directory tree rooted at *src*, returning the
destination directory. The destination
directory, named by *dst*, must not already exist; it will be created as
well as missing parent directories. Permissions and times of directories
are copied with :func:`copystat`, individual files are copied using
:func:`shutil.copy2`.
Recursively copy an entire directory tree rooted at *src* to a directory
named *dst* and return the destination directory. *dirs_exist_ok* dictates
whether to raise an exception in case *dst* or any missing parent directory
already exists.
Permissions and times of directories are copied with :func:`copystat`,
individual files are copied using :func:`shutil.copy2`.
If *symlinks* is true, symbolic links in the source tree are represented as
symbolic links in the new tree and the metadata of the original links will
@ -262,6 +264,9 @@ Directory and files operations
copy the file more efficiently. See
:ref:`shutil-platform-dependent-efficient-copy-operations` section.
.. versionadded:: 3.8
The *dirs_exist_ok* parameter.
.. function:: rmtree(path, ignore_errors=False, onerror=None)
.. index:: single: directory; deleting

View File

@ -196,6 +196,14 @@ pathlib
contain characters unrepresentable at the OS level.
(Contributed by Serhiy Storchaka in :issue:`33721`.)
shutil
------
:func:`shutil.copytree` now accepts a new ``dirs_exist_ok`` keyword argument.
(Contributed by Josh Bronson in :issue:`20849`.)
ssl
---
@ -284,7 +292,6 @@ Optimizations
syscalls is reduced by 38% making :func:`shutil.copytree` especially faster
on network filesystems. (Contributed by Giampaolo Rodola' in :issue:`33695`.)
* The default protocol in the :mod:`pickle` module is now Protocol 4,
first introduced in Python 3.4. It offers better performance and smaller
size compared to Protocol 3 available since Python 3.0.

View File

@ -432,13 +432,13 @@ def ignore_patterns(*patterns):
return _ignore_patterns
def _copytree(entries, src, dst, symlinks, ignore, copy_function,
ignore_dangling_symlinks):
ignore_dangling_symlinks, dirs_exist_ok=False):
if ignore is not None:
ignored_names = ignore(src, set(os.listdir(src)))
else:
ignored_names = set()
os.makedirs(dst)
os.makedirs(dst, exist_ok=dirs_exist_ok)
errors = []
use_srcentry = copy_function is copy2 or copy_function is copy
@ -461,14 +461,15 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
# ignore dangling symlink if the flag is on
if not os.path.exists(linkto) and ignore_dangling_symlinks:
continue
# otherwise let the copy occurs. copy2 will raise an error
# otherwise let the copy occur. copy2 will raise an error
if srcentry.is_dir():
copytree(srcobj, dstname, symlinks, ignore,
copy_function)
copy_function, dirs_exist_ok=dirs_exist_ok)
else:
copy_function(srcobj, dstname)
elif srcentry.is_dir():
copytree(srcobj, dstname, symlinks, ignore, copy_function)
copytree(srcobj, dstname, symlinks, ignore, copy_function,
dirs_exist_ok=dirs_exist_ok)
else:
# Will raise a SpecialFileError for unsupported file types
copy_function(srcentry, dstname)
@ -489,10 +490,12 @@ def _copytree(entries, src, dst, symlinks, ignore, copy_function,
return dst
def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
ignore_dangling_symlinks=False):
"""Recursively copy a directory tree.
ignore_dangling_symlinks=False, dirs_exist_ok=False):
"""Recursively copy a directory tree and return the destination directory.
dirs_exist_ok dictates whether to raise an exception in case dst or any
missing parent directory already exists.
The destination directory must not already exist.
If exception(s) occur, an Error is raised with a list of reasons.
If the optional symlinks flag is true, symbolic links in the
@ -527,7 +530,8 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
with os.scandir(src) as entries:
return _copytree(entries=entries, src=src, dst=dst, symlinks=symlinks,
ignore=ignore, copy_function=copy_function,
ignore_dangling_symlinks=ignore_dangling_symlinks)
ignore_dangling_symlinks=ignore_dangling_symlinks,
dirs_exist_ok=dirs_exist_ok)
# version vulnerable to race conditions
def _rmtree_unsafe(path, onerror):

View File

@ -691,6 +691,31 @@ class TestShutil(unittest.TestCase):
actual = read_file((dst_dir, 'test_dir', 'test.txt'))
self.assertEqual(actual, '456')
def test_copytree_dirs_exist_ok(self):
src_dir = tempfile.mkdtemp()
dst_dir = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, src_dir)
self.addCleanup(shutil.rmtree, dst_dir)
write_file((src_dir, 'nonexisting.txt'), '123')
os.mkdir(os.path.join(src_dir, 'existing_dir'))
os.mkdir(os.path.join(dst_dir, 'existing_dir'))
write_file((dst_dir, 'existing_dir', 'existing.txt'), 'will be replaced')
write_file((src_dir, 'existing_dir', 'existing.txt'), 'has been replaced')
shutil.copytree(src_dir, dst_dir, dirs_exist_ok=True)
self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'nonexisting.txt')))
self.assertTrue(os.path.isdir(os.path.join(dst_dir, 'existing_dir')))
self.assertTrue(os.path.isfile(os.path.join(dst_dir, 'existing_dir',
'existing.txt')))
actual = read_file((dst_dir, 'nonexisting.txt'))
self.assertEqual(actual, '123')
actual = read_file((dst_dir, 'existing_dir', 'existing.txt'))
self.assertEqual(actual, 'has been replaced')
with self.assertRaises(FileExistsError):
shutil.copytree(src_dir, dst_dir, dirs_exist_ok=False)
@support.skip_unless_symlink
def test_copytree_symlinks(self):
tmp_dir = self.mkdtemp()

View File

@ -0,0 +1,2 @@
shutil.copytree now accepts a new ``dirs_exist_ok`` keyword argument.
Patch by Josh Bronson.