From dcdc90d384723920e8dea0ee04eae8c219333634 Mon Sep 17 00:00:00 2001 From: thirumurugan <67573527+thirumurugan-git@users.noreply.github.com> Date: Thu, 18 May 2023 23:29:31 +0530 Subject: [PATCH] GH-104484: Add case_sensitive argument to `pathlib.PurePath.match()` (GH-104565) Co-authored-by: Barney Gale --- Doc/library/pathlib.rst | 7 ++++++- Doc/whatsnew/3.12.rst | 3 +++ Lib/pathlib.py | 20 +++++++++++++------ Lib/test/test_pathlib.py | 7 ++++++- ...-05-17-03-14-07.gh-issue-104484.y6KxL6.rst | 1 + 5 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2023-05-17-03-14-07.gh-issue-104484.y6KxL6.rst diff --git a/Doc/library/pathlib.rst b/Doc/library/pathlib.rst index 93af07ae5ac..627f2df9263 100644 --- a/Doc/library/pathlib.rst +++ b/Doc/library/pathlib.rst @@ -546,7 +546,7 @@ Pure paths provide the following methods and properties: PureWindowsPath('c:/Program Files') -.. method:: PurePath.match(pattern) +.. method:: PurePath.match(pattern, *, case_sensitive=None) Match this path against the provided glob-style pattern. Return ``True`` if matching is successful, ``False`` otherwise. @@ -576,6 +576,11 @@ Pure paths provide the following methods and properties: >>> PureWindowsPath('b.py').match('*.PY') True + Set *case_sensitive* to ``True`` or ``False`` to override this behaviour. + + .. versionadded:: 3.12 + The *case_sensitive* argument. + .. method:: PurePath.relative_to(other, walk_up=False) diff --git a/Doc/whatsnew/3.12.rst b/Doc/whatsnew/3.12.rst index 3e55b3fa0f4..25f0a4c3ca2 100644 --- a/Doc/whatsnew/3.12.rst +++ b/Doc/whatsnew/3.12.rst @@ -395,6 +395,9 @@ pathlib * Add :meth:`pathlib.Path.is_junction` as a proxy to :func:`os.path.isjunction`. (Contributed by Charles Machalow in :gh:`99547`.) +* Add *case_sensitive* optional parameter to :meth:`pathlib.Path.glob`, + :meth:`pathlib.Path.rglob` and :meth:`pathlib.PurePath.match` for matching + the path's case sensitivity, allowing for more precise control over the matching process. dis --- diff --git a/Lib/pathlib.py b/Lib/pathlib.py index ef7c47c9e77..3d68c161603 100644 --- a/Lib/pathlib.py +++ b/Lib/pathlib.py @@ -86,6 +86,12 @@ def _make_selector(pattern_parts, flavour, case_sensitive): return cls(pat, child_parts, flavour, case_sensitive) +@functools.lru_cache(maxsize=256) +def _compile_pattern(pat, case_sensitive): + flags = re.NOFLAG if case_sensitive else re.IGNORECASE + return re.compile(fnmatch.translate(pat), flags).match + + class _Selector: """A selector matches a specific glob pattern part against the children of a given path.""" @@ -133,8 +139,7 @@ class _WildcardSelector(_Selector): if case_sensitive is None: # TODO: evaluate case-sensitivity of each directory in _select_from() case_sensitive = _is_case_sensitive(flavour) - flags = re.NOFLAG if case_sensitive else re.IGNORECASE - self.match = re.compile(fnmatch.translate(pat), flags=flags).fullmatch + self.match = _compile_pattern(pat, case_sensitive) def _select_from(self, parent_path, scandir): try: @@ -680,22 +685,25 @@ class PurePath(object): name = self._tail[-1].partition('.')[0].partition(':')[0].rstrip(' ') return name.upper() in _WIN_RESERVED_NAMES - def match(self, path_pattern): + def match(self, path_pattern, *, case_sensitive=None): """ Return True if this path matches the given pattern. """ + if case_sensitive is None: + case_sensitive = _is_case_sensitive(self._flavour) pat = self.with_segments(path_pattern) if not pat.parts: raise ValueError("empty pattern") - pat_parts = pat._parts_normcase - parts = self._parts_normcase + pat_parts = pat.parts + parts = self.parts if pat.drive or pat.root: if len(pat_parts) != len(parts): return False elif len(pat_parts) > len(parts): return False for part, pat in zip(reversed(parts), reversed(pat_parts)): - if not fnmatch.fnmatchcase(part, pat): + match = _compile_pattern(pat, case_sensitive) + if not match(part): return False return True diff --git a/Lib/test/test_pathlib.py b/Lib/test/test_pathlib.py index 46a5248499c..ab2c2b232a0 100644 --- a/Lib/test/test_pathlib.py +++ b/Lib/test/test_pathlib.py @@ -312,6 +312,11 @@ class _BasePurePathTest(object): # Multi-part glob-style pattern. self.assertFalse(P('/a/b/c.py').match('/**/*.py')) self.assertTrue(P('/a/b/c.py').match('/a/**/*.py')) + # Case-sensitive flag + self.assertFalse(P('A.py').match('a.PY', case_sensitive=True)) + self.assertTrue(P('A.py').match('a.PY', case_sensitive=False)) + self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True)) + self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False)) def test_ordering_common(self): # Ordering is tuple-alike. @@ -916,7 +921,7 @@ class PureWindowsPathTest(_BasePurePathTest, unittest.TestCase): self.assertEqual(P('//some/share/a/b%#c\xe9').as_uri(), 'file://some/share/a/b%25%23c%C3%A9') - def test_match_common(self): + def test_match(self): P = self.cls # Absolute patterns. self.assertTrue(P('c:/b.py').match('*:/*.py')) diff --git a/Misc/NEWS.d/next/Library/2023-05-17-03-14-07.gh-issue-104484.y6KxL6.rst b/Misc/NEWS.d/next/Library/2023-05-17-03-14-07.gh-issue-104484.y6KxL6.rst new file mode 100644 index 00000000000..6d42078c35d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-05-17-03-14-07.gh-issue-104484.y6KxL6.rst @@ -0,0 +1 @@ +Added *case_sensitive* argument to :meth:`pathlib.PurePath.match`