diff -r 0387054b2038 Doc/library/shutil.rst --- a/Doc/library/shutil.rst Sat Nov 30 19:04:00 2013 -0800 +++ b/Doc/library/shutil.rst Sun Dec 01 10:45:31 2013 +0200 @@ -282,7 +282,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 +295,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 + used. Otherwise, *src* is copied (using :func:`shutil.copy2`, but depends + on *copy_function*) 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. + If *copy_function* is given, it must be a callable that will be used to copy + the source, if :func:`os.rename` fails or it will be delegated to + :func:`shutil.copytree` as its `copy_function` parameter, if the source is + a directory. By default, :func:`shutil.copy2` is used, but any function + that supports the same signature (like :func:`shutil.copy`) can be used. + .. 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* argument to be able to provide a custom copy + function. + .. function:: disk_usage(path) Return disk usage statistics about the given path as a :term:`named tuple` diff -r 0387054b2038 Lib/shutil.py --- a/Lib/shutil.py Sat Nov 30 19:04:00 2013 -0800 +++ b/Lib/shutil.py Sun Dec 01 10:45:31 2013 +0200 @@ -485,7 +485,7 @@ # Thus we always get the last component of the path, even for directories. return os.path.basename(path.rstrip(os.path.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. @@ -502,6 +502,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,10 +532,11 @@ 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) + 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 -r 0387054b2038 Lib/test/test_shutil.py --- a/Lib/test/test_shutil.py Sat Nov 30 19:04:00 2013 -0800 +++ b/Lib/test/test_shutil.py Sun Dec 01 10:45:31 2013 +0200 @@ -1579,6 +1579,25 @@ 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):