Skip to content

Commit e1be3cd

Browse files
committed
pythonGH-70303: Make pathlib.Path.glob('**') return both files and directories
Return files and directories from `pathlib.Path.glob()` if the pattern ends with `**`. This is more compatible with `PurePath.full_match()` and with other glob implementations such as bash and `glob.glob()`. Users can add a trailing slash to match only directories. In my previous patch I added a `FutureWarning` with the intention of fixing this in Python 3.15. Upon further reflection I think this was an unnecessarily cautious remedy to a clear bug.
1 parent a768e12 commit e1be3cd

File tree

5 files changed

+25
-23
lines changed

5 files changed

+25
-23
lines changed

Doc/library/pathlib.rst

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,9 +1038,8 @@ call fails (for example because the path doesn't exist).
10381038
The *follow_symlinks* parameter was added.
10391039

10401040
.. versionchanged:: 3.13
1041-
Emits :exc:`FutureWarning` if the pattern ends with "``**``". In a
1042-
future Python release, patterns with this ending will match both files
1043-
and directories. Add a trailing slash to match only directories.
1041+
Return files and directories if *pattern* ends with "``**``". In
1042+
previous versions, only directories were returned.
10441043

10451044
.. versionchanged:: 3.13
10461045
The *pattern* parameter accepts a :term:`path-like object`.

Lib/pathlib/__init__.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -465,14 +465,6 @@ def _pattern_stack(self):
465465
elif pattern[-1] in (self.pathmod.sep, self.pathmod.altsep):
466466
# GH-65238: pathlib doesn't preserve trailing slash. Add it back.
467467
parts.append('')
468-
elif parts[-1] == '**':
469-
# GH-70303: '**' only matches directories. Add trailing slash.
470-
warnings.warn(
471-
"Pattern ending '**' will match files and directories in a "
472-
"future Python release. Add a trailing slash to match only "
473-
"directories and remove this warning.",
474-
FutureWarning, 4)
475-
parts.append('')
476468
parts.reverse()
477469
return parts
478470

Lib/test/test_pathlib/test_pathlib.py

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1265,18 +1265,6 @@ def test_glob_above_recursion_limit(self):
12651265
with set_recursion_limit(recursion_limit):
12661266
list(base.glob('**/'))
12671267

1268-
def test_glob_recursive_no_trailing_slash(self):
1269-
P = self.cls
1270-
p = P(self.base)
1271-
with self.assertWarns(FutureWarning):
1272-
p.glob('**')
1273-
with self.assertWarns(FutureWarning):
1274-
p.glob('*/**')
1275-
with self.assertWarns(FutureWarning):
1276-
p.rglob('**')
1277-
with self.assertWarns(FutureWarning):
1278-
p.rglob('*/**')
1279-
12801268
def test_glob_pathlike(self):
12811269
P = self.cls
12821270
p = P(self.base)

Lib/test/test_pathlib/test_pathlib_abc.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,16 +1766,26 @@ def _check(path, glob, expected):
17661766
_check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"])
17671767
_check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
17681768
_check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."])
1769+
_check(p, "dir*/**", [
1770+
"dirA", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB",
1771+
"dirB", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB",
1772+
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
1773+
"dirE"])
17691774
_check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
17701775
"dirC/", "dirC/dirD/", "dirE/"])
17711776
_check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..",
17721777
"dirB/linkD/..", "dirA/linkC/linkD/..",
17731778
"dirC/..", "dirC/dirD/..", "dirE/.."])
1779+
_check(p, "dir*/*/**", [
1780+
"dirA/linkC", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB",
1781+
"dirB/linkD", "dirB/linkD/fileB",
1782+
"dirC/dirD", "dirC/dirD/fileD"])
17741783
_check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"])
17751784
_check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..",
17761785
"dirB/linkD/..", "dirC/dirD/.."])
17771786
_check(p, "dir*/**/fileC", ["dirC/fileC"])
17781787
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
1788+
_check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"])
17791789
_check(p, "*/dirD/**/", ["dirC/dirD/"])
17801790

17811791
@needs_symlinks
@@ -1792,12 +1802,20 @@ def _check(path, glob, expected):
17921802
_check(p, "*/fileB", ["dirB/fileB"])
17931803
_check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"])
17941804
_check(p, "dir*/*/..", ["dirC/dirD/.."])
1805+
_check(p, "dir*/**", [
1806+
"dirA", "dirA/linkC",
1807+
"dirB", "dirB/fileB", "dirB/linkD",
1808+
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
1809+
"dirE"])
17951810
_check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
17961811
_check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."])
1812+
_check(p, "dir*/*/**", ["dirC/dirD", "dirC/dirD/fileD"])
17971813
_check(p, "dir*/*/**/", ["dirC/dirD/"])
17981814
_check(p, "dir*/*/**/..", ["dirC/dirD/.."])
17991815
_check(p, "dir*/**/fileC", ["dirC/fileC"])
1816+
_check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD", "dirC/dirD/../dirD/fileD"])
18001817
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
1818+
_check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"])
18011819
_check(p, "*/dirD/**/", ["dirC/dirD/"])
18021820

18031821
def test_rglob_common(self):
@@ -1834,10 +1852,13 @@ def _check(glob, expected):
18341852
"dirC/dirD", "dirC/dirD/fileD"])
18351853
_check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
18361854
_check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"])
1855+
_check(p.rglob("dir*/**"), ["dirC/dirD", "dirC/dirD/fileD"])
18371856
_check(p.rglob("dir*/**/"), ["dirC/dirD/"])
18381857
_check(p.rglob("*/*"), ["dirC/dirD/fileD"])
18391858
_check(p.rglob("*/"), ["dirC/dirD/"])
18401859
_check(p.rglob(""), ["dirC/", "dirC/dirD/"])
1860+
_check(p.rglob("**"), [
1861+
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"])
18411862
_check(p.rglob("**/"), ["dirC/", "dirC/dirD/"])
18421863
# gh-91616, a re module regression
18431864
_check(p.rglob("*.txt"), ["dirC/novel.txt"])
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Return both files and directories from :meth:`pathlib.Path.glob` if a
2+
pattern ends with "``**``". Previously only directories were returned.

0 commit comments

Comments
 (0)