Skip to content

Commit cb88ae6

Browse files
authored
GH-102613: Fix recursion error from pathlib.Path.glob() (GH-104373)
Use `Path.walk()` to implement the recursive wildcard `**`. This method uses an iterative (rather than recursive) walk - see GH-100282.
1 parent b378d99 commit cb88ae6

File tree

3 files changed

+18
-20
lines changed

3 files changed

+18
-20
lines changed

Lib/pathlib.py

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -164,30 +164,15 @@ class _RecursiveWildcardSelector(_Selector):
164164
def __init__(self, pat, child_parts, flavour, case_sensitive):
165165
_Selector.__init__(self, child_parts, flavour, case_sensitive)
166166

167-
def _iterate_directories(self, parent_path, scandir):
167+
def _iterate_directories(self, parent_path):
168168
yield parent_path
169-
try:
170-
# We must close the scandir() object before proceeding to
171-
# avoid exhausting file descriptors when globbing deep trees.
172-
with scandir(parent_path) as scandir_it:
173-
entries = list(scandir_it)
174-
except OSError:
175-
pass
176-
else:
177-
for entry in entries:
178-
entry_is_dir = False
179-
try:
180-
entry_is_dir = entry.is_dir(follow_symlinks=False)
181-
except OSError:
182-
pass
183-
if entry_is_dir:
184-
path = parent_path._make_child_relpath(entry.name)
185-
for p in self._iterate_directories(path, scandir):
186-
yield p
169+
for dirpath, dirnames, _ in parent_path.walk():
170+
for dirname in dirnames:
171+
yield dirpath._make_child_relpath(dirname)
187172

188173
def _select_from(self, parent_path, scandir):
189174
successor_select = self.successor._select_from
190-
for starting_point in self._iterate_directories(parent_path, scandir):
175+
for starting_point in self._iterate_directories(parent_path):
191176
for p in successor_select(starting_point, scandir):
192177
yield p
193178

Lib/test/test_pathlib.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,6 +1972,17 @@ def test_glob_long_symlink(self):
19721972
bad_link.symlink_to("bad" * 200)
19731973
self.assertEqual(sorted(base.glob('**/*')), [bad_link])
19741974

1975+
def test_glob_above_recursion_limit(self):
1976+
recursion_limit = 40
1977+
# directory_depth > recursion_limit
1978+
directory_depth = recursion_limit + 10
1979+
base = pathlib.Path(os_helper.TESTFN, 'deep')
1980+
path = pathlib.Path(base, *(['d'] * directory_depth))
1981+
path.mkdir(parents=True)
1982+
1983+
with set_recursion_limit(recursion_limit):
1984+
list(base.glob('**'))
1985+
19751986
def _check_resolve(self, p, expected, strict=True):
19761987
q = p.resolve(strict)
19771988
self.assertEqual(q, expected)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix issue where :meth:`pathlib.Path.glob` raised :exc:`RecursionError` when
2+
walking deep directory trees.

0 commit comments

Comments
 (0)