summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorR David Murray <rdmurray@bitdance.com>2014-06-11 18:40:13 (GMT)
committerR David Murray <rdmurray@bitdance.com>2014-06-11 18:40:13 (GMT)
commit6ffface4293f20e504de6a7ca012c482a203409d (patch)
tree06d09d0290dfdaa7c0c990a060dc958710126cdc
parent6fe56a329dd427f2a5363b43ad24f04785d2091d (diff)
downloadcpython-6ffface4293f20e504de6a7ca012c482a203409d.zip
cpython-6ffface4293f20e504de6a7ca012c482a203409d.tar.gz
cpython-6ffface4293f20e504de6a7ca012c482a203409d.tar.bz2
#19840: Add copy_function to shutil.move.
Patch by Claudiu Popa.
-rw-r--r--Doc/library/shutil.rst22
-rw-r--r--Doc/whatsnew/3.5.rst8
-rw-r--r--Lib/shutil.py15
-rw-r--r--Lib/test/test_shutil.py18
4 files changed, 54 insertions, 9 deletions
diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst
index 7cc397d..7a62367 100644
--- a/Doc/library/shutil.rst
+++ b/Doc/library/shutil.rst
@@ -191,7 +191,8 @@ Directory and files operations
match one of the glob-style *patterns* provided. See the example below.
-.. function:: copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False)
+.. function:: copytree(src, dst, symlinks=False, ignore=None, \
+ copy_function=copy2, ignore_dangling_symlinks=False)
Recursively copy an entire directory tree rooted at *src*, returning the
destination directory. The destination
@@ -282,7 +283,7 @@ Directory and files operations
.. versionadded:: 3.3
-.. function:: move(src, dst)
+.. function:: move(src, dst, copy_function=copy2)
Recursively move a file or directory (*src*) to another location (*dst*)
and return the destination.
@@ -295,15 +296,26 @@ Directory and files operations
:func:`os.rename` semantics.
If the destination is on the current filesystem, then :func:`os.rename` is
- used. Otherwise, *src* is copied (using :func:`shutil.copy2`) to *dst* and
- then removed. In case of symlinks, a new symlink pointing to the target of
- *src* will be created in or as *dst* and *src* will be removed.
+ used. Otherwise, *src* is copied to *dst* using *copy_function* and then
+ removed. In case of symlinks, a new symlink pointing to the target of *src*
+ will be created in or as *dst* and *src* will be removed.
+
+ If *copy_function* is given, it must be a callable that takes two arguments
+ *src* and *dst*, and will be used to copy *src* to *dest* if
+ :func:`os.rename` cannot be used. If the source is a directory,
+ :func:`copytree` is called, passing it the :func:`copy_function`. The
+ default *copy_function* is :func:`copy2`. Using :func:`copy` as the
+ *copy_function* allows the move to succeed when it is not possible to also
+ copy the metadata, at the expense of not copying any of the metadata.
.. versionchanged:: 3.3
Added explicit symlink handling for foreign filesystems, thus adapting
it to the behavior of GNU's :program:`mv`.
Now returns *dst*.
+ .. versionchanged:: 3.5
+ Added the *copy_function* keyword argument.
+
.. function:: disk_usage(path)
Return disk usage statistics about the given path as a :term:`named tuple`
diff --git a/Doc/whatsnew/3.5.rst b/Doc/whatsnew/3.5.rst
index 846a416..b5536ad 100644
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -176,6 +176,14 @@ ipaddress
network objects from existing addresses (contributed by Peter Moody
and Antoine Pitrou in :issue:`16531`).
+shutil
+------
+
+* :func:`~shutil.move` now accepts a *copy_function* argument, allowing,
+ for example, :func:`~shutil.copy` to be used instead of the default
+ :func:`~shutil.copy2` if there is a need to ignore metadata. (Contributed by
+ Claudiu Popa in :issue:`19840`.)
+
signal
------
diff --git a/Lib/shutil.py b/Lib/shutil.py
index 508a368..b59017b 100644
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -486,7 +486,7 @@ def _basename(path):
sep = os.path.sep + (os.path.altsep or '')
return os.path.basename(path.rstrip(sep))
-def move(src, dst):
+def move(src, dst, copy_function=copy2):
"""Recursively move a file or directory to another location. This is
similar to the Unix "mv" command. Return the file or directory's
destination.
@@ -503,6 +503,11 @@ def move(src, dst):
recreated under the new name if os.rename() fails because of cross
filesystem renames.
+ The optional `copy_function` argument is a callable that will be used
+ to copy the source or it will be delegated to `copytree`.
+ By default, copy2() is used, but any function that supports the same
+ signature (like copy()) can be used.
+
A lot more could be done here... A look at a mv.c shows a lot of
the issues this implementation glosses over.
@@ -527,11 +532,13 @@ def move(src, dst):
os.unlink(src)
elif os.path.isdir(src):
if _destinsrc(src, dst):
- raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst))
- copytree(src, real_dst, symlinks=True)
+ raise Error("Cannot move a directory '%s' into itself"
+ " '%s'." % (src, dst))
+ copytree(src, real_dst, copy_function=copy_function,
+ symlinks=True)
rmtree(src)
else:
- copy2(src, real_dst)
+ copy_function(src, real_dst)
os.unlink(src)
return real_dst
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
index a483fe1..098d58e 100644
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -1592,6 +1592,24 @@ class TestMove(unittest.TestCase):
rv = shutil.move(self.src_file, os.path.join(self.dst_dir, 'bar'))
self.assertEqual(rv, os.path.join(self.dst_dir, 'bar'))
+ @mock_rename
+ def test_move_file_special_function(self):
+ moved = []
+ def _copy(src, dst):
+ moved.append((src, dst))
+ shutil.move(self.src_file, self.dst_dir, copy_function=_copy)
+ self.assertEqual(len(moved), 1)
+
+ @mock_rename
+ def test_move_dir_special_function(self):
+ moved = []
+ def _copy(src, dst):
+ moved.append((src, dst))
+ support.create_empty_file(os.path.join(self.src_dir, 'child'))
+ support.create_empty_file(os.path.join(self.src_dir, 'child1'))
+ shutil.move(self.src_dir, self.dst_dir, copy_function=_copy)
+ self.assertEqual(len(moved), 3)
+
class TestCopyFile(unittest.TestCase):