8000 GH-127381: pathlib ABCs: remove `JoinablePath.match()` (#129147) · python/cpython@a4459c3 · GitHub
[go: up one dir, main page]

Skip to content

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a4459c3

Browse files
authored
GH-127381: pathlib ABCs: remove JoinablePath.match() (#129147)
Unlike `ReadablePath.[r]glob()` and `JoinablePath.full_match()`, the `JoinablePath.match()` method doesn't support the recursive wildcard `**`, and matches from the right when a fully relative pattern is given. These quirks means its probably unsuitable for inclusion in the pathlib ABCs, especially given `full_match()` handles the same use case.
1 parent d23f570 commit a4459c3

File tree

4 files changed

+104
-109
lines changed

4 files changed

+104
-109
lines changed

Lib/pathlib/_abc.py

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -358,33 +358,6 @@ def parents(self):
358358
parent = split(path)[0]
359359
return tuple(parents)
360360

361-
def match(self, path_pattern, *, case_sensitive=None):
362-
"""
363-
Return True if this path matches the given pattern. If the pattern is
364-
relative, matching is done from the right; otherwise, the entire path
365-
is matched. The recursive wildcard '**' is *not* supported by this
366-
method.
367-
"""
368-
if not isinstance(path_pattern, JoinablePath):
369-
path_pattern = self.with_segments(path_pattern)
370-
if case_sensitive is None:
371-
case_sensitive = _is_case_sensitive(self.parser)
372-
sep = path_pattern.parser.sep
373-
path_parts = self.parts[::-1]
374-
pattern_parts = path_pattern.parts[::-1]
375-
if not pattern_parts:
376-
raise ValueError("empty pattern")
377-
if len(path_parts) < len(pattern_parts):
378-
return False
379-
if len(path_parts) > len(pattern_parts) and path_pattern.anchor:
380-
return False
381-
globber = PathGlobber(sep, case_sensitive)
382-
for path_part, pattern_part in zip(path_parts, pattern_parts):
383-
match = globber.compile(pattern_part)
384-
if match(path_part) is None:
385-
return False
386-
return True
387-
388361
def full_match(self, pattern, *, case_sensitive=None):
389362
"""
390363
Return True if this path matches the given glob-style pattern. The

Lib/pathlib/_local.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,32 @@ def full_match(self, pattern, *, case_sensitive=None):
668668
globber = _StringGlobber(self.parser.sep, case_sensitive, recursive=True)
669669
return globber.compile(pattern)(path) is not None
670670

671+
def match(self, path_pattern, *, case_sensitive=None):
672+
"""
673+
Return True if this path matches the given pattern. If the pattern is
674+
relative, matching is done from the right; otherwise, the entire path
675+
is matched. The recursive wildcard '**' is *not* supported by this
676+
method.
677+
"""
678+
if not isinstance(path_pattern, PurePath):
679+
path_pattern = self.with_segments(path_pattern)
680+
if case_sensitive is None:
681+
case_sensitive = self.parser is posixpath
682+
path_parts = self.parts[::-1]
683+
pattern_parts = path_pattern.parts[::-1]
684+
if not pattern_parts:
685+
raise ValueError("empty pattern")
686+
if len(path_parts) < len(pattern_parts):
687+
return False
688+
if len(path_parts) > len(pattern_parts) and path_pattern.anchor:
689+
return False
690+
globber = _StringGlobber(self.parser.sep, case_sensitive)
691+
for path_part, pattern_part in zip(path_parts, pattern_parts):
692+
match = globber.compile(pattern_part)
693+
if match(path_part) is None:
694+
return False
695+
return True
696+
671697
# Subclassing os.PathLike makes isinstance() checks slower,
672698
# which in turn makes Path construction slower. Register instead!
673699
os.PathLike.register(PurePath)

Lib/test/test_pathlib/test_pathlib.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,84 @@ def test_match_empty(self):
438438
self.assertRaises(ValueError, P('a').match, '')
439439
self.assertRaises(ValueError, P('a').match, '.')
440440

441+
def test_match_common(self):
442+
P = self.cls
443+
# Simple relative pattern.
444+
self.assertTrue(P('b.py').match('b.py'))
445+
self.assertTrue(P('a/b.py').match('b.py'))
446+
self.assertTrue(P('/a/b.py').match('b.py'))
447+
self.assertFalse(P('a.py').match('b.py'))
448+
self.assertFalse(P('b/py').match('b.py'))
449+
self.assertFalse(P('/a.py').match('b.py'))
450+
self.assertFalse(P('b.py/c').match('b.py'))
451+
# Wildcard relative pattern.
452+
self.assertTrue(P('b.py').match('*.py'))
453+
self.assertTrue(P('a/b.py').match('*.py'))
454+
self.assertTrue(P('/a/b.py').match('*.py'))
455+
self.assertFalse(P('b.pyc').match('*.py'))
456+
self.assertFalse(P('b./py').match('*.py'))
457+
self.assertFalse(P('b.py/c').match('*.py'))
458+
# Multi-part relative pattern.
459+
self.assertTrue(P('ab/c.py').match('a*/*.py'))
460+
self.assertTrue(P('/d/ab/c.py').match('a*/*.py'))
461+
self.assertFalse(P('a.py').match('a*/*.py'))
462+
self.assertFalse(P('/dab/c.py').match('a*/*.py'))
463+
self.assertFalse(P('ab/c.py/d').match('a*/*.py'))
464+
# Absolute pattern.
465+
self.assertTrue(P('/b.py').match('/*.py'))
466+
self.assertFalse(P('b.py').match('/*.py'))
467+
self.assertFalse(P('a/b.py').match('/*.py'))
468+
self.assertFalse(P('/a/b.py').match('/*.py'))
469+
# Multi-part absolute pattern.
470+
self.assertTrue(P('/a/b.py').match('/a/*.py'))
471+
self.assertFalse(P('/ab.py').match('/a/*.py'))
472+
self.assertFalse(P('/a/b/c.py').match('/a/*.py'))
473+
# Multi-part glob-style pattern.
474+
self.assertFalse(P('/a/b/c.py').match('/**/*.py'))
475+
self.assertTrue(P('/a/b/c.py').match('/a/**/*.py'))
476+
# Case-sensitive flag
477+
self.assertFalse(P('A.py').match('a.PY', case_sensitive=True))
478+
self.assertTrue(P('A.py').match('a.PY', case_sensitive=False))
479+
self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True))
480+
self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False))
481+
# Matching against empty path
482+
self.assertFalse(P('').match('*'))
483+
self.assertFalse(P('').match('**'))
484+
self.assertFalse(P('').match('**/*'))
485+
486+
@needs_posix
487+
def test_match_posix(self):
488+
P = self.cls
489+
self.assertFalse(P('A.py').match('a.PY'))
490+
491+
@needs_windows
492+
def test_match_windows(self):
493+
P = self.cls
494+
# Absolute patterns.
495+
self.assertTrue(P('c:/b.py').match('*:/*.py'))
496+
self.assertTrue(P('c:/b.py').match('c:/*.py'))
497+
self.assertFalse(P('d:/b.py').match('c:/*.py')) # wrong drive
498+
self.assertFalse(P('b.py').match('/*.py'))
499+
self.assertFalse(P('b.py').match('c:*.py'))
500+
self.assertFalse(P('b.py').match('c:/*.py'))
501+
self.assertFalse(P('c:b.py').match('/*.py'))
502+
self.assertFalse(P('c:b.py').match('c:/*.py'))
503+
self.assertFalse(P('/b.py').match('c:*.py'))
504+
self.assertFalse(P('/b.py').match('c:/*.py'))
505+
# UNC patterns.
506+
self.assertTrue(P('//some/share/a.py').match('//*/*/*.py'))
507+
self.assertTrue(P('//some/share/a.py').match('//some/share/*.py'))
508+
self.assertFalse(P('//other/share/a.py').match('//some/share/*.py'))
509+
self.assertFalse(P('//some/share/a/b.py').match('//some/share/*.py'))
510+
# Case-insensitivity.
511+
self.assertTrue(P('B.py').match('b.PY'))
512+
self.assertTrue(P('c:/a/B.Py').match('C:/A/*.pY'))
513+
self.assertTrue(P('//Some/Share/B.Py').match('//somE/sharE/*.pY'))
514+
# Path anchor doesn't match pattern anchor
515+
self.assertFalse(P('c:/b.py').match('/*.py')) # 'c:/' vs '/'
516+
self.assertFalse(P('c:/b.py').match('c:*.py')) # 'c:/' vs 'c:'
517+
self.assertFalse(P('//some/share/a.py').match('/*.py')) # '//some/share/' vs '/'
518+
441519
@needs_posix
442520
def test_parse_path_posix(self):
443521
check = self._check_parse_path

Lib/test/test_pathlib/test_pathlib_abc.py

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -296,88 +296,6 @@ def test_str_windows(self):
296296
p = self.cls('//a/b/c/d')
297297
self.assertEqual(str(p), '\\\\a\\b\\c\\d')
298298

299-
def test_match_empty(self):
300-
P = self.cls
301-
self.assertRaises(ValueError, P('a').match, '')
302-
303-
def test_match_common(self):
304-
P = self.cls
305-
# Simple relative pattern.
306-
self.assertTrue(P('b.py').match('b.py'))
307-
self.assertTrue(P('a/b.py').match('b.py'))
308-
self.assertTrue(P('/a/b.py').match('b.py'))
309-
self.assertFalse(P('a.py').match('b.py'))
310-
self.assertFalse(P('b/py').match('b.py'))
311-
self.assertFalse(P('/a.py').match('b.py'))
312-
self.assertFalse(P('b.py/c').match('b.py'))
313-
# Wildcard relative pattern.
314-
self.assertTrue(P('b.py').match('*.py'))
315-
self.assertTrue(P('a/b.py').match('*.py'))
316-
self.assertTrue(P('/a/b.py').match('*.py'))
317-
self.assertFalse(P('b.pyc').match('*.py'))
318-
self.assertFalse(P('b./py').match('*.py'))
319-
self.assertFalse(P('b.py/c').match('*.py'))
320-
# Multi-part relative pattern.
321-
self.assertTrue(P('ab/c.py').match('a*/*.py'))
322-
self.assertTrue(P('/d/ab/c.py').match('a*/*.py'))
323-
self.assertFalse(P('a.py').match('a*/*.py'))
324-
self.assertFalse(P('/dab/c.py').match('a*/*.py'))
325-
self.assertFalse(P('ab/c.py/d').match('a*/*.py'))
326-
# Absolute pattern.
327-
self.assertTrue(P('/b.py').match('/*.py'))
328-
self.assertFalse(P('b.py').match('/*.py'))
329-
self.assertFalse(P('a/b.py').match('/*.py'))
330-
self.assertFalse(P('/a/b.py').match('/*.py'))
331-
# Multi-part absolute pattern.
332-
self.assertTrue(P('/a/b.py').match('/a/*.py'))
333-
self.assertFalse(P('/ab.py').match('/a/*.py'))
334-
self.assertFalse(P('/a/b/c.py').match('/a/*.py'))
335-
# Multi-part glob-style pattern.
336-
self.assertFalse(P('/a/b/c.py').match('/**/*.py'))
337-
self.assertTrue(P('/a/b/c.py').match('/a/**/*.py'))
338-
# Case-sensitive flag
339-
self.assertFalse(P('A.py').match('a.PY', case_sensitive=True))
340-
self.assertTrue(P('A.py').match('a.PY', case_sensitive=False))
341-
self.assertFalse(P('c:/a/B.Py').match('C:/A/*.pY', case_sensitive=True))
342-
self.assertTrue(P('/a/b/c.py').match('/A/*/*.Py', case_sensitive=False))
343-
# Matching against empty path
344-
self.assertFalse(P('').match('*'))
345-
self.assertFalse(P('').match('**'))
346-
self.assertFalse(P('').match('**/*'))
347-
348-
@needs_posix
349-
def test_match_posix(self):
350-
P = self.cls
351-
self.assertFalse(P('A.py').match('a.PY'))
352-
353-
@needs_windows
354-
def test_match_windows(self):
355-
P = self.cls
356-
# Absolute patterns.
357-
self.assertTrue(P('c:/b.py').match('*:/*.py'))
358-
self.assertTrue(P('c:/b.py').match('c:/*.py'))
359-
self.assertFalse(P('d:/b.py').match('c:/*.py')) # wrong drive
360-
self.assertFalse(P('b.py').match('/*.py'))
361-
self.assertFalse(P('b.py').match('c:*.py'))
362-
self.assertFalse(P('b.py').match('c:/*.py'))
363-
self.assertFalse(P('c:b.py').match('/*.py'))
364-
self.assertFalse(P('c:b.py').match('c:/*.py'))
365-
self.assertFalse(P('/b.py').match('c:*.py'))
366-
self.assertFalse(P('/b.py').match('c:/*.py'))
367-
# UNC patterns.
368-
self.assertTrue(P('//some/share/a.py').match('//*/*/*.py'))
369-
self.assertTrue(P('//some/share/a.py').match('//some/share/*.py'))
370-
self.assertFalse(P('//other/share/a.py').match('//some/share/*.py'))
371-
self.assertFalse(P('//some/share/a/b.py').match('//some/share/*.py'))
372-
# Case-insensitivity.
373-
self.assertTrue(P('B.py').match('b.PY'))
374-
self.assertTrue(P('c:/a/B.Py').match('C:/A/*.pY'))
375-
self.assertTrue(P('//Some/Share/B.Py').match('//somE/sharE/*.pY'))
376-
# Path anchor doesn't match pattern anchor
377-
self.assertFalse(P('c:/b.py').match('/*.py')) # 'c:/' vs '/'
378-
self.assertFalse(P('c:/b.py').match('c:*.py')) # 'c:/' vs 'c:'
379-
self.assertFalse(P('//some/share/a.py').match('/*.py')) # '//some/share/' vs '/'
380-
381299
def test_full_match_common(self):
382300
P = self.cls
383301
# Simple relative pattern.

0 commit comments

Comments
 (0)
0