[Python-checkins] cpython: #19840: Add copy_function to shutil.move.

r.david.murray python-checkins at python.org
Wed Jun 11 20:40:28 CEST 2014


http://hg.python.org/cpython/rev/0d61a2a50f9f
changeset:   91135:0d61a2a50f9f
user:        R David Murray <rdmurray at bitdance.com>
date:        Wed Jun 11 14:40:13 2014 -0400
summary:
  #19840: Add copy_function to shutil.move.

Patch by Claudiu Popa.

files:
  Doc/library/shutil.rst  |  22 +++++++++++++++++-----
  Doc/whatsnew/3.5.rst    |   8 ++++++++
  Lib/shutil.py           |  15 +++++++++++----
  Lib/test/test_shutil.py |  18 ++++++++++++++++++
  4 files changed, 54 insertions(+), 9 deletions(-)


diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst
--- a/Doc/library/shutil.rst
+++ b/Doc/library/shutil.rst
@@ -191,7 +191,8 @@
    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 @@
       .. 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 @@
    :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
--- a/Doc/whatsnew/3.5.rst
+++ b/Doc/whatsnew/3.5.rst
@@ -176,6 +176,14 @@
   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
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -486,7 +486,7 @@
     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 @@
     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 @@
             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
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -1592,6 +1592,24 @@
         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):
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list