Skip to content

Commit dcdc90d

Browse files
pythonGH-104484: Add case_sensitive argument to pathlib.PurePath.match() (pythonGH-104565)
Co-authored-by: Barney Gale <[email protected]>
1 parent cfa517d commit dcdc90d

File tree

5 files changed

+30
-8
lines changed

5 files changed

+30
-8
lines changed

Doc/library/pathlib.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,7 @@ Pure paths provide the following methods and properties:
546546
PureWindowsPath('c:/Program Files')
547547

548548

549-
.. method:: PurePath.match(pattern)
549+
.. method:: PurePath.match(pattern, *, case_sensitive=None)
550550

551551
Match this path against the provided glob-style pattern. Return ``True``
552552
if matching is successful, ``False`` otherwise.
@@ -576,6 +576,11 @@ Pure paths provide the following methods and properties:
576576
>>> PureWindowsPath('b.py').match('*.PY')
577577
True
578578

579+
Set *case_sensitive* to ``True`` or ``False`` to override this behaviour.
580+
581+
.. versionadded:: 3.12
582+
The *case_sensitive* argument.
583+
579584

580585
.. method:: PurePath.relative_to(other, walk_up=False)
581586

Doc/whatsnew/3.12.rst

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@ pathlib
395395
* Add :meth:`pathlib.Path.is_junction` as a proxy to :func:`os.path.isjunction`.
396396
(Contributed by Charles Machalow in :gh:`99547`.)
397397

398+
* Add *case_sensitive* optional parameter to :meth:`pathlib.Path.glob`,
399+
:meth:`pathlib.Path.rglob` and :meth:`pathlib.PurePath.match` for matching
400+
the path's case sensitivity, allowing for more precise control over the matching process.
398401

399402
dis
400403
---

Lib/pathlib.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ def _make_selector(pattern_parts, flavour, case_sensitive):
8686
return cls(pat, child_parts, flavour, case_sensitive)
8787

8888

89+
@functools.lru_cache(maxsize=256)
90+
def _compile_pattern(pat, case_sensitive):
91+
flags = re.NOFLAG if case_sensitive else re.IGNORECASE
92+
return re.compile(fnmatch.translate(pat), flags).match
93+
94+
8995
class _Selector:
9096
"""A selector matches a specific glob pattern part against the children
9197
of a given path."""
@@ -133,8 +139,7 @@ def __init__(self, pat, child_parts, flavour, case_sensitive):
133139
if case_sensitive is None:
134140
# TODO: evaluate case-sensitivity of each directory in _select_from()
135141
case_sensitive = _is_case_sensitive(flavour)
136-
flags = re.NOFLAG if case_sensitive else re.IGNORECASE
137-
self.match = re.compile(fnmatch.translate(pat), flags=flags).fullmatch
142+
self.match = _compile_pattern(pat, case_sensitive)
138143

139144
def _select_from(self, parent_path, scandir):
140145
try:
@@ -680,22 +685,25 @@ def is_reserved(self):
680685
name = self._tail[-1].partition('.')[0].partition(':')[0].rstrip(' ')
681686
return name.upper() in _WIN_RESERVED_NAMES
682687

683-
def match(self, path_pattern):
688+
def match(self, path_pattern, *, case_sensitive=None):
684689
"""
685690
Return True if this path matches the given pattern.
686691
"""
692+
if case_sensitive is None:
693+
case_sensitive = _is_case_sensitive(self._flavour)
687694
pat = self.with_segments(path_pattern)
688695
if not pat.parts:
689696
raise ValueError("empty pattern")
690-
pat_parts = pat._parts_normcase
691-
parts = self._parts_normcase
697+
pat_parts = pat.parts
698+
parts = self.parts
692699
if pat.drive or pat.root:
693700
if len(pat_parts) != len(parts):
694701
return False
695702
elif len(pat_parts) > len(parts):
696703
return False
697704
for part, pat in zip(reversed(parts), reversed(pat_parts)):
698-
if not fnmatch.fnmatchcase(part, pat):
705+
match = _compile_pattern(pat, case_sensitive)
706+
if not match(part):
699707
return False
700708
return True
701709

Lib/test/test_pathlib.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,11 @@ def test_match_common(self):
312312
# Multi-part glob-style pattern.
313313
self.assertFalse(P('/a/b/c.py').match('/**/*.py'))
314314
self.assertTrue(P('/a/b/c.py').match('/a/**/*.py'))
315+
# Case-sensitive flag
316+
self.assertFalse(P('A.py').match('a.PY', case_sensitive=True))
317+
self.assertTrue(P('A.py').match('a.PY', case_sensitive=False))
318+
self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True))
319+
self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False))
315320

316321
def test_ordering_common(self):
317322
# Ordering is tuple-alike.
@@ -916,7 +921,7 @@ def test_as_uri(self):
916921
self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(),
917922
'file://some/share/a/b%25%23c%C3%A9')
918923

919-
def test_match_common(self):
924+
def test_match(self):
920925
P = self.cls
921926
# Absolute patterns.
922927
self.assertTrue(P('c:/b.py').match('*:/*.py'))
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Added *case_sensitive* argument to :meth:`pathlib.PurePath.match`

0 commit comments

Comments
 (0)