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

Reply via email to