Skip to content

Commit 375fde6

Browse files
barneygaleaisk
authored andcommitted
pythonGH-70303: Make pathlib.Path.glob('**') return both files and directories (python#114684)
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 d9157c3 commit 375fde6

File tree

6 files changed

+35
-23
lines changed

6 files changed

+35
-23
lines changed

Doc/library/pathlib.rst

+2-3
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`.

Doc/whatsnew/3.13.rst

+10
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,11 @@ pathlib
350350
(Contributed by Barney Gale in :gh:`77609` and :gh:`105793`, and
351351
Kamil Turek in :gh:`107962`).
352352

353+
* Return files and directories from :meth:`pathlib.Path.glob` and
354+
:meth:`~pathlib.Path.rglob` when given a pattern that ends with "``**``". In
355+
earlier versions, only directories were returned.
356+
(Contributed by Barney Gale in :gh:`70303`).
357+
353358
pdb
354359
---
355360

@@ -1211,6 +1216,11 @@ Changes in the Python API
12111216
* :class:`mailbox.Maildir` now ignores files with a leading dot.
12121217
(Contributed by Zackery Spytz in :gh:`65559`.)
12131218

1219+
* :meth:`pathlib.Path.glob` and :meth:`~pathlib.Path.rglob` now return both
1220+
files and directories if a pattern that ends with "``**``" is given, rather
1221+
than directories only. Users may add a trailing slash to match only
1222+
directories.
1223+
12141224

12151225
Build Changes
12161226
=============

Lib/pathlib/__init__.py

-8
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

-12
Original file line numberDiff line numberDiff line change
@@ -1258,18 +1258,6 @@ def test_glob_above_recursion_limit(self):
12581258
with set_recursion_limit(recursion_limit):
12591259
list(base.glob('**/'))
12601260

1261-
def test_glob_recursive_no_trailing_slash(self):
1262-
P = self.cls
1263-
p = P(self.base)
1264-
with self.assertWarns(FutureWarning):
1265-
p.glob('**')
1266-
with self.assertWarns(FutureWarning):
1267-
p.glob('*/**')
1268-
with self.assertWarns(FutureWarning):
1269-
p.rglob('**')
1270-
with self.assertWarns(FutureWarning):
1271-
p.rglob('*/**')
1272-
12731261
def test_glob_pathlike(self):
12741262
P = self.cls
12751263
p = P(self.base)

Lib/test/test_pathlib/test_pathlib_abc.py

+21
Original file line numberDiff line numberDiff line change
@@ -1765,16 +1765,26 @@ def _check(path, glob, expected):
17651765
_check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"])
17661766
_check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
17671767
_check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."])
1768+
_check(p, "dir*/**", [
1769+
"dirA", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB",
1770+
"dirB", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB",
1771+
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
1772+
"dirE"])
17681773
_check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
17691774
"dirC/", "dirC/dirD/", "dirE/"])
17701775
_check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..",
17711776
"dirB/linkD/..", "dirA/linkC/linkD/..",
17721777
"dirC/..", "dirC/dirD/..", "dirE/.."])
1778+
_check(p, "dir*/*/**", [
1779+
"dirA/linkC", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB",
1780+
"dirB/linkD", "dirB/linkD/fileB",
1781+
"dirC/dirD", "dirC/dirD/fileD"])
17731782
_check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"])
17741783
_check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..",
17751784
"dirB/linkD/..", "dirC/dirD/.."])
17761785
_check(p, "dir*/**/fileC", ["dirC/fileC"])
17771786
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
1787+
_check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"])
17781788
_check(p, "*/dirD/**/", ["dirC/dirD/"])
17791789

17801790
@needs_symlinks
@@ -1791,12 +1801,20 @@ def _check(path, glob, expected):
17911801
_check(p, "*/fileB", ["dirB/fileB"])
17921802
_check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"])
17931803
_check(p, "dir*/*/..", ["dirC/dirD/.."])
1804+
_check(p, "dir*/**", [
1805+
"dirA", "dirA/linkC",
1806+
"dirB", "dirB/fileB", "dirB/linkD",
1807+
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
1808+
"dirE"])
17941809
_check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
17951810
_check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."])
1811+
_check(p, "dir*/*/**", ["dirC/dirD", "dirC/dirD/fileD"])
17961812
_check(p, "dir*/*/**/", ["dirC/dirD/"])
17971813
_check(p, "dir*/*/**/..", ["dirC/dirD/.."])
17981814
_check(p, "dir*/**/fileC", ["dirC/fileC"])
1815+
_check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD", "dirC/dirD/../dirD/fileD"])
17991816
_check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
1817+
_check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"])
18001818
_check(p, "*/dirD/**/", ["dirC/dirD/"])
18011819

18021820
def test_rglob_common(self):
@@ -1833,10 +1851,13 @@ def _check(glob, expected):
18331851
"dirC/dirD", "dirC/dirD/fileD"])
18341852
_check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
18351853
_check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"])
1854+
_check(p.rglob("dir*/**"), ["dirC/dirD", "dirC/dirD/fileD"])
18361855
_check(p.rglob("dir*/**/"), ["dirC/dirD/"])
18371856
_check(p.rglob("*/*"), ["dirC/dirD/fileD"])
18381857
_check(p.rglob("*/"), ["dirC/dirD/"])
18391858
_check(p.rglob(""), ["dirC/", "dirC/dirD/"])
1859+
_check(p.rglob("**"), [
1860+
"dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"])
18401861
_check(p.rglob("**/"), ["dirC/", "dirC/dirD/"])
18411862
# gh-91616, a re module regression
18421863
_check(p.rglob("*.txt"), ["dirC/novel.txt"])
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)