mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 19:34:08 +00:00 
			
		
		
		
	GH-70303: Make pathlib.Path.glob('**') return both files and directories (#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.
This commit is contained in:
		
							parent
							
								
									6de8aa31f3
								
							
						
					
					
						commit
						fda7445ca5
					
				
					 6 changed files with 35 additions and 23 deletions
				
			
		| 
						 | 
				
			
			@ -1038,9 +1038,8 @@ call fails (for example because the path doesn't exist).
 | 
			
		|||
      The *follow_symlinks* parameter was added.
 | 
			
		||||
 | 
			
		||||
   .. versionchanged:: 3.13
 | 
			
		||||
      Emits :exc:`FutureWarning` if the pattern ends with "``**``". In a
 | 
			
		||||
      future Python release, patterns with this ending will match both files
 | 
			
		||||
      and directories. Add a trailing slash to match only directories.
 | 
			
		||||
      Return files and directories if *pattern* ends with "``**``". In
 | 
			
		||||
      previous versions, only directories were returned.
 | 
			
		||||
 | 
			
		||||
   .. versionchanged:: 3.13
 | 
			
		||||
      The *pattern* parameter accepts a :term:`path-like object`.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -350,6 +350,11 @@ pathlib
 | 
			
		|||
  (Contributed by Barney Gale in :gh:`77609` and :gh:`105793`, and
 | 
			
		||||
  Kamil Turek in :gh:`107962`).
 | 
			
		||||
 | 
			
		||||
* Return files and directories from :meth:`pathlib.Path.glob` and
 | 
			
		||||
  :meth:`~pathlib.Path.rglob` when given a pattern that ends with "``**``". In
 | 
			
		||||
  earlier versions, only directories were returned.
 | 
			
		||||
  (Contributed by Barney Gale in :gh:`70303`).
 | 
			
		||||
 | 
			
		||||
pdb
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1211,6 +1216,11 @@ Changes in the Python API
 | 
			
		|||
* :class:`mailbox.Maildir` now ignores files with a leading dot.
 | 
			
		||||
  (Contributed by Zackery Spytz in :gh:`65559`.)
 | 
			
		||||
 | 
			
		||||
* :meth:`pathlib.Path.glob` and :meth:`~pathlib.Path.rglob` now return both
 | 
			
		||||
  files and directories if a pattern that ends with "``**``" is given, rather
 | 
			
		||||
  than directories only. Users may add a trailing slash to match only
 | 
			
		||||
  directories.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Build Changes
 | 
			
		||||
=============
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -465,14 +465,6 @@ class PurePath(_abc.PurePathBase):
 | 
			
		|||
        elif pattern[-1] in (self.pathmod.sep, self.pathmod.altsep):
 | 
			
		||||
            # GH-65238: pathlib doesn't preserve trailing slash. Add it back.
 | 
			
		||||
            parts.append('')
 | 
			
		||||
        elif parts[-1] == '**':
 | 
			
		||||
            # GH-70303: '**' only matches directories. Add trailing slash.
 | 
			
		||||
            warnings.warn(
 | 
			
		||||
                "Pattern ending '**' will match files and directories in a "
 | 
			
		||||
                "future Python release. Add a trailing slash to match only "
 | 
			
		||||
                "directories and remove this warning.",
 | 
			
		||||
                FutureWarning, 4)
 | 
			
		||||
            parts.append('')
 | 
			
		||||
        parts.reverse()
 | 
			
		||||
        return parts
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1258,18 +1258,6 @@ class PathTest(test_pathlib_abc.DummyPathTest, PurePathTest):
 | 
			
		|||
        with set_recursion_limit(recursion_limit):
 | 
			
		||||
            list(base.glob('**/'))
 | 
			
		||||
 | 
			
		||||
    def test_glob_recursive_no_trailing_slash(self):
 | 
			
		||||
        P = self.cls
 | 
			
		||||
        p = P(self.base)
 | 
			
		||||
        with self.assertWarns(FutureWarning):
 | 
			
		||||
            p.glob('**')
 | 
			
		||||
        with self.assertWarns(FutureWarning):
 | 
			
		||||
            p.glob('*/**')
 | 
			
		||||
        with self.assertWarns(FutureWarning):
 | 
			
		||||
            p.rglob('**')
 | 
			
		||||
        with self.assertWarns(FutureWarning):
 | 
			
		||||
            p.rglob('*/**')
 | 
			
		||||
 | 
			
		||||
    def test_glob_pathlike(self):
 | 
			
		||||
        P = self.cls
 | 
			
		||||
        p = P(self.base)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1765,16 +1765,26 @@ class DummyPathTest(DummyPurePathTest):
 | 
			
		|||
        _check(p, "*/fileB", ["dirB/fileB", "linkB/fileB"])
 | 
			
		||||
        _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/", "linkB/"])
 | 
			
		||||
        _check(p, "dir*/*/..", ["dirC/dirD/..", "dirA/linkC/..", "dirB/linkD/.."])
 | 
			
		||||
        _check(p, "dir*/**", [
 | 
			
		||||
            "dirA", "dirA/linkC", "dirA/linkC/fileB", "dirA/linkC/linkD", "dirA/linkC/linkD/fileB",
 | 
			
		||||
            "dirB", "dirB/fileB", "dirB/linkD", "dirB/linkD/fileB",
 | 
			
		||||
            "dirC", "dirC/fileC", "dirC/dirD",  "dirC/dirD/fileD", "dirC/novel.txt",
 | 
			
		||||
            "dirE"])
 | 
			
		||||
        _check(p, "dir*/**/", ["dirA/", "dirA/linkC/", "dirA/linkC/linkD/", "dirB/", "dirB/linkD/",
 | 
			
		||||
                               "dirC/", "dirC/dirD/", "dirE/"])
 | 
			
		||||
        _check(p, "dir*/**/..", ["dirA/..", "dirA/linkC/..", "dirB/..",
 | 
			
		||||
                                 "dirB/linkD/..", "dirA/linkC/linkD/..",
 | 
			
		||||
                                 "dirC/..", "dirC/dirD/..", "dirE/.."])
 | 
			
		||||
        _check(p, "dir*/*/**", [
 | 
			
		||||
            "dirA/linkC", "dirA/linkC/linkD", "dirA/linkC/fileB", "dirA/linkC/linkD/fileB",
 | 
			
		||||
            "dirB/linkD", "dirB/linkD/fileB",
 | 
			
		||||
            "dirC/dirD", "dirC/dirD/fileD"])
 | 
			
		||||
        _check(p, "dir*/*/**/", ["dirA/linkC/", "dirA/linkC/linkD/", "dirB/linkD/", "dirC/dirD/"])
 | 
			
		||||
        _check(p, "dir*/*/**/..", ["dirA/linkC/..", "dirA/linkC/linkD/..",
 | 
			
		||||
                                   "dirB/linkD/..", "dirC/dirD/.."])
 | 
			
		||||
        _check(p, "dir*/**/fileC", ["dirC/fileC"])
 | 
			
		||||
        _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
 | 
			
		||||
        _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"])
 | 
			
		||||
        _check(p, "*/dirD/**/", ["dirC/dirD/"])
 | 
			
		||||
 | 
			
		||||
    @needs_symlinks
 | 
			
		||||
| 
						 | 
				
			
			@ -1791,12 +1801,20 @@ class DummyPathTest(DummyPurePathTest):
 | 
			
		|||
        _check(p, "*/fileB", ["dirB/fileB"])
 | 
			
		||||
        _check(p, "*/", ["dirA/", "dirB/", "dirC/", "dirE/"])
 | 
			
		||||
        _check(p, "dir*/*/..", ["dirC/dirD/.."])
 | 
			
		||||
        _check(p, "dir*/**", [
 | 
			
		||||
            "dirA", "dirA/linkC",
 | 
			
		||||
            "dirB", "dirB/fileB", "dirB/linkD",
 | 
			
		||||
            "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt",
 | 
			
		||||
            "dirE"])
 | 
			
		||||
        _check(p, "dir*/**/", ["dirA/", "dirB/", "dirC/", "dirC/dirD/", "dirE/"])
 | 
			
		||||
        _check(p, "dir*/**/..", ["dirA/..", "dirB/..", "dirC/..", "dirC/dirD/..", "dirE/.."])
 | 
			
		||||
        _check(p, "dir*/*/**", ["dirC/dirD", "dirC/dirD/fileD"])
 | 
			
		||||
        _check(p, "dir*/*/**/", ["dirC/dirD/"])
 | 
			
		||||
        _check(p, "dir*/*/**/..", ["dirC/dirD/.."])
 | 
			
		||||
        _check(p, "dir*/**/fileC", ["dirC/fileC"])
 | 
			
		||||
        _check(p, "dir*/*/../dirD/**", ["dirC/dirD/../dirD", "dirC/dirD/../dirD/fileD"])
 | 
			
		||||
        _check(p, "dir*/*/../dirD/**/", ["dirC/dirD/../dirD/"])
 | 
			
		||||
        _check(p, "*/dirD/**", ["dirC/dirD", "dirC/dirD/fileD"])
 | 
			
		||||
        _check(p, "*/dirD/**/", ["dirC/dirD/"])
 | 
			
		||||
 | 
			
		||||
    def test_rglob_common(self):
 | 
			
		||||
| 
						 | 
				
			
			@ -1833,10 +1851,13 @@ class DummyPathTest(DummyPurePathTest):
 | 
			
		|||
                              "dirC/dirD", "dirC/dirD/fileD"])
 | 
			
		||||
        _check(p.rglob("file*"), ["dirC/fileC", "dirC/dirD/fileD"])
 | 
			
		||||
        _check(p.rglob("**/file*"), ["dirC/fileC", "dirC/dirD/fileD"])
 | 
			
		||||
        _check(p.rglob("dir*/**"), ["dirC/dirD", "dirC/dirD/fileD"])
 | 
			
		||||
        _check(p.rglob("dir*/**/"), ["dirC/dirD/"])
 | 
			
		||||
        _check(p.rglob("*/*"), ["dirC/dirD/fileD"])
 | 
			
		||||
        _check(p.rglob("*/"), ["dirC/dirD/"])
 | 
			
		||||
        _check(p.rglob(""), ["dirC/", "dirC/dirD/"])
 | 
			
		||||
        _check(p.rglob("**"), [
 | 
			
		||||
            "dirC", "dirC/fileC", "dirC/dirD", "dirC/dirD/fileD", "dirC/novel.txt"])
 | 
			
		||||
        _check(p.rglob("**/"), ["dirC/", "dirC/dirD/"])
 | 
			
		||||
        # gh-91616, a re module regression
 | 
			
		||||
        _check(p.rglob("*.txt"), ["dirC/novel.txt"])
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,2 @@
 | 
			
		|||
Return both files and directories from :meth:`pathlib.Path.glob` if a
 | 
			
		||||
pattern ends with "``**``". Previously only directories were returned.
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue