diff options
-rw-r--r-- | Doc/lib/libos.tex | 22 | ||||
-rw-r--r-- | Lib/os.py | 10 | ||||
-rw-r--r-- | Lib/test/test_os.py | 66 | ||||
-rw-r--r-- | Misc/NEWS | 4 |
4 files changed, 70 insertions, 32 deletions
diff --git a/Doc/lib/libos.tex b/Doc/lib/libos.tex index 1a39693b80..638ed6b6ed 100644 --- a/Doc/lib/libos.tex +++ b/Doc/lib/libos.tex @@ -1233,7 +1233,8 @@ Availability: Macintosh, \UNIX, Windows. \end{funcdesc} \begin{funcdesc}{walk}{top\optional{, topdown\code{=True} - \optional{, onerror\code{=None}}}} + \optional{, onerror\code{=None}\optional{, + followlinks\code{=False}}}}} \index{directory!walking} \index{directory!traversal} \function{walk()} generates the file names in a directory tree, by @@ -1273,24 +1274,25 @@ report the error to continue with the walk, or raise the exception to abort the walk. Note that the filename is available as the \code{filename} attribute of the exception object. +By default, \function{walk()} will not walk down into symbolic links that +resolve to directories. Set \var{followlinks} to True to visit directories +pointed to by symlinks, on systems that support them. + \versionadded[The \var{followlinks} parameter]{2.6} \begin{notice} +Be aware that setting \var{followlinks} to true can lead to infinite recursion +if a link points to a parent directory of itself. \function{walk()} does not +keep track of the directories it visited already. +\end{notice} + +\begin{notice} If you pass a relative pathname, don't change the current working directory between resumptions of \function{walk()}. \function{walk()} never changes the current directory, and assumes that its caller doesn't either. \end{notice} -\begin{notice} -On systems that support symbolic links, links to subdirectories appear -in \var{dirnames} lists, but \function{walk()} will not visit them -(infinite loops are hard to avoid when following symbolic links). -To visit linked directories, you can identify them with -\code{os.path.islink(\var{path})}, and invoke \code{walk(\var{path})} -on each directly. -\end{notice} - This example displays the number of bytes taken by non-directory files in each directory under the starting directory, except that it doesn't look under any CVS subdirectory: @@ -221,7 +221,7 @@ def renames(old, new): __all__.extend(["makedirs", "removedirs", "renames"]) -def walk(top, topdown=True, onerror=None): +def walk(top, topdown=True, onerror=None, followlinks=False): """Directory tree generator. For each directory in the directory tree rooted at top (including top @@ -257,6 +257,10 @@ def walk(top, topdown=True, onerror=None): to abort the walk. Note that the filename is available as the filename attribute of the exception object. + By default, os.walk does not follow symbolic links to subdirectories on + systems that support them. In order to get this functionality, set the + optional argument 'followlinks' to true. + Caution: if you pass a relative pathname for top, don't change the current working directory between resumptions of walk. walk never changes the current directory, and assumes that the client doesn't @@ -300,8 +304,8 @@ def walk(top, topdown=True, onerror=None): yield top, dirs, nondirs for name in dirs: path = join(top, name) - if not islink(path): - for x in walk(path, topdown, onerror): + if followlinks or not islink(path): + for x in walk(path, topdown, onerror, followlinks): yield x if not topdown: yield top, dirs, nondirs diff --git a/Lib/test/test_os.py b/Lib/test/test_os.py index 9dcdb1808f..6d7fe8ec9b 100644 --- a/Lib/test/test_os.py +++ b/Lib/test/test_os.py @@ -272,65 +272,89 @@ class WalkTests(unittest.TestCase): from os.path import join # Build: - # TESTFN/ a file kid and two directory kids + # TESTFN/ + # TEST1/ a file kid and two directory kids # tmp1 # SUB1/ a file kid and a directory kid - # tmp2 - # SUB11/ no kids - # SUB2/ just a file kid - # tmp3 - sub1_path = join(test_support.TESTFN, "SUB1") + # tmp2 + # SUB11/ no kids + # SUB2/ a file kid and a dirsymlink kid + # tmp3 + # link/ a symlink to TESTFN.2 + # TEST2/ + # tmp4 a lone file + walk_path = join(test_support.TESTFN, "TEST1") + sub1_path = join(walk_path, "SUB1") sub11_path = join(sub1_path, "SUB11") - sub2_path = join(test_support.TESTFN, "SUB2") - tmp1_path = join(test_support.TESTFN, "tmp1") + sub2_path = join(walk_path, "SUB2") + tmp1_path = join(walk_path, "tmp1") tmp2_path = join(sub1_path, "tmp2") tmp3_path = join(sub2_path, "tmp3") + link_path = join(sub2_path, "link") + t2_path = join(test_support.TESTFN, "TEST2") + tmp4_path = join(test_support.TESTFN, "TEST2", "tmp4") # Create stuff. os.makedirs(sub11_path) os.makedirs(sub2_path) - for path in tmp1_path, tmp2_path, tmp3_path: + os.makedirs(t2_path) + for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path: f = file(path, "w") f.write("I'm " + path + " and proud of it. Blame test_os.\n") f.close() + if hasattr(os, "symlink"): + os.symlink(os.path.abspath(t2_path), link_path) + else: + # it must be a directory because the test expects that + os.mkdir(link_path) # Walk top-down. - all = list(os.walk(test_support.TESTFN)) + all = list(os.walk(walk_path)) self.assertEqual(len(all), 4) # We can't know which order SUB1 and SUB2 will appear in. # Not flipped: TESTFN, SUB1, SUB11, SUB2 # flipped: TESTFN, SUB2, SUB1, SUB11 flipped = all[0][1][0] != "SUB1" all[0][1].sort() - self.assertEqual(all[0], (test_support.TESTFN, ["SUB1", "SUB2"], ["tmp1"])) + self.assertEqual(all[0], (walk_path, ["SUB1", "SUB2"], ["tmp1"])) self.assertEqual(all[1 + flipped], (sub1_path, ["SUB11"], ["tmp2"])) self.assertEqual(all[2 + flipped], (sub11_path, [], [])) - self.assertEqual(all[3 - 2 * flipped], (sub2_path, [], ["tmp3"])) + self.assertEqual(all[3 - 2 * flipped], (sub2_path, ["link"], ["tmp3"])) # Prune the search. all = [] - for root, dirs, files in os.walk(test_support.TESTFN): + for root, dirs, files in os.walk(walk_path): all.append((root, dirs, files)) # Don't descend into SUB1. if 'SUB1' in dirs: # Note that this also mutates the dirs we appended to all! dirs.remove('SUB1') self.assertEqual(len(all), 2) - self.assertEqual(all[0], (test_support.TESTFN, ["SUB2"], ["tmp1"])) - self.assertEqual(all[1], (sub2_path, [], ["tmp3"])) + self.assertEqual(all[0], (walk_path, ["SUB2"], ["tmp1"])) + self.assertEqual(all[1], (sub2_path, ["link"], ["tmp3"])) # Walk bottom-up. - all = list(os.walk(test_support.TESTFN, topdown=False)) + all = list(os.walk(walk_path, topdown=False)) self.assertEqual(len(all), 4) # We can't know which order SUB1 and SUB2 will appear in. # Not flipped: SUB11, SUB1, SUB2, TESTFN # flipped: SUB2, SUB11, SUB1, TESTFN flipped = all[3][1][0] != "SUB1" all[3][1].sort() - self.assertEqual(all[3], (test_support.TESTFN, ["SUB1", "SUB2"], ["tmp1"])) + self.assertEqual(all[3], (walk_path, ["SUB1", "SUB2"], ["tmp1"])) self.assertEqual(all[flipped], (sub11_path, [], [])) self.assertEqual(all[flipped + 1], (sub1_path, ["SUB11"], ["tmp2"])) - self.assertEqual(all[2 - 2 * flipped], (sub2_path, [], ["tmp3"])) + self.assertEqual(all[2 - 2 * flipped], (sub2_path, ["link"], ["tmp3"])) + + # Walk, following symlinks. + for root, dirs, files in os.walk(walk_path, followlinks=True): + if root == link_path: + self.assertEqual(dirs, []) + self.assertEqual(files, ["tmp4"]) + break + else: + self.fail("Didn't follow symlink with followlinks=True") + # Tear everything down. This is a decent use for bottom-up on # Windows, which doesn't have a recursive delete command. The @@ -340,7 +364,11 @@ class WalkTests(unittest.TestCase): for name in files: os.remove(join(root, name)) for name in dirs: - os.rmdir(join(root, name)) + dirname = join(root, name) + if not os.path.islink(dirname): + os.rmdir(dirname) + else: + os.remove(dirname) os.rmdir(test_support.TESTFN) class MakedirTests (unittest.TestCase): @@ -194,6 +194,10 @@ Library - Patch #1630118: add a SpooledTemporaryFile class to tempfile.py. +- Patch #1273829: os.walk() now has a "followlinks" parameter. If set to + True (which is not the default), it visits symlinks pointing to + directories. + - Bug #1681228: the webbrowser module now correctly uses the default GNOME or KDE browser, depending on whether there is a session of one of those present. Also, it tries the Windows default browser before |