mirror of
https://github.com/python/cpython.git
synced 2025-09-23 17:03:23 +00:00
bpo-22276: Change pathlib.Path.glob not to ignore trailing path separator (GH-10349)
Now pathlib.Path.glob() **only** matches directories when the pattern ends in a path separator. Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
0ef8d921f5
commit
ea2f5bcda1
5 changed files with 42 additions and 0 deletions
|
@ -815,6 +815,9 @@ call fails (for example because the path doesn't exist).
|
||||||
|
|
||||||
.. audit-event:: pathlib.Path.glob self,pattern pathlib.Path.glob
|
.. audit-event:: pathlib.Path.glob self,pattern pathlib.Path.glob
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
Return only directories if *pattern* ends with a pathname components
|
||||||
|
separator (:data:`~os.sep` or :data:`~os.altsep`).
|
||||||
|
|
||||||
.. method:: Path.group()
|
.. method:: Path.group()
|
||||||
|
|
||||||
|
@ -1104,6 +1107,9 @@ call fails (for example because the path doesn't exist).
|
||||||
|
|
||||||
.. audit-event:: pathlib.Path.rglob self,pattern pathlib.Path.rglob
|
.. audit-event:: pathlib.Path.rglob self,pattern pathlib.Path.rglob
|
||||||
|
|
||||||
|
.. versionchanged:: 3.11
|
||||||
|
Return only directories if *pattern* ends with a pathname components
|
||||||
|
separator (:data:`~os.sep` or :data:`~os.altsep`).
|
||||||
|
|
||||||
.. method:: Path.rmdir()
|
.. method:: Path.rmdir()
|
||||||
|
|
||||||
|
|
|
@ -506,6 +506,15 @@ os
|
||||||
instead of ``CryptGenRandom()`` which is deprecated.
|
instead of ``CryptGenRandom()`` which is deprecated.
|
||||||
(Contributed by Dong-hee Na in :issue:`44611`.)
|
(Contributed by Dong-hee Na in :issue:`44611`.)
|
||||||
|
|
||||||
|
|
||||||
|
pathlib
|
||||||
|
-------
|
||||||
|
|
||||||
|
* :meth:`~pathlib.Path.glob` and :meth:`~pathlib.Path.rglob` return only
|
||||||
|
directories if *pattern* ends with a pathname components separator:
|
||||||
|
:data:`~os.sep` or :data:`~os.altsep`.
|
||||||
|
(Contributed by Eisuke Kawasima in :issue:`22276` and :issue:`33392`.)
|
||||||
|
|
||||||
re
|
re
|
||||||
--
|
--
|
||||||
|
|
||||||
|
|
|
@ -281,6 +281,8 @@ _posix_flavour = _PosixFlavour()
|
||||||
def _make_selector(pattern_parts, flavour):
|
def _make_selector(pattern_parts, flavour):
|
||||||
pat = pattern_parts[0]
|
pat = pattern_parts[0]
|
||||||
child_parts = pattern_parts[1:]
|
child_parts = pattern_parts[1:]
|
||||||
|
if not pat:
|
||||||
|
return _TerminatingSelector()
|
||||||
if pat == '**':
|
if pat == '**':
|
||||||
cls = _RecursiveWildcardSelector
|
cls = _RecursiveWildcardSelector
|
||||||
elif '**' in pat:
|
elif '**' in pat:
|
||||||
|
@ -943,6 +945,8 @@ class Path(PurePath):
|
||||||
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
|
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
|
||||||
if drv or root:
|
if drv or root:
|
||||||
raise NotImplementedError("Non-relative patterns are unsupported")
|
raise NotImplementedError("Non-relative patterns are unsupported")
|
||||||
|
if pattern[-1] in (self._flavour.sep, self._flavour.altsep):
|
||||||
|
pattern_parts.append('')
|
||||||
selector = _make_selector(tuple(pattern_parts), self._flavour)
|
selector = _make_selector(tuple(pattern_parts), self._flavour)
|
||||||
for p in selector.select_from(self):
|
for p in selector.select_from(self):
|
||||||
yield p
|
yield p
|
||||||
|
@ -956,6 +960,8 @@ class Path(PurePath):
|
||||||
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
|
drv, root, pattern_parts = self._flavour.parse_parts((pattern,))
|
||||||
if drv or root:
|
if drv or root:
|
||||||
raise NotImplementedError("Non-relative patterns are unsupported")
|
raise NotImplementedError("Non-relative patterns are unsupported")
|
||||||
|
if pattern[-1] in (self._flavour.sep, self._flavour.altsep):
|
||||||
|
pattern_parts.append('')
|
||||||
selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour)
|
selector = _make_selector(("**",) + tuple(pattern_parts), self._flavour)
|
||||||
for p in selector.select_from(self):
|
for p in selector.select_from(self):
|
||||||
yield p
|
yield p
|
||||||
|
|
|
@ -1662,6 +1662,11 @@ class _BasePathTest(object):
|
||||||
else:
|
else:
|
||||||
_check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB'])
|
_check(p.glob("*/fileB"), ['dirB/fileB', 'linkB/fileB'])
|
||||||
|
|
||||||
|
if not os_helper.can_symlink():
|
||||||
|
_check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE"])
|
||||||
|
else:
|
||||||
|
_check(p.glob("*/"), ["dirA", "dirB", "dirC", "dirE", "linkB"])
|
||||||
|
|
||||||
def test_rglob_common(self):
|
def test_rglob_common(self):
|
||||||
def _check(glob, expected):
|
def _check(glob, expected):
|
||||||
self.assertEqual(set(glob), { P(BASE, q) for q in expected })
|
self.assertEqual(set(glob), { P(BASE, q) for q in expected })
|
||||||
|
@ -1679,6 +1684,16 @@ class _BasePathTest(object):
|
||||||
"linkB/fileB", "dirA/linkC/fileB"])
|
"linkB/fileB", "dirA/linkC/fileB"])
|
||||||
_check(p.rglob("file*"), ["fileA", "dirB/fileB",
|
_check(p.rglob("file*"), ["fileA", "dirB/fileB",
|
||||||
"dirC/fileC", "dirC/dirD/fileD"])
|
"dirC/fileC", "dirC/dirD/fileD"])
|
||||||
|
if not os_helper.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",
|
||||||
|
])
|
||||||
|
|
||||||
p = P(BASE, "dirC")
|
p = P(BASE, "dirC")
|
||||||
_check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
|
_check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
|
||||||
_check(p.rglob("*/*"), ["dirC/dirD/fileD"])
|
_check(p.rglob("*/*"), ["dirC/dirD/fileD"])
|
||||||
|
@ -2704,6 +2719,7 @@ class WindowsPathTest(_BasePathTest, unittest.TestCase):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
p = P(BASE)
|
p = P(BASE)
|
||||||
self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") })
|
self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") })
|
||||||
|
self.assertEqual(set(p.glob("*a\\")), { P(BASE, "dirA") })
|
||||||
self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") })
|
self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") })
|
||||||
self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\FILEa"})
|
self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\FILEa"})
|
||||||
self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"})
|
self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"})
|
||||||
|
@ -2712,6 +2728,7 @@ class WindowsPathTest(_BasePathTest, unittest.TestCase):
|
||||||
P = self.cls
|
P = self.cls
|
||||||
p = P(BASE, "dirC")
|
p = P(BASE, "dirC")
|
||||||
self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })
|
self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })
|
||||||
|
self.assertEqual(set(p.rglob("*\\")), { P(BASE, "dirC/dirD") })
|
||||||
self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\FILEd"})
|
self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\FILEd"})
|
||||||
|
|
||||||
def test_expanduser(self):
|
def test_expanduser(self):
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
:class:`~pathlib.Path` methods :meth:`~pathlib.Path.glob` and :meth:`~pathlib.Path.rglob` return only
|
||||||
|
directories if *pattern* ends with a pathname components separator
|
||||||
|
(``/`` or :data:`~os.sep`).
|
||||||
|
Patch by Eisuke Kawashima.
|
Loading…
Add table
Add a link
Reference in a new issue