Skip to content

Commit da1980a

Browse files
authored
GH-104114: Fix pathlib.WindowsPath.glob() use of literal pattern segment case (GH-104116)
We now use `_WildcardSelector` to evaluate literal pattern segments, which allows us to retrieve the real filesystem case. This change is necessary in order to implement a *case_sensitive* argument (see GH-81079) and a *follow_symlinks* argument (see GH-77609).
1 parent 38dc3f2 commit da1980a

File tree

3 files changed

+18
-41
lines changed

3 files changed

+18
-41
lines changed

Lib/pathlib.py

+13-39
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,6 @@ def _ignore_error(exception):
5454
getattr(exception, 'winerror', None) in _IGNORED_WINERRORS)
5555

5656

57-
def _is_wildcard_pattern(pat):
58-
# Whether this pattern needs actual matching using fnmatch, or can
59-
# be looked up directly as a file.
60-
return "*" in pat or "?" in pat or "[" in pat
61-
6257
def _is_case_sensitive(flavour):
6358
return flavour.normcase('Aa') == 'Aa'
6459

@@ -78,10 +73,8 @@ def _make_selector(pattern_parts, flavour):
7873
cls = _ParentSelector
7974
elif '**' in pat:
8075
raise ValueError("Invalid pattern: '**' can only be an entire path component")
81-
elif _is_wildcard_pattern(pat):
82-
cls = _WildcardSelector
8376
else:
84-
cls = _PreciseSelector
77+
cls = _WildcardSelector
8578
return cls(pat, child_parts, flavour)
8679

8780

@@ -102,55 +95,36 @@ def select_from(self, parent_path):
10295
"""Iterate over all child paths of `parent_path` matched by this
10396
selector. This can contain parent_path itself."""
10497
path_cls = type(parent_path)
105-
is_dir = path_cls.is_dir
106-
exists = path_cls.exists
10798
scandir = path_cls._scandir
108-
if not is_dir(parent_path):
99+
if not parent_path.is_dir():
109100
return iter([])
110-
return self._select_from(parent_path, is_dir, exists, scandir)
101+
return self._select_from(parent_path, scandir)
111102

112103

113104
class _TerminatingSelector:
114105

115-
def _select_from(self, parent_path, is_dir, exists, scandir):
106+
def _select_from(self, parent_path, scandir):
116107
yield parent_path
117108

118109

119110
class _ParentSelector(_Selector):
120111
def __init__(self, name, child_parts, flavour):
121112
_Selector.__init__(self, child_parts, flavour)
122113

123-
def _select_from(self, parent_path, is_dir, exists, scandir):
114+
def _select_from(self, parent_path, scandir):
124115
path = parent_path._make_child_relpath('..')
125-
for p in self.successor._select_from(path, is_dir, exists, scandir):
116+
for p in self.successor._select_from(path, scandir):
126117
yield p
127118

128119

129-
class _PreciseSelector(_Selector):
130-
131-
def __init__(self, name, child_parts, flavour):
132-
self.name = name
133-
_Selector.__init__(self, child_parts, flavour)
134-
135-
def _select_from(self, parent_path, is_dir, exists, scandir):
136-
try:
137-
path = parent_path._make_child_relpath(self.name)
138-
follow = is_dir(path) if self.dironly else exists(path, follow_symlinks=False)
139-
if follow:
140-
for p in self.successor._select_from(path, is_dir, exists, scandir):
141-
yield p
142-
except PermissionError:
143-
return
144-
145-
146120
class _WildcardSelector(_Selector):
147121

148122
def __init__(self, pat, child_parts, flavour):
149123
flags = re.NOFLAG if _is_case_sensitive(flavour) else re.IGNORECASE
150124
self.match = re.compile(fnmatch.translate(pat), flags=flags).fullmatch
151125
_Selector.__init__(self, child_parts, flavour)
152126

153-
def _select_from(self, parent_path, is_dir, exists, scandir):
127+
def _select_from(self, parent_path, scandir):
154128
try:
155129
# We must close the scandir() object before proceeding to
156130
# avoid exhausting file descriptors when globbing deep trees.
@@ -171,7 +145,7 @@ def _select_from(self, parent_path, is_dir, exists, scandir):
171145
name = entry.name
172146
if self.match(name):
173147
path = parent_path._make_child_relpath(name)
174-
for p in self.successor._select_from(path, is_dir, exists, scandir):
148+
for p in self.successor._select_from(path, scandir):
175149
yield p
176150
except PermissionError:
177151
return
@@ -182,7 +156,7 @@ class _RecursiveWildcardSelector(_Selector):
182156
def __init__(self, pat, child_parts, flavour):
183157
_Selector.__init__(self, child_parts, flavour)
184158

185-
def _iterate_directories(self, parent_path, is_dir, scandir):
159+
def _iterate_directories(self, parent_path, scandir):
186160
yield parent_path
187161
try:
188162
# We must close the scandir() object before proceeding to
@@ -198,18 +172,18 @@ def _iterate_directories(self, parent_path, is_dir, scandir):
198172
raise
199173
if entry_is_dir and not entry.is_symlink():
200174
path = parent_path._make_child_relpath(entry.name)
201-
for p in self._iterate_directories(path, is_dir, scandir):
175+
for p in self._iterate_directories(path, scandir):
202176
yield p
203177
except PermissionError:
204178
return
205179

206-
def _select_from(self, parent_path, is_dir, exists, scandir):
180+
def _select_from(self, parent_path, scandir):
207181
try:
208182
yielded = set()
209183
try:
210184
successor_select = self.successor._select_from
211-
for starting_point in self._iterate_directories(parent_path, is_dir, scandir):
212-
for p in successor_select(starting_point, is_dir, exists, scandir):
185+
for starting_point in self._iterate_directories(parent_path, scandir):
186+
for p in successor_select(starting_point, scandir):
213187
if p not in yielded:
214188
yield p
215189
yielded.add(p)

Lib/test/test_pathlib.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -3122,15 +3122,15 @@ def test_glob(self):
31223122
self.assertEqual(set(p.glob("FILEa")), { P(BASE, "fileA") })
31233123
self.assertEqual(set(p.glob("*a\\")), { P(BASE, "dirA") })
31243124
self.assertEqual(set(p.glob("F*a")), { P(BASE, "fileA") })
3125-
self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\FILEa"})
3125+
self.assertEqual(set(map(str, p.glob("FILEa"))), {f"{p}\\fileA"})
31263126
self.assertEqual(set(map(str, p.glob("F*a"))), {f"{p}\\fileA"})
31273127

31283128
def test_rglob(self):
31293129
P = self.cls
31303130
p = P(BASE, "dirC")
31313131
self.assertEqual(set(p.rglob("FILEd")), { P(BASE, "dirC/dirD/fileD") })
31323132
self.assertEqual(set(p.rglob("*\\")), { P(BASE, "dirC/dirD") })
3133-
self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\FILEd"})
3133+
self.assertEqual(set(map(str, p.rglob("FILEd"))), {f"{p}\\dirD\\fileD"})
31343134

31353135
def test_expanduser(self):
31363136
P = self.cls
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix issue where :meth:`pathlib.Path.glob` returns paths using the case of
2+
non-wildcard segments for corresponding path segments, rather than the real
3+
filesystem case.

0 commit comments

Comments
 (0)