Commit 9e00d9e8 authored by jab's avatar jab Committed by Giampaolo Rodola

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

parent ed57e13d
......@@ -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
......
......@@ -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.
......
......@@ -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):
......
......@@ -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()
......
shutil.copytree now accepts a new ``dirs_exist_ok`` keyword argument.
Patch by Josh Bronson.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment