pathlib ABCs: follow all symlinks in PathBase.glob() (#116293)

Switch the default value of *follow_symlinks* from `None` to `True` in
`pathlib._abc.PathBase.glob()` and `rglob()`. This speeds up recursive
globbing.

No change to the public pathlib classes.
This commit is contained in:
Barney Gale 2024-03-04 02:26:33 +00:00 committed by GitHub
parent 3383d6afa3
commit 1dce0073da
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 35 additions and 40 deletions

View file

@ -789,7 +789,7 @@ class PathBase(PurePathBase):
def _make_child_relpath(self, name): def _make_child_relpath(self, name):
return self.joinpath(name) return self.joinpath(name)
def glob(self, pattern, *, case_sensitive=None, follow_symlinks=None): def glob(self, pattern, *, case_sensitive=None, follow_symlinks=True):
"""Iterate over this subtree and yield all existing files (of any """Iterate over this subtree and yield all existing files (of any
kind, including directories) matching the given relative pattern. kind, including directories) matching the given relative pattern.
""" """
@ -846,7 +846,7 @@ class PathBase(PurePathBase):
paths = _select_children(paths, bool(stack), follow_symlinks, match) paths = _select_children(paths, bool(stack), follow_symlinks, match)
return paths return paths
def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=None): def rglob(self, pattern, *, case_sensitive=None, follow_symlinks=True):
"""Recursively yield all existing files (of any kind, including """Recursively yield all existing files (of any kind, including
directories) matching the given relative pattern, anywhere in directories) matching the given relative pattern, anywhere in
this subtree. this subtree.

View file

@ -1844,51 +1844,46 @@ class DummyPathTest(DummyPurePathTest):
_check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "*/dirD/**", ["dirC/dirD/", "dirC/dirD/fileD"])
_check(p, "*/dirD/**/", ["dirC/dirD/"]) _check(p, "*/dirD/**/", ["dirC/dirD/"])
def test_rglob_common(self): def test_rglob_follow_symlinks_none(self):
def _check(glob, expected): def _check(path, glob, expected):
self.assertEqual(set(glob), {P(self.base, q) for q in expected}) actual = set(path.rglob(glob, follow_symlinks=None))
self.assertEqual(actual, { P(self.base, q) for q in expected })
P = self.cls P = self.cls
p = P(self.base) p = P(self.base)
it = p.rglob("fileA") it = p.rglob("fileA")
self.assertIsInstance(it, collections.abc.Iterator) self.assertIsInstance(it, collections.abc.Iterator)
_check(it, ["fileA"]) _check(p, "fileA", ["fileA"])
_check(p.rglob("fileB"), ["dirB/fileB"]) _check(p, "fileB", ["dirB/fileB"])
_check(p.rglob("**/fileB"), ["dirB/fileB"]) _check(p, "**/fileB", ["dirB/fileB"])
_check(p.rglob("*/fileA"), []) _check(p, "*/fileA", [])
if not self.can_symlink:
_check(p.rglob("*/fileB"), ["dirB/fileB"])
else:
_check(p.rglob("*/fileB"), ["dirB/fileB", "dirB/linkD/fileB",
"linkB/fileB", "dirA/linkC/fileB"])
_check(p.rglob("file*"), ["fileA", "dirB/fileB",
"dirC/fileC", "dirC/dirD/fileD"])
if not self.can_symlink:
_check(p.rglob("*/"), [
"dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/",
])
else:
_check(p.rglob("*/"), [
"dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/",
"dirC/dirD/", "dirE/", "linkB/",
])
_check(p.rglob(""), ["", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"])
if self.can_symlink:
_check(p, "*/fileB", ["dirB/fileB", "dirB/linkD/fileB",
"linkB/fileB", "dirA/linkC/fileB"])
_check(p, "*/", [
"dirA/", "dirA/linkC/", "dirB/", "dirB/linkD/", "dirC/",
"dirC/dirD/", "dirE/", "linkB/"])
else:
_check(p, "*/fileB", ["dirB/fileB"])
_check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
_check(p, "file*", ["fileA", "dirB/fileB", "dirC/fileC", "dirC/dirD/fileD"])
_check(p, "", ["", "dirA/", "dirB/", "dirC/", "dirE/", "dirC/dirD/"])
p = P(self.base, "dirC") p = P(self.base, "dirC")
_check(p.rglob("*"), ["dirC/fileC", "dirC/novel.txt", _check(p, "*", ["dirC/fileC", "dirC/novel.txt",
"dirC/dirD", "dirC/dirD/fileD"]) "dirC/dirD", "dirC/dirD/fileD"])
_check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"]) _check(p, "file*", ["dirC/fileC", "dirC/dirD/fileD"])
_check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"]) _check(p, "**/file*", ["dirC/fileC", "dirC/dirD/fileD"])
_check(p.rglob("dir*/**"), ["dirC/dirD/", "dirC/dirD/fileD"]) _check(p, "dir*/**", ["dirC/dirD/", "dirC/dirD/fileD"])
_check(p.rglob("dir*/**/"), ["dirC/dirD/"]) _check(p, "dir*/**/", ["dirC/dirD/"])
_check(p.rglob("*/*"), ["dirC/dirD/fileD"]) _check(p, "*/*", ["dirC/dirD/fileD"])
_check(p.rglob("*/"), ["dirC/dirD/"]) _check(p, "*/", ["dirC/dirD/"])
_check(p.rglob(""), ["dirC/", "dirC/dirD/"]) _check(p, "", ["dirC/", "dirC/dirD/"])
_check(p.rglob("**"), [ _check(p, "**", ["dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"])
"dirC/", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"]) _check(p, "**/", ["dirC/", "dirC/dirD/"])
_check(p.rglob("**/"), ["dirC/", "dirC/dirD/"])
# gh-91616, a re module regression # gh-91616, a re module regression
_check(p.rglob("*.txt"), ["dirC/novel.txt"]) _check(p, "*.txt", ["dirC/novel.txt"])
_check(p.rglob("*.*"), ["dirC/novel.txt"]) _check(p, "*.*", ["dirC/novel.txt"])
@needs_posix @needs_posix
def test_rglob_posix(self): def test_rglob_posix(self):
@ -1969,7 +1964,7 @@ class DummyPathTest(DummyPurePathTest):
# Don't get fooled by symlink loops (Issue #26012). # Don't get fooled by symlink loops (Issue #26012).
P = self.cls P = self.cls
p = P(self.base) p = P(self.base)
given = set(p.rglob('*')) given = set(p.rglob('*', follow_symlinks=None))
expect = {'brokenLink', expect = {'brokenLink',
'dirA', 'dirA/linkC', 'dirA', 'dirA/linkC',
'dirB', 'dirB/fileB', 'dirB/linkD', 'dirB', 'dirB/fileB', 'dirB/linkD',