diff options
author | R David Murray <rdmurray@bitdance.com> | 2014-06-11 18:40:13 (GMT) |
---|---|---|
committer | R David Murray <rdmurray@bitdance.com> | 2014-06-11 18:40:13 (GMT) |
commit | 6ffface4293f20e504de6a7ca012c482a203409d (patch) | |
tree | 06d09d0290dfdaa7c0c990a060dc958710126cdc | |
parent | 6fe56a329dd427f2a5363b43ad24f04785d2091d (diff) | |
download | cpython-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.rst | 22 | ||||
-rw-r--r-- | Doc/whatsnew/3.5.rst | 8 | ||||
-rw-r--r-- | Lib/shutil.py | 15 | ||||
-rw-r--r-- | 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 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): |