Serhiy Storchaka <storch...@gmail.com> added the comment:
And here is a patch, that replaces "symlinks" arguments in shutil by
"follow_symlinks" (with keeping old name if it exists before 3.3). I
very hope native speakers corrected me in docs.
----------
Added file: http://bugs.python.org/file26187/symlinks-to-follow_symlinks.patch
_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue15202>
_______________________________________
diff -r b66e82c9f852 Doc/library/shutil.rst
--- a/Doc/library/shutil.rst Tue Jun 26 23:05:27 2012 +0200
+++ b/Doc/library/shutil.rst Thu Jun 28 00:00:38 2012 +0300
@@ -47,7 +47,7 @@
be copied.
-.. function:: copyfile(src, dst, symlinks=False)
+.. function:: copyfile(src, dst, follow_symlinks=True)
Copy the contents (no metadata) of the file named *src* to a file named
*dst* and return *dst*. *dst* must be the complete target file name; look
at
@@ -59,63 +59,63 @@
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
+ If *follow_symlinks* is false 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.
+ Added *follow_symlinks* argument.
.. versionchanged:: 3.3
Added return of the *dst*.
-.. function:: copymode(src, dst, symlinks=False)
+.. function:: copymode(src, dst, follow_symlinks=True)
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
+ *follow_symlinks* is false, *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.
+ Added *follow_symlinks* argument.
-.. function:: copystat(src, dst, symlinks=False)
+.. function:: copystat(src, dst, follow_symlinks=True)
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
+ symbolic links and *follow_symlinks* false, the stats of the link will be
copied as
far as the platform allows.
.. versionchanged:: 3.3
- Added *symlinks* argument.
+ Added *follow_symlinks* argument.
-.. function:: copy(src, dst, symlinks=False))
+.. function:: copy(src, dst, follow_symlinks=True))
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
+ names given as strings. If *follow_symlinks* is false, symbolic links
won't be
followed but recreated instead -- this resembles GNU's :program:`cp -P`.
.. versionchanged:: 3.3
- Added *symlinks* argument.
+ Added *follow_symlinks* argument.
.. versionchanged:: 3.3
Added return of the *dst*.
-.. function:: copy2(src, dst, symlinks=False)
+.. function:: copy2(src, dst, follow_symlinks=False)
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,
+ similar to the Unix command :program:`cp -p`. If *follow_symlinks* is
false,
symbolic links won't be followed but recreated instead -- this resembles
GNU's :program:`cp -P`.
.. versionchanged:: 3.3
- Added *symlinks* argument, try to copy extended file system attributes
+ Added *follow_symlinks* argument, try to copy extended file system
attributes
too (currently Linux only).
.. versionchanged:: 3.3
@@ -128,7 +128,9 @@
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, *,
+ follow_symlinks=True)
Recursively copy an entire directory tree rooted at *src*, returning the
destination directory. The destination
@@ -137,12 +139,14 @@
are copied with :func:`copystat`, individual files are copied using
:func:`shutil.copy2`.
- If *symlinks* is true, symbolic links in the source tree are represented as
+ If *follow_symlinks* is false or *symlinks* is true,
+ symbolic links in the source tree are represented as
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
+ When *follow_symlinks* is true or *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
a :exc:`Error` exception at the end of the copy process.
You can set the optional *ignore_dangling_symlinks* flag to true if you
@@ -172,10 +176,13 @@
.. versionchanged:: 3.2
Added the *ignore_dangling_symlinks* argument to silent dangling symlinks
- errors when *symlinks* is false.
+ errors when *follow_symlinks* is true or *symlinks* is false.
.. versionchanged:: 3.3
- Copy metadata when *symlinks* is false.
+ Copy metadata when *follow_symlinks* is true or *symlinks* is false.
+
+ .. versionchanged:: 3.3
+ Added *follow_symlinks* argument with opposite meaning to *symlinks*.
.. versionchanged:: 3.3
Added return of the *dst*.
diff -r b66e82c9f852 Lib/shutil.py
--- a/Lib/shutil.py Tue Jun 26 23:05:27 2012 +0200
+++ b/Lib/shutil.py Thu Jun 28 00:00:38 2012 +0300
@@ -82,10 +82,10 @@
return (os.path.normcase(os.path.abspath(src)) ==
os.path.normcase(os.path.abspath(dst)))
-def copyfile(src, dst, symlinks=False):
+def copyfile(src, dst, follow_symlinks=True):
"""Copy data from src to dst.
- If optional flag `symlinks` is set and `src` is a symbolic link, a new
+ If optional flag `follow_symlinks` is not set and `src` is a symbolic
link, a new
symlink will be created instead of copying the file it points to.
"""
@@ -103,7 +103,7 @@
if stat.S_ISFIFO(st.st_mode):
raise SpecialFileError("`%s` is a named pipe" % fn)
- if symlinks and os.path.islink(src):
+ if not follow_symlinks and os.path.islink(src):
os.symlink(os.readlink(src), dst)
else:
with open(src, 'rb') as fsrc:
@@ -111,15 +111,15 @@
copyfileobj(fsrc, fdst)
return dst
-def copymode(src, dst, symlinks=False):
+def copymode(src, dst, follow_symlinks=True):
"""Copy mode bits from src to dst.
- If the optional flag `symlinks` is set, symlinks aren't followed if and
+ If the optional flag `follow_symlinks` is not set, symlinks aren't
followed if and
only if both `src` and `dst` are symlinks. If `lchmod` isn't available (eg.
Linux), in these cases, this method does nothing.
"""
- if symlinks and os.path.islink(src) and os.path.islink(dst):
+ if not follow_symlinks and os.path.islink(src) and os.path.islink(dst):
if hasattr(os, 'lchmod'):
stat_func, chmod_func = os.lstat, os.lchmod
else:
@@ -132,10 +132,10 @@
st = stat_func(src)
chmod_func(dst, stat.S_IMODE(st.st_mode))
-def copystat(src, dst, symlinks=False):
+def copystat(src, dst, follow_symlinks=True):
"""Copy all stat info (mode bits, atime, mtime, flags) from src to dst.
- If the optional flag `symlinks` is set, symlinks aren't followed if and
+ If the optional flag `follow_symlinks` is not set, symlinks aren't
followed if and
only if both `src` and `dst` are symlinks.
"""
@@ -143,7 +143,7 @@
pass
# follow symlinks (aka don't not follow symlinks)
- follow = not (symlinks and os.path.islink(src) and os.path.islink(dst))
+ follow = follow_symlinks or not (os.path.islink(src) and
os.path.islink(dst))
if follow:
# use the real function if it exists
def lookup(name):
@@ -186,19 +186,19 @@
raise
if hasattr(os, 'listxattr'):
- def _copyxattr(src, dst, symlinks=False):
+ def _copyxattr(src, dst, follow_symlinks=True):
"""Copy extended filesystem attributes from `src` to `dst`.
Overwrite existing attributes.
- If the optional flag `symlinks` is set, symlinks won't be followed.
+ If the optional flag `follow_symlinks` is not set, symlinks won't be
followed.
"""
- for name in os.listxattr(src, follow_symlinks=symlinks):
+ for name in os.listxattr(src, follow_symlinks=follow_symlinks):
try:
- value = os.getxattr(src, name, follow_symlinks=symlinks)
- os.setxattr(dst, name, value, follow_symlinks=symlinks)
+ value = os.getxattr(src, name, follow_symlinks=follow_symlinks)
+ os.setxattr(dst, name, value, follow_symlinks=follow_symlinks)
except OSError as e:
if e.errno not in (errno.EPERM, errno.ENOTSUP, errno.ENODATA):
raise
@@ -206,36 +206,36 @@
def _copyxattr(*args, **kwargs):
pass
-def copy(src, dst, symlinks=False):
+def copy(src, dst, follow_symlinks=True):
"""Copy data and mode bits ("cp src dst"). Return the file's destination.
The destination may be a directory.
- If the optional flag `symlinks` is set, symlinks won't be followed. This
+ If the optional flag `follow_symlinks` is not 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))
- copyfile(src, dst, symlinks=symlinks)
- copymode(src, dst, symlinks=symlinks)
+ copyfile(src, dst, follow_symlinks=follow_symlinks)
+ copymode(src, dst, follow_symlinks=follow_symlinks)
return dst
-def copy2(src, dst, symlinks=False):
+def copy2(src, dst, follow_symlinks=True):
"""Copy data and all stat info ("cp -p src dst"). Return the file's
destination."
The destination may be a directory.
- If the optional flag `symlinks` is set, symlinks won't be followed. This
+ If the optional flag `follow_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))
- copyfile(src, dst, symlinks=symlinks)
- copystat(src, dst, symlinks=symlinks)
- _copyxattr(src, dst, symlinks=symlinks)
+ copyfile(src, dst, follow_symlinks=follow_symlinks)
+ copystat(src, dst, follow_symlinks=follow_symlinks)
+ _copyxattr(src, dst, follow_symlinks=follow_symlinks)
return dst
def ignore_patterns(*patterns):
@@ -250,8 +250,10 @@
return set(ignored_names)
return _ignore_patterns
-def copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
- ignore_dangling_symlinks=False):
+_sentry = object()
+
+def copytree(src, dst, symlinks=_sentry, ignore=None, copy_function=copy2,
+ ignore_dangling_symlinks=False, *, follow_symlinks=_sentry):
"""Recursively copy a directory tree.
The destination directory must not already exist.
@@ -286,6 +288,15 @@
function that supports the same signature (like copy()) can be used.
"""
+ if symlinks is _sentry:
+ if follow_symlinks is _sentry:
+ symlinks = False
+ else:
+ symlinks = not follow_symlinks
+ elif follow_symlinks is not _sentry:
+ raise ValueError(
+ "'follow_symlinks' and 'symlinks' arguments are incompatible")
+
names = os.listdir(src)
if ignore is not None:
ignored_names = ignore(src, names)
@@ -307,7 +318,7 @@
# code with a custom `copy_function` may rely on copytree
# doing the right thing.
os.symlink(linkto, dstname)
- copystat(srcname, dstname, symlinks=symlinks)
+ copystat(srcname, dstname, follow_symlinks=not symlinks)
else:
# ignore dangling symlink if the flag is on
if not os.path.exists(linkto) and ignore_dangling_symlinks:
diff -r b66e82c9f852 Lib/test/test_shutil.py
--- a/Lib/test/test_shutil.py Tue Jun 26 23:05:27 2012 +0200
+++ b/Lib/test/test_shutil.py Thu Jun 28 00:00:38 2012 +0300
@@ -245,17 +245,17 @@
os.lchmod(src_link, stat.S_IRWXO|stat.S_IRWXG)
# link to link
os.lchmod(dst_link, stat.S_IRWXO)
- shutil.copymode(src_link, dst_link, symlinks=True)
+ shutil.copymode(src_link, dst_link, follow_symlinks=False)
self.assertEqual(os.lstat(src_link).st_mode,
os.lstat(dst_link).st_mode)
self.assertNotEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
# src link - use chmod
os.lchmod(dst_link, stat.S_IRWXO)
- shutil.copymode(src_link, dst, symlinks=True)
+ shutil.copymode(src_link, dst, follow_symlinks=False)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
# dst link - use chmod
os.lchmod(dst_link, stat.S_IRWXO)
- shutil.copymode(src, dst_link, symlinks=True)
+ shutil.copymode(src, dst_link, follow_symlinks=False)
self.assertEqual(os.stat(src).st_mode, os.stat(dst).st_mode)
@unittest.skipIf(hasattr(os, 'lchmod'), 'requires os.lchmod to be missing')
@@ -270,7 +270,7 @@
write_file(dst, 'foo')
os.symlink(src, src_link)
os.symlink(dst, dst_link)
- shutil.copymode(src_link, dst_link, symlinks=True) # silent fail
+ shutil.copymode(src_link, dst_link, follow_symlinks=False) # silent
fail
@support.skip_unless_symlink
def test_copystat_symlinks(self):
@@ -294,10 +294,10 @@
src_link_stat = os.lstat(src_link)
# follow
if hasattr(os, 'lchmod'):
- shutil.copystat(src_link, dst_link, symlinks=False)
+ shutil.copystat(src_link, dst_link, follow_symlinks=True)
self.assertNotEqual(src_link_stat.st_mode, os.stat(dst).st_mode)
# don't follow
- shutil.copystat(src_link, dst_link, symlinks=True)
+ shutil.copystat(src_link, dst_link, follow_symlinks=False)
dst_link_stat = os.lstat(dst_link)
if os.utime in os.supports_follow_symlinks:
for attr in 'st_atime', 'st_mtime':
@@ -309,7 +309,7 @@
if hasattr(os, 'lchflags') and hasattr(src_link_stat, 'st_flags'):
self.assertEqual(src_link_stat.st_flags, dst_link_stat.st_flags)
# tell to follow but dst is not a link
- shutil.copystat(src_link, dst, symlinks=True)
+ shutil.copystat(src_link, dst, follow_symlinks=False)
self.assertTrue(abs(os.stat(src).st_mtime - os.stat(dst).st_mtime) <
00000.1)
@@ -397,10 +397,10 @@
dst_link = os.path.join(tmp_dir, 'qux')
write_file(dst, 'bar')
os.symlink(dst, dst_link)
- shutil._copyxattr(src_link, dst_link, symlinks=True)
+ shutil._copyxattr(src_link, dst_link, follow_symlinks=False)
self.assertEqual(os.getxattr(dst_link, 'trusted.foo',
follow_symlinks=False), b'43')
self.assertRaises(OSError, os.getxattr, dst, 'trusted.foo')
- shutil._copyxattr(src_link, dst, symlinks=True)
+ shutil._copyxattr(src_link, dst, follow_symlinks=False)
self.assertEqual(os.getxattr(dst, 'trusted.foo'), b'43')
@support.skip_unless_symlink
@@ -414,12 +414,12 @@
if hasattr(os, 'lchmod'):
os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
# don't follow
- shutil.copy(src_link, dst, symlinks=False)
+ shutil.copy(src_link, dst, follow_symlinks=True)
self.assertFalse(os.path.islink(dst))
self.assertEqual(read_file(src), read_file(dst))
os.remove(dst)
# follow
- shutil.copy(src_link, dst, symlinks=True)
+ shutil.copy(src_link, dst, follow_symlinks=False)
self.assertTrue(os.path.islink(dst))
self.assertEqual(os.readlink(dst), os.readlink(src_link))
if hasattr(os, 'lchmod'):
@@ -441,12 +441,12 @@
src_stat = os.stat(src)
src_link_stat = os.lstat(src_link)
# follow
- shutil.copy2(src_link, dst, symlinks=False)
+ shutil.copy2(src_link, dst, follow_symlinks=True)
self.assertFalse(os.path.islink(dst))
self.assertEqual(read_file(src), read_file(dst))
os.remove(dst)
# don't follow
- shutil.copy2(src_link, dst, symlinks=True)
+ shutil.copy2(src_link, dst, follow_symlinks=False)
self.assertTrue(os.path.islink(dst))
self.assertEqual(os.readlink(dst), os.readlink(src_link))
dst_stat = os.lstat(dst)
@@ -484,7 +484,7 @@
write_file(src, 'foo')
os.symlink(src, link)
# don't follow
- shutil.copyfile(link, dst_link, symlinks=True)
+ shutil.copyfile(link, dst_link, follow_symlinks=False)
self.assertTrue(os.path.islink(dst_link))
self.assertEqual(os.readlink(link), os.readlink(dst_link))
# follow
@@ -555,6 +555,33 @@
if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):
os.lchflags(src_link, stat.UF_NODUMP)
src_stat = os.lstat(src_link)
+ shutil.copytree(src_dir, dst_dir, follow_symlinks=False)
+ self.assertTrue(os.path.islink(os.path.join(dst_dir, 'sub', 'link')))
+ self.assertEqual(os.readlink(os.path.join(dst_dir, 'sub', 'link')),
+ os.path.join(src_dir, 'file.txt'))
+ dst_stat = os.lstat(dst_link)
+ if hasattr(os, 'lchmod'):
+ self.assertEqual(dst_stat.st_mode, src_stat.st_mode)
+ if hasattr(os, 'lchflags'):
+ self.assertEqual(dst_stat.st_flags, src_stat.st_flags)
+
+ def test_copytree_symlinks_old(self):
+ tmp_dir = self.mkdtemp()
+ src_dir = os.path.join(tmp_dir, 'src')
+ dst_dir = os.path.join(tmp_dir, 'dst')
+ sub_dir = os.path.join(src_dir, 'sub')
+ os.mkdir(src_dir)
+ os.mkdir(sub_dir)
+ write_file((src_dir, 'file.txt'), 'foo')
+ src_link = os.path.join(sub_dir, 'link')
+ dst_link = os.path.join(dst_dir, 'sub/link')
+ os.symlink(os.path.join(src_dir, 'file.txt'),
+ src_link)
+ if hasattr(os, 'lchmod'):
+ os.lchmod(src_link, stat.S_IRWXU | stat.S_IRWXO)
+ if hasattr(os, 'lchflags') and hasattr(stat, 'UF_NODUMP'):
+ os.lchflags(src_link, stat.UF_NODUMP)
+ src_stat = os.lstat(src_link)
shutil.copytree(src_dir, dst_dir, symlinks=True)
self.assertTrue(os.path.islink(os.path.join(dst_dir, 'sub', 'link')))
self.assertEqual(os.readlink(os.path.join(dst_dir, 'sub', 'link')),
@@ -565,6 +592,15 @@
if hasattr(os, 'lchflags'):
self.assertEqual(dst_stat.st_flags, src_stat.st_flags)
+ def test_copytree_incompatible_args(self):
+ tmp_dir = self.mkdtemp()
+ src_dir = os.path.join(tmp_dir, 'src')
+ dst_dir = os.path.join(tmp_dir, 'dst')
+ sub_dir = os.path.join(src_dir, 'sub')
+ os.mkdir(src_dir)
+ os.mkdir(sub_dir)
+ self.assertRaises(ValueError, shutil.copytree, src_dir, dst_dir,
symlinks=True, follow_symlinks=False)
+
def test_copytree_with_exclude(self):
# creating data
join = os.path.join
@@ -748,9 +784,9 @@
shutil.copytree(src_dir, dst_dir, ignore_dangling_symlinks=True)
self.assertNotIn('test.txt', os.listdir(dst_dir))
- # a dangling symlink is copied if symlinks=True
+ # a dangling symlink is copied if follow_symlinks=False
dst_dir = os.path.join(self.mkdtemp(), 'destination3')
- shutil.copytree(src_dir, dst_dir, symlinks=True)
+ shutil.copytree(src_dir, dst_dir, follow_symlinks=False)
self.assertIn('test.txt', os.listdir(dst_dir))
def _copy_file(self, method):
_______________________________________________
Python-bugs-list mailing list
Unsubscribe:
http://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com