Commit 6ffface4 authored by R David Murray's avatar R David Murray

#19840: Add copy_function to shutil.move.

Patch by Claudiu Popa.
parent 6fe56a32
...@@ -191,7 +191,8 @@ Directory and files operations ...@@ -191,7 +191,8 @@ Directory and files operations
match one of the glob-style *patterns* provided. See the example below. 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 Recursively copy an entire directory tree rooted at *src*, returning the
destination directory. The destination destination directory. The destination
...@@ -282,7 +283,7 @@ Directory and files operations ...@@ -282,7 +283,7 @@ Directory and files operations
.. versionadded:: 3.3 .. 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*) Recursively move a file or directory (*src*) to another location (*dst*)
and return the destination. and return the destination.
...@@ -295,15 +296,26 @@ Directory and files operations ...@@ -295,15 +296,26 @@ Directory and files operations
:func:`os.rename` semantics. :func:`os.rename` semantics.
If the destination is on the current filesystem, then :func:`os.rename` is 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 used. Otherwise, *src* is copied to *dst* using *copy_function* and then
then removed. In case of symlinks, a new symlink pointing to the target of removed. In case of symlinks, a new symlink pointing to the target of *src*
*src* will be created in or as *dst* and *src* will be removed. 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 .. versionchanged:: 3.3
Added explicit symlink handling for foreign filesystems, thus adapting Added explicit symlink handling for foreign filesystems, thus adapting
it to the behavior of GNU's :program:`mv`. it to the behavior of GNU's :program:`mv`.
Now returns *dst*. Now returns *dst*.
.. versionchanged:: 3.5
Added the *copy_function* keyword argument.
.. function:: disk_usage(path) .. function:: disk_usage(path)
Return disk usage statistics about the given path as a :term:`named tuple` Return disk usage statistics about the given path as a :term:`named tuple`
......
...@@ -176,6 +176,14 @@ ipaddress ...@@ -176,6 +176,14 @@ ipaddress
network objects from existing addresses (contributed by Peter Moody network objects from existing addresses (contributed by Peter Moody
and Antoine Pitrou in :issue:`16531`). 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 signal
------ ------
......
...@@ -486,7 +486,7 @@ def _basename(path): ...@@ -486,7 +486,7 @@ def _basename(path):
sep = os.path.sep + (os.path.altsep or '') sep = os.path.sep + (os.path.altsep or '')
return os.path.basename(path.rstrip(sep)) 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 """Recursively move a file or directory to another location. This is
similar to the Unix "mv" command. Return the file or directory's similar to the Unix "mv" command. Return the file or directory's
destination. destination.
...@@ -503,6 +503,11 @@ def move(src, dst): ...@@ -503,6 +503,11 @@ def move(src, dst):
recreated under the new name if os.rename() fails because of cross recreated under the new name if os.rename() fails because of cross
filesystem renames. 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 A lot more could be done here... A look at a mv.c shows a lot of
the issues this implementation glosses over. the issues this implementation glosses over.
...@@ -527,11 +532,13 @@ def move(src, dst): ...@@ -527,11 +532,13 @@ def move(src, dst):
os.unlink(src) os.unlink(src)
elif os.path.isdir(src): elif os.path.isdir(src):
if _destinsrc(src, dst): if _destinsrc(src, dst):
raise Error("Cannot move a directory '%s' into itself '%s'." % (src, dst)) raise Error("Cannot move a directory '%s' into itself"
copytree(src, real_dst, symlinks=True) " '%s'." % (src, dst))
copytree(src, real_dst, copy_function=copy_function,
symlinks=True)
rmtree(src) rmtree(src)
else: else:
copy2(src, real_dst) copy_function(src, real_dst)
os.unlink(src) os.unlink(src)
return real_dst return real_dst
......
...@@ -1592,6 +1592,24 @@ class TestMove(unittest.TestCase): ...@@ -1592,6 +1592,24 @@ class TestMove(unittest.TestCase):
rv = shutil.move(self.src_file, os.path.join(self.dst_dir, 'bar')) rv = shutil.move(self.src_file, os.path.join(self.dst_dir, 'bar'))
self.assertEqual(rv, 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): class TestCopyFile(unittest.TestCase):
......
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