Skip to content

Commit

Permalink
GH-104102: Optimize pathlib.Path.glob() handling of ../ pattern s…
Browse files Browse the repository at this point in the history
…egments (GH-104103)

These segments do not require a `stat()` call, as the selector's
`_select_from()` method is called after we've established that the
parent is a directory.
  • Loading branch information
barneygale authored May 2, 2023
1 parent 47770a1 commit 65a49c6
Show file tree
Hide file tree
Showing 3 changed files with 19 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Lib/pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ def _make_selector(pattern_parts, flavour):
return _TerminatingSelector()
if pat == '**':
cls = _RecursiveWildcardSelector
elif pat == '..':
cls = _ParentSelector
elif '**' in pat:
raise ValueError("Invalid pattern: '**' can only be an entire path component")
elif _is_wildcard_pattern(pat):
Expand Down Expand Up @@ -114,6 +116,16 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
yield parent_path


class _ParentSelector(_Selector):
def __init__(self, name, child_parts, flavour):
_Selector.__init__(self, child_parts, flavour)

def _select_from(self, parent_path, is_dir, exists, scandir):
path = parent_path._make_child_relpath('..')
for p in self.successor._select_from(path, is_dir, exists, scandir):
yield p


class _PreciseSelector(_Selector):

def __init__(self, name, child_parts, flavour):
Expand Down
5 changes: 5 additions & 0 deletions Lib/test/test_pathlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1892,8 +1892,13 @@ def test_glob_dotdot(self):
P = self.cls
p = P(BASE)
self.assertEqual(set(p.glob("..")), { P(BASE, "..") })
self.assertEqual(set(p.glob("../..")), { P(BASE, "..", "..") })
self.assertEqual(set(p.glob("dirA/..")), { P(BASE, "dirA", "..") })
self.assertEqual(set(p.glob("dirA/../file*")), { P(BASE, "dirA/../fileA") })
self.assertEqual(set(p.glob("dirA/../file*/..")), set())
self.assertEqual(set(p.glob("../xyzzy")), set())
self.assertEqual(set(p.glob("xyzzy/..")), set())
self.assertEqual(set(p.glob("/".join([".."] * 50))), { P(BASE, *[".."] * 50)})

@os_helper.skip_unless_symlink
def test_glob_permissions(self):
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Improve performance of :meth:`pathlib.Path.glob` when evaluating patterns
that contain ``'../'`` segments.

0 comments on commit 65a49c6

Please sign in to comment.