From 0d0a1dedbcf85130885166c066276442c99b3242 Mon Sep 17 00:00:00 2001 From: Brian Curtin Date: Mon, 18 Jun 2012 18:41:07 -0500 Subject: [PATCH] Fix #14772: Return the destination from some shutil functions. --- Doc/library/shutil.rst | 14 +++++++++----- Lib/shutil.py | 13 ++++++++++--- Lib/test/test_shutil.py | 41 +++++++++++++++++++++++++++++++++++++++++ Misc/NEWS | 2 ++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/Doc/library/shutil.rst b/Doc/library/shutil.rst index 27d374a19a5..3b9e6f4f73c 100644 --- a/Doc/library/shutil.rst +++ b/Doc/library/shutil.rst @@ -50,7 +50,7 @@ Directory and files operations .. function:: copyfile(src, dst, symlinks=False) Copy the contents (no metadata) of the file named *src* to a file named - *dst*. *dst* must be the complete target file name; look at + *dst* and return *dst*. *dst* must be the complete target file name; look at :func:`shutil.copy` for a copy that accepts a target directory path. If *src* and *dst* are the same files, :exc:`Error` is raised. @@ -91,7 +91,8 @@ Directory and files operations .. function:: copy(src, dst, symlinks=False)) - Copy the file *src* to the file or directory *dst*. If *dst* is a directory, a + Copy the file *src* to the file or directory *dst* and return the file's + destination. If *dst* is a directory, a file with the same basename as *src* is created (or overwritten) in the directory specified. Permission bits are copied. *src* and *dst* are path names given as strings. If *symlinks* is true, symbolic links won't be @@ -102,7 +103,8 @@ Directory and files operations .. function:: copy2(src, dst, symlinks=False) - Similar to :func:`shutil.copy`, but metadata is copied as well. This is + Similar to :func:`shutil.copy`, including that the destination is + returned, but metadata is copied as well. This is similar to the Unix command :program:`cp -p`. If *symlinks* is true, symbolic links won't be followed but recreated instead -- this resembles GNU's :program:`cp -P`. @@ -120,7 +122,8 @@ Directory and files operations .. function:: copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, ignore_dangling_symlinks=False) - Recursively copy an entire directory tree rooted at *src*. The destination + Recursively copy an entire directory tree rooted at *src*, returning the + destination directory. The destination directory, named by *dst*, must not already exist; it will be created as well as missing parent directories. Permissions and times of directories are copied with :func:`copystat`, individual files are copied using @@ -189,7 +192,8 @@ Directory and files operations .. function:: move(src, dst) - 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. If the destination is a directory or a symlink to a directory, then *src* is moved inside that directory. diff --git a/Lib/shutil.py b/Lib/shutil.py index ce60c3b1137..46398efd017 100644 --- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -109,6 +109,7 @@ def copyfile(src, dst, symlinks=False): with open(src, 'rb') as fsrc: with open(dst, 'wb') as fdst: copyfileobj(fsrc, fdst) + return dst def copymode(src, dst, symlinks=False): """Copy mode bits from src to dst. @@ -197,7 +198,7 @@ else: pass def copy(src, dst, symlinks=False): - """Copy data and mode bits ("cp src dst"). + """Copy data and mode bits ("cp src dst"). Return the file's destination. The destination may be a directory. @@ -209,9 +210,11 @@ def copy(src, dst, symlinks=False): dst = os.path.join(dst, os.path.basename(src)) copyfile(src, dst, symlinks=symlinks) copymode(src, dst, symlinks=symlinks) + return dst def copy2(src, dst, symlinks=False): - """Copy data and all stat info ("cp -p src dst"). + """Copy data and all stat info ("cp -p src dst"). Return the file's + destination." The destination may be a directory. @@ -224,6 +227,7 @@ def copy2(src, dst, symlinks=False): copyfile(src, dst, symlinks=symlinks) copystat(src, dst, symlinks=symlinks) _copyxattr(src, dst, symlinks=symlinks) + return dst def ignore_patterns(*patterns): """Function that can be used as copytree() ignore parameter. @@ -322,6 +326,7 @@ def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2, errors.extend((src, dst, str(why))) if errors: raise Error(errors) + return dst def rmtree(path, ignore_errors=False, onerror=None): """Recursively delete a directory tree. @@ -379,7 +384,8 @@ def _basename(path): def move(src, dst): """Recursively move a file or directory to another location. This is - similar to the Unix "mv" command. + similar to the Unix "mv" command. Return the file or directory's + destination. If the destination is a directory or a symlink to a directory, the source is moved inside the directory. The destination path must not already @@ -423,6 +429,7 @@ def move(src, dst): else: copy2(src, real_dst) os.unlink(src) + return real_dst def _destinsrc(src, dst): src = abspath(src) diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py index 3b4e381a0b4..118152031ad 100644 --- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -1095,6 +1095,38 @@ class TestShutil(unittest.TestCase): shutil.chown(dirname, user, group) check_chown(dirname, uid, gid) + def test_copy_return_value(self): + # copy and copy2 both return their destination path. + for fn in (shutil.copy, shutil.copy2): + src_dir = self.mkdtemp() + dst_dir = self.mkdtemp() + src = os.path.join(src_dir, 'foo') + write_file(src, 'foo') + rv = fn(src, dst_dir) + self.assertEqual(rv, os.path.join(dst_dir, 'foo')) + rv = fn(src, os.path.join(dst_dir, 'bar')) + self.assertEqual(rv, os.path.join(dst_dir, 'bar')) + + def test_copyfile_return_value(self): + # copytree returns its destination path. + src_dir = self.mkdtemp() + dst_dir = self.mkdtemp() + dst_file = os.path.join(dst_dir, 'bar') + src_file = os.path.join(src_dir, 'foo') + write_file(src_file, 'foo') + rv = shutil.copyfile(src_file, dst_file) + self.assertTrue(os.path.exists(rv)) + self.assertEqual(read_file(src_file), read_file(dst_file)) + + def test_copytree_return_value(self): + # copytree returns its destination path. + src_dir = self.mkdtemp() + dst_dir = src_dir + "dest" + src = os.path.join(src_dir, 'foo') + write_file(src, 'foo') + rv = shutil.copytree(src_dir, dst_dir) + self.assertEqual(['foo'], os.listdir(rv)) + class TestMove(unittest.TestCase): @@ -1251,6 +1283,15 @@ class TestMove(unittest.TestCase): self.assertTrue(os.path.islink(dst_link)) self.assertTrue(os.path.samefile(src, dst_link)) + def test_move_return_value(self): + rv = shutil.move(self.src_file, self.dst_dir) + self.assertEqual(rv, + os.path.join(self.dst_dir, os.path.basename(self.src_file))) + + def test_move_as_rename_return_value(self): + rv = shutil.move(self.src_file, os.path.join(self.dst_dir, 'bar')) + self.assertEqual(rv, os.path.join(self.dst_dir, 'bar')) + class TestCopyFile(unittest.TestCase): diff --git a/Misc/NEWS b/Misc/NEWS index 93aeaf87716..de7a2c485d6 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -31,6 +31,8 @@ Core and Builtins Library ------- +- Issue #14772: Return destination values from some shutil functions. + - Issue #15064: Implement context manager protocol for multiprocessing types - Issue #15101: Make pool finalizer avoid joining current thread.