cpython: cf57ef65bcd0 (original) (raw)
--- a/Doc/library/shutil.rst
+++ b/Doc/library/shutil.rst
@@ -45,7 +45,7 @@ Directory and files operations
be copied.
-.. function:: copyfile(src, dst)
+.. 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 :func:copy
for a copy that
@@ -56,37 +56,56 @@ Directory and files operations
such as character or block devices and pipes cannot be copied with this
function. src and dst are path names given as strings.
- If symlinks is true and src is a symbolic link, a new symbolic link will
- be created instead of copying the file src points to.
+
.. versionchanged:: 3.3
:exc:
IOError
used to be raised instead of :exc:OSError
. Added *symlinks* argument.[](#l1.21)
-.. function:: copymode(src, dst) +.. function:: copymode(src, dst[, symlinks=False]) Copy the permission bits from src to dst. The file contents, owner, and
- group are unaffected. src and dst are path names given as strings. If
- symlinks is true, src a symbolic link and the operating system supports
- modes for symbolic links (for example BSD-based ones), the mode of the link
- will be copied.
- .. versionchanged:: 3.3
Added *symlinks* argument.[](#l1.35)
-.. function:: copystat(src, dst) +.. function:: copystat(src, dst[, symlinks=False]) Copy the permission bits, last access time, last modification time, and flags from src to dst. The file contents, owner, and group are unaffected. src
- and dst are path names given as strings. If src and dst are both
- symbolic links and symlinks true, the stats of the link will be copied as
- far as the platform allows.
- .. versionchanged:: 3.3
Added *symlinks* argument.[](#l1.48)
-.. function:: copy(src, dst) +.. function:: copy(src, dst[, symlinks=False])) Copy the file src to the file or directory dst. 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
- followed but recreated instead -- this resembles GNU's :program:
cp -P
. - .. versionchanged:: 3.3
Added *symlinks* argument.[](#l1.61)
-.. function:: copy2(src, dst)
+.. function:: copy2(src, dst[, symlinks=False])
Similar to :func:copy
, but metadata is copied as well -- in fact, this is just
:func:copy
followed by :func:copystat
. 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
. - .. versionchanged:: 3.3
Added *symlinks* argument.[](#l1.73)
.. function:: ignore_patterns(*patterns)
@@ -104,9 +123,9 @@ Directory and files operations
:func:copy2
.
If symlinks is true, symbolic links in the source tree are represented as
- symbolic links in the new tree, but the metadata of the original links is NOT
- copied; if false or omitted, the contents and metadata of the linked files
- are copied to the new tree.
- symbolic links in the new tree and the metadata of the original links will
- be copied as far as the platform allows; if false or omitted, the contents
- and metadata of the linked files are copied to the new tree. When symlinks is false, if the file pointed by the symlink doesn't exist, a exception will be added in the list of errors raised in @@ -140,6 +159,9 @@ Directory and files operations Added the ignore_dangling_symlinks argument to silent dangling symlinks errors when symlinks is false.
- .. versionchanged:: 3.3
Copy metadata when *symlinks* is false.[](#l1.95)
+ .. function:: rmtree(path, ignore_errors=False, onerror=None)
--- a/Lib/shutil.py +++ b/Lib/shutil.py @@ -82,8 +82,13 @@ def _samefile(src, dst): return (os.path.normcase(os.path.abspath(src)) == os.path.normcase(os.path.abspath(dst))) -def copyfile(src, dst):
+def copyfile(src, dst, symlinks=False):
- If optional flag
symlinks
is set andsrc
is a symbolic link, a new - symlink will be created instead of copying the file it points to.
@@ -98,54 +103,94 @@ def copyfile(src, dst):
if stat.S_ISFIFO(st.st_mode):
raise SpecialFileError("%s
is a named pipe" % fn)
- with open(src, 'rb') as fsrc:
with open(dst, 'wb') as fdst:[](#l2.24)
copyfileobj(fsrc, fdst)[](#l2.25)
- if symlinks and os.path.islink(src):
os.symlink(os.readlink(src), dst)[](#l2.27)
- else:
with open(src, 'rb') as fsrc:[](#l2.29)
with open(dst, 'wb') as fdst:[](#l2.30)
copyfileobj(fsrc, fdst)[](#l2.31)
+ +def copymode(src, dst, symlinks=False):
- If the optional flag
symlinks
is set, symlinks aren't followed if and - only if both
src
anddst
are symlinks. Iflchmod
isn't available (eg. - Linux), in these cases, this method does nothing.
- """Copy mode bits from src to dst"""
- if hasattr(os, 'chmod'):
st = os.stat(src)[](#l2.43)
mode = stat.S_IMODE(st.st_mode)[](#l2.44)
os.chmod(dst, mode)[](#l2.45)
- """
- if symlinks and os.path.islink(src) and os.path.islink(dst):
if hasattr(os, 'lchmod'):[](#l2.48)
stat_func, chmod_func = os.lstat, os.lchmod[](#l2.49)
else:[](#l2.50)
return[](#l2.51)
- elif hasattr(os, 'chmod'):
stat_func, chmod_func = os.stat, os.chmod[](#l2.53)
- else:
return[](#l2.55)
+def copystat(src, dst, symlinks=False):
- If the optional flag
symlinks
is set, symlinks aren't followed if and - only if both
src
anddst
are symlinks.
- if symlinks and os.path.islink(src) and os.path.islink(dst):
stat_func = os.lstat[](#l2.74)
utime_func = os.lutimes if hasattr(os, 'lutimes') else _nop[](#l2.75)
chmod_func = os.lchmod if hasattr(os, 'lchmod') else _nop[](#l2.76)
chflags_func = os.lchflags if hasattr(os, 'lchflags') else _nop[](#l2.77)
- else:
stat_func = os.stat[](#l2.79)
utime_func = os.utime if hasattr(os, 'utime') else _nop[](#l2.80)
chmod_func = os.chmod if hasattr(os, 'chmod') else _nop[](#l2.81)
chflags_func = os.chflags if hasattr(os, 'chflags') else _nop[](#l2.82)
- if hasattr(os, 'utime'):
os.utime(dst, (st.st_atime, st.st_mtime))[](#l2.87)
- if hasattr(os, 'chmod'):
os.chmod(dst, mode)[](#l2.89)
- if hasattr(os, 'chflags') and hasattr(st, 'st_flags'):
os.chflags(dst, st.st_flags)[](#l2.95)
chflags_func(dst, st.st_flags)[](#l2.96) except OSError as why:[](#l2.97) if (not hasattr(errno, 'EOPNOTSUPP') or[](#l2.98) why.errno != errno.EOPNOTSUPP):[](#l2.99) raise[](#l2.100)
-def copy(src, dst): +def copy(src, dst, symlinks=False): """Copy data and mode bits ("cp src dst"). The destination may be a directory.
- If the optional flag
symlinks
is set, symlinks won't be followed. This - resembles GNU's "cp -P src dst".
+ """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src))
-def copy2(src, dst): +def copy2(src, dst, symlinks=False): """Copy data and all stat info ("cp -p src dst"). The destination may be a directory.
- If the optional flag
symlinks
is set, symlinks won't be followed. This - resembles GNU's "cp -P src dst".
+ """ if os.path.isdir(dst): dst = os.path.join(dst, os.path.basename(src))
def ignore_patterns(*patterns): """Function that can be used as copytree() ignore parameter. @@ -212,7 +257,11 @@ def copytree(src, dst, symlinks=False, i if os.path.islink(srcname): linkto = os.readlink(srcname) if symlinks:
# We can't just leave it to `copy_function` because legacy[](#l2.142)
# code with a custom `copy_function` may rely on copytree[](#l2.143)
# doing the right thing.[](#l2.144) os.symlink(linkto, dstname)[](#l2.145)
copystat(srcname, dstname, symlinks=symlinks)[](#l2.146) else:[](#l2.147) # ignore dangling symlink if the flag is on[](#l2.148) if not os.path.exists(linkto) and ignore_dangling_symlinks:[](#l2.149)
--- a/Lib/test/test_shutil.py +++ b/Lib/test/test_shutil.py @@ -164,6 +164,197 @@ class TestShutil(unittest.TestCase): self.assertTrue(issubclass(exc[0], OSError)) self.errorState = 2
- @unittest.skipUnless(hasattr(os, 'chmod'), 'requires os.chmod')
- @support.skip_unless_symlink
- def test_copymode_follow_symlinks(self):
tmp_dir = self.mkdtemp()[](#l3.10)
src = os.path.join(tmp_dir, 'foo')[](#l3.11)
dst = os.path.join(tmp_dir, 'bar')[](#l3.12)
src_link = os.path.join(tmp_dir, 'baz')[](#l3.13)
dst_link = os.path.join(tmp_dir, 'quux')[](#l3.14)
write_file(src, 'foo')[](#l3.15)
write_file(dst, 'foo')[](#l3.16)
os.symlink(src, src_link)[](#l3.17)
os.symlink(dst, dst_link)[](#l3.18)
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)[](#l3.19)
# file to file[](#l3.20)
os.chmod(dst, stat.S_IRWXO)[](#l3.21)
self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode)[](#l3.22)
shutil.copymode(src, dst)[](#l3.23)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)[](#l3.24)
# follow src link[](#l3.25)
os.chmod(dst, stat.S_IRWXO)[](#l3.26)
shutil.copymode(src_link, dst)[](#l3.27)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)[](#l3.28)
# follow dst link[](#l3.29)
os.chmod(dst, stat.S_IRWXO)[](#l3.30)
shutil.copymode(src, dst_link)[](#l3.31)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)[](#l3.32)
# follow both links[](#l3.33)
os.chmod(dst, stat.S_IRWXO)[](#l3.34)
shutil.copymode(src_link, dst)[](#l3.35)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)[](#l3.36)
- @unittest.skipUnless(hasattr(os, 'lchmod'), 'requires os.lchmod')
- @support.skip_unless_symlink
- def test_copymode_symlink_to_symlink(self):
tmp_dir = self.mkdtemp()[](#l3.41)
src = os.path.join(tmp_dir, 'foo')[](#l3.42)
dst = os.path.join(tmp_dir, 'bar')[](#l3.43)
src_link = os.path.join(tmp_dir, 'baz')[](#l3.44)
dst_link = os.path.join(tmp_dir, 'quux')[](#l3.45)
write_file(src, 'foo')[](#l3.46)
write_file(dst, 'foo')[](#l3.47)
os.symlink(src, src_link)[](#l3.48)
os.symlink(dst, dst_link)[](#l3.49)
os.chmod(src, stat.S_IRWXU|stat.S_IRWXG)[](#l3.50)
os.chmod(dst, stat.S_IRWXU)[](#l3.51)
os.lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG)[](#l3.52)
# link to link[](#l3.53)
os.lchmod(dst_link, stat.S_IRWXO)[](#l3.54)
shutil.copymode(src_link, dst_link, symlinks=True)[](#l3.55)
self.assertEqual(os.lstat(src_link).st_mode,[](#l3.56)
os.lstat(dst_link).st_mode)[](#l3.57)
self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode)[](#l3.58)
# src link - use chmod[](#l3.59)
os.lchmod(dst_link, stat.S_IRWXO)[](#l3.60)
shutil.copymode(src_link, dst, symlinks=True)[](#l3.61)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)[](#l3.62)
# dst link - use chmod[](#l3.63)
os.lchmod(dst_link, stat.S_IRWXO)[](#l3.64)
shutil.copymode(src, dst_link, symlinks=True)[](#l3.65)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)[](#l3.66)
- @unittest.skipIf(hasattr(os, 'lchmod'), 'requires os.lchmod to be missing')
- @support.skip_unless_symlink
- def test_copymode_symlink_to_symlink_wo_lchmod(self):
tmp_dir = self.mkdtemp()[](#l3.71)
src = os.path.join(tmp_dir, 'foo')[](#l3.72)
dst = os.path.join(tmp_dir, 'bar')[](#l3.73)
src_link = os.path.join(tmp_dir, 'baz')[](#l3.74)
dst_link = os.path.join(tmp_dir, 'quux')[](#l3.75)
write_file(src, 'foo')[](#l3.76)
write_file(dst, 'foo')[](#l3.77)
os.symlink(src, src_link)[](#l3.78)
os.symlink(dst, dst_link)[](#l3.79)
shutil.copymode(src_link, dst_link, symlinks=True) # silent fail[](#l3.80)
- @support.skip_unless_symlink
- def test_copystat_symlinks(self):
tmp_dir = self.mkdtemp()[](#l3.84)
src = os.path.join(tmp_dir, 'foo')[](#l3.85)
dst = os.path.join(tmp_dir, 'bar')[](#l3.86)
src_link = os.path.join(tmp_dir, 'baz')[](#l3.87)
dst_link = os.path.join(tmp_dir, 'qux')[](#l3.88)
write_file(src, 'foo')[](#l3.89)
src_stat = os.stat(src)[](#l3.90)
os.utime(src, (src_stat.st_atime,[](#l3.91)
src_stat.st_mtime - 42.0)) # ensure different mtimes[](#l3.92)
write_file(dst, 'bar')[](#l3.93)
self.assertNotEqual(os.stat(src).st_mtime, os.stat(dst).st_mtime)[](#l3.94)
os.symlink(src, src_link)[](#l3.95)
os.symlink(dst, dst_link)[](#l3.96)
if hasattr(os, 'lchmod'):[](#l3.97)
os.lchmod(src_link, stat.S_IRWXO)[](#l3.98)
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):[](#l3.99)
os.lchflags(src_link, stat.UF_NODUMP)[](#l3.100)
src_link_stat = os.lstat(src_link)[](#l3.101)
# follow[](#l3.102)
if hasattr(os, 'lchmod'):[](#l3.103)
shutil.copystat(src_link, dst_link, symlinks=False)[](#l3.104)
self.assertNotEqual(src_link_stat.st_mode, os.stat(dst).st_mode)[](#l3.105)
# don't follow[](#l3.106)
shutil.copystat(src_link, dst_link, symlinks=True)[](#l3.107)
dst_link_stat = os.lstat(dst_link)[](#l3.108)
if hasattr(os, 'lutimes'):[](#l3.109)
for attr in 'st_atime', 'st_mtime':[](#l3.110)
# The modification times may be truncated in the new file.[](#l3.111)
self.assertLessEqual(getattr(src_link_stat, attr),[](#l3.112)
getattr(dst_link_stat, attr) + 1)[](#l3.113)
if hasattr(os, 'lchmod'):[](#l3.114)
self.assertEqual(src_link_stat.st_mode, dst_link_stat.st_mode)[](#l3.115)
if hasattr(os, 'lchflags') and hasattr(src_link_stat, 'st_flags'):[](#l3.116)
self.assertEqual(src_link_stat.st_flags, dst_link_stat.st_flags)[](#l3.117)
# tell to follow but dst is not a link[](#l3.118)
shutil.copystat(src_link, dst, symlinks=True)[](#l3.119)
self.assertTrue(abs(os.stat(src).st_mtime - os.stat(dst).st_mtime) <[](#l3.120)
00000.1)[](#l3.121)
- @support.skip_unless_symlink
- def test_copy_symlinks(self):
tmp_dir = self.mkdtemp()[](#l3.125)
src = os.path.join(tmp_dir, 'foo')[](#l3.126)
dst = os.path.join(tmp_dir, 'bar')[](#l3.127)
src_link = os.path.join(tmp_dir, 'baz')[](#l3.128)
write_file(src, 'foo')[](#l3.129)
os.symlink(src, src_link)[](#l3.130)
if hasattr(os, 'lchmod'):[](#l3.131)
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)[](#l3.132)
# don't follow[](#l3.133)
shutil.copy(src_link, dst, symlinks=False)[](#l3.134)
self.assertFalse(os.path.islink(dst))[](#l3.135)
self.assertEqual(read_file(src), read_file(dst))[](#l3.136)
os.remove(dst)[](#l3.137)
# follow[](#l3.138)
shutil.copy(src_link, dst, symlinks=True)[](#l3.139)
self.assertTrue(os.path.islink(dst))[](#l3.140)
self.assertEqual(os.readlink(dst), os.readlink(src_link))[](#l3.141)
if hasattr(os, 'lchmod'):[](#l3.142)
self.assertEqual(os.lstat(src_link).st_mode,[](#l3.143)
os.lstat(dst).st_mode)[](#l3.144)
- @support.skip_unless_symlink
- def test_copy2_symlinks(self):
tmp_dir = self.mkdtemp()[](#l3.148)
src = os.path.join(tmp_dir, 'foo')[](#l3.149)
dst = os.path.join(tmp_dir, 'bar')[](#l3.150)
src_link = os.path.join(tmp_dir, 'baz')[](#l3.151)
write_file(src, 'foo')[](#l3.152)
os.symlink(src, src_link)[](#l3.153)
if hasattr(os, 'lchmod'):[](#l3.154)
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)[](#l3.155)
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):[](#l3.156)
os.lchflags(src_link, stat.UF_NODUMP)[](#l3.157)
src_stat = os.stat(src)[](#l3.158)
src_link_stat = os.lstat(src_link)[](#l3.159)
# follow[](#l3.160)
shutil.copy2(src_link, dst, symlinks=False)[](#l3.161)
self.assertFalse(os.path.islink(dst))[](#l3.162)
self.assertEqual(read_file(src), read_file(dst))[](#l3.163)
os.remove(dst)[](#l3.164)
# don't follow[](#l3.165)
shutil.copy2(src_link, dst, symlinks=True)[](#l3.166)
self.assertTrue(os.path.islink(dst))[](#l3.167)
self.assertEqual(os.readlink(dst), os.readlink(src_link))[](#l3.168)
dst_stat = os.lstat(dst)[](#l3.169)
if hasattr(os, 'lutimes'):[](#l3.170)
for attr in 'st_atime', 'st_mtime':[](#l3.171)
# The modification times may be truncated in the new file.[](#l3.172)
self.assertLessEqual(getattr(src_link_stat, attr),[](#l3.173)
getattr(dst_stat, attr) + 1)[](#l3.174)
if hasattr(os, 'lchmod'):[](#l3.175)
self.assertEqual(src_link_stat.st_mode, dst_stat.st_mode)[](#l3.176)
self.assertNotEqual(src_stat.st_mode, dst_stat.st_mode)[](#l3.177)
if hasattr(os, 'lchflags') and hasattr(src_link_stat, 'st_flags'):[](#l3.178)
self.assertEqual(src_link_stat.st_flags, dst_stat.st_flags)[](#l3.179)
- @support.skip_unless_symlink
- def test_copyfile_symlinks(self):
tmp_dir = self.mkdtemp()[](#l3.183)
src = os.path.join(tmp_dir, 'src')[](#l3.184)
dst = os.path.join(tmp_dir, 'dst')[](#l3.185)
dst_link = os.path.join(tmp_dir, 'dst_link')[](#l3.186)
link = os.path.join(tmp_dir, 'link')[](#l3.187)
write_file(src, 'foo')[](#l3.188)
os.symlink(src, link)[](#l3.189)
# don't follow[](#l3.190)
shutil.copyfile(link, dst_link, symlinks=True)[](#l3.191)
self.assertTrue(os.path.islink(dst_link))[](#l3.192)
self.assertEqual(os.readlink(link), os.readlink(dst_link))[](#l3.193)
# follow[](#l3.194)
shutil.copyfile(link, dst)[](#l3.195)
self.assertFalse(os.path.islink(dst))[](#l3.196)
+ def test_rmtree_dont_delete_file(self): # When called on a file instead of a directory, don't delete it. handle, path = tempfile.mkstemp() @@ -190,6 +381,34 @@ class TestShutil(unittest.TestCase): actual = read_file((dst_dir, 'test_dir', 'test.txt')) self.assertEqual(actual, '456')
- @support.skip_unless_symlink
- def test_copytree_symlinks(self):
tmp_dir = self.mkdtemp()[](#l3.207)
src_dir = os.path.join(tmp_dir, 'src')[](#l3.208)
dst_dir = os.path.join(tmp_dir, 'dst')[](#l3.209)
sub_dir = os.path.join(src_dir, 'sub')[](#l3.210)
os.mkdir(src_dir)[](#l3.211)
os.mkdir(sub_dir)[](#l3.212)
write_file((src_dir, 'file.txt'), 'foo')[](#l3.213)
src_link = os.path.join(sub_dir, 'link')[](#l3.214)
dst_link = os.path.join(dst_dir, 'sub/link')[](#l3.215)
os.symlink(os.path.join(src_dir, 'file.txt'),[](#l3.216)
src_link)[](#l3.217)
if hasattr(os, 'lchmod'):[](#l3.218)
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)[](#l3.219)
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):[](#l3.220)
os.lchflags(src_link, stat.UF_NODUMP)[](#l3.221)
src_stat = os.lstat(src_link)[](#l3.222)
shutil.copytree(src_dir, dst_dir, symlinks=True)[](#l3.223)
self.assertTrue(os.path.islink(os.path.join(dst_dir, 'sub', 'link')))[](#l3.224)
self.assertEqual(os.readlink(os.path.join(dst_dir, 'sub', 'link')),[](#l3.225)
os.path.join(src_dir, 'file.txt'))[](#l3.226)
dst_stat = os.lstat(dst_link)[](#l3.227)
if hasattr(os, 'lchmod'):[](#l3.228)
self.assertEqual(dst_stat.st_mode, src_stat.st_mode)[](#l3.229)
if hasattr(os, 'lchflags'):[](#l3.230)
self.assertEqual(dst_stat.st_flags, src_stat.st_flags)[](#l3.231)
+ def test_copytree_with_exclude(self): # creating data join = os.path.join
--- a/Misc/NEWS +++ b/Misc/NEWS @@ -422,6 +422,11 @@ Core and Builtins Library ------- +- Issue #12715: Add an optional symlinks argument to shutil functions