https://github.com/python/cpython/commit/add0ca9ea00ab02fd3a58d059e8370c2d0a1d32c
commit: add0ca9ea00ab02fd3a58d059e8370c2d0a1d32c
branch: main
author: Serhiy Storchaka <storch...@gmail.com>
committer: serhiy-storchaka <storch...@gmail.com>
date: 2025-05-03T17:58:21+03:00
summary:

gh-133306: Use \z instead of \Z in fnmatch.translate() and glob.translate() 
(GH-133338)

files:
A Misc/NEWS.d/next/Library/2025-05-03-13-19-22.gh-issue-133306.ustKV3.rst
M Doc/library/fnmatch.rst
M Doc/library/glob.rst
M Lib/fnmatch.py
M Lib/glob.py
M Lib/test/test_fnmatch.py
M Lib/test/test_glob.py

diff --git a/Doc/library/fnmatch.rst b/Doc/library/fnmatch.rst
index 8674e855b8e5c7..12e61bc36f5db0 100644
--- a/Doc/library/fnmatch.rst
+++ b/Doc/library/fnmatch.rst
@@ -111,7 +111,7 @@ functions: :func:`fnmatch`, :func:`fnmatchcase`, 
:func:`.filter`.
       >>>
       >>> regex = fnmatch.translate('*.txt')
       >>> regex
-      '(?s:.*\\.txt)\\Z'
+      '(?s:.*\\.txt)\\z'
       >>> reobj = re.compile(regex)
       >>> reobj.match('foobar.txt')
       <re.Match object; span=(0, 10), match='foobar.txt'>
diff --git a/Doc/library/glob.rst b/Doc/library/glob.rst
index 684466d354aef8..59ad1b07f27338 100644
--- a/Doc/library/glob.rst
+++ b/Doc/library/glob.rst
@@ -134,7 +134,7 @@ The :mod:`glob` module defines the following functions:
       >>>
       >>> regex = glob.translate('**/*.txt', recursive=True, 
include_hidden=True)
       >>> regex
-      '(?s:(?:.+/)?[^/]*\\.txt)\\Z'
+      '(?s:(?:.+/)?[^/]*\\.txt)\\z'
       >>> reobj = re.compile(regex)
       >>> reobj.match('foo/bar/baz.txt')
       <re.Match object; span=(0, 15), match='foo/bar/baz.txt'>
diff --git a/Lib/fnmatch.py b/Lib/fnmatch.py
index 1dee8330f5d9d5..10e1c936688d8f 100644
--- a/Lib/fnmatch.py
+++ b/Lib/fnmatch.py
@@ -185,7 +185,7 @@ def _translate(pat, star, question_mark):
 
 def _join_translated_parts(parts, star_indices):
     if not star_indices:
-        return fr'(?s:{"".join(parts)})\Z'
+        return fr'(?s:{"".join(parts)})\z'
     iter_star_indices = iter(star_indices)
     j = next(iter_star_indices)
     buffer = parts[:j]  # fixed pieces at the start
@@ -206,4 +206,4 @@ def _join_translated_parts(parts, star_indices):
     append('.*')
     extend(parts[i:])
     res = ''.join(buffer)
-    return fr'(?s:{res})\Z'
+    return fr'(?s:{res})\z'
diff --git a/Lib/glob.py b/Lib/glob.py
index 8879eff80415aa..341524282ba675 100644
--- a/Lib/glob.py
+++ b/Lib/glob.py
@@ -316,7 +316,7 @@ def translate(pat, *, recursive=False, 
include_hidden=False, seps=None):
             if idx < last_part_idx:
                 results.append(any_sep)
     res = ''.join(results)
-    return fr'(?s:{res})\Z'
+    return fr'(?s:{res})\z'
 
 
 @functools.lru_cache(maxsize=512)
diff --git a/Lib/test/test_fnmatch.py b/Lib/test/test_fnmatch.py
index d4163cfe782ce0..5daaf3b3fddb9e 100644
--- a/Lib/test/test_fnmatch.py
+++ b/Lib/test/test_fnmatch.py
@@ -218,24 +218,24 @@ class TranslateTestCase(unittest.TestCase):
 
     def test_translate(self):
         import re
-        self.assertEqual(translate('*'), r'(?s:.*)\Z')
-        self.assertEqual(translate('?'), r'(?s:.)\Z')
-        self.assertEqual(translate('a?b*'), r'(?s:a.b.*)\Z')
-        self.assertEqual(translate('[abc]'), r'(?s:[abc])\Z')
-        self.assertEqual(translate('[]]'), r'(?s:[]])\Z')
-        self.assertEqual(translate('[!x]'), r'(?s:[^x])\Z')
-        self.assertEqual(translate('[^x]'), r'(?s:[\^x])\Z')
-        self.assertEqual(translate('[x'), r'(?s:\[x)\Z')
+        self.assertEqual(translate('*'), r'(?s:.*)\z')
+        self.assertEqual(translate('?'), r'(?s:.)\z')
+        self.assertEqual(translate('a?b*'), r'(?s:a.b.*)\z')
+        self.assertEqual(translate('[abc]'), r'(?s:[abc])\z')
+        self.assertEqual(translate('[]]'), r'(?s:[]])\z')
+        self.assertEqual(translate('[!x]'), r'(?s:[^x])\z')
+        self.assertEqual(translate('[^x]'), r'(?s:[\^x])\z')
+        self.assertEqual(translate('[x'), r'(?s:\[x)\z')
         # from the docs
-        self.assertEqual(translate('*.txt'), r'(?s:.*\.txt)\Z')
+        self.assertEqual(translate('*.txt'), r'(?s:.*\.txt)\z')
         # squash consecutive stars
-        self.assertEqual(translate('*********'), r'(?s:.*)\Z')
-        self.assertEqual(translate('A*********'), r'(?s:A.*)\Z')
-        self.assertEqual(translate('*********A'), r'(?s:.*A)\Z')
-        self.assertEqual(translate('A*********?[?]?'), r'(?s:A.*.[?].)\Z')
+        self.assertEqual(translate('*********'), r'(?s:.*)\z')
+        self.assertEqual(translate('A*********'), r'(?s:A.*)\z')
+        self.assertEqual(translate('*********A'), r'(?s:.*A)\z')
+        self.assertEqual(translate('A*********?[?]?'), r'(?s:A.*.[?].)\z')
         # fancy translation to prevent exponential-time match failure
         t = translate('**a*a****a')
-        self.assertEqual(t, r'(?s:(?>.*?a)(?>.*?a).*a)\Z')
+        self.assertEqual(t, r'(?s:(?>.*?a)(?>.*?a).*a)\z')
         # and try pasting multiple translate results - it's an undocumented
         # feature that this works
         r1 = translate('**a**a**a*')
@@ -249,27 +249,27 @@ def test_translate(self):
 
     def test_translate_wildcards(self):
         for pattern, expect in [
-            ('ab*', r'(?s:ab.*)\Z'),
-            ('ab*cd', r'(?s:ab.*cd)\Z'),
-            ('ab*cd*', r'(?s:ab(?>.*?cd).*)\Z'),
-            ('ab*cd*12', r'(?s:ab(?>.*?cd).*12)\Z'),
-            ('ab*cd*12*', r'(?s:ab(?>.*?cd)(?>.*?12).*)\Z'),
-            ('ab*cd*12*34', r'(?s:ab(?>.*?cd)(?>.*?12).*34)\Z'),
-            ('ab*cd*12*34*', r'(?s:ab(?>.*?cd)(?>.*?12)(?>.*?34).*)\Z'),
+            ('ab*', r'(?s:ab.*)\z'),
+            ('ab*cd', r'(?s:ab.*cd)\z'),
+            ('ab*cd*', r'(?s:ab(?>.*?cd).*)\z'),
+            ('ab*cd*12', r'(?s:ab(?>.*?cd).*12)\z'),
+            ('ab*cd*12*', r'(?s:ab(?>.*?cd)(?>.*?12).*)\z'),
+            ('ab*cd*12*34', r'(?s:ab(?>.*?cd)(?>.*?12).*34)\z'),
+            ('ab*cd*12*34*', r'(?s:ab(?>.*?cd)(?>.*?12)(?>.*?34).*)\z'),
         ]:
             with self.subTest(pattern):
                 translated = translate(pattern)
                 self.assertEqual(translated, expect, pattern)
 
         for pattern, expect in [
-            ('*ab', r'(?s:.*ab)\Z'),
-            ('*ab*', r'(?s:(?>.*?ab).*)\Z'),
-            ('*ab*cd', r'(?s:(?>.*?ab).*cd)\Z'),
-            ('*ab*cd*', r'(?s:(?>.*?ab)(?>.*?cd).*)\Z'),
-            ('*ab*cd*12', r'(?s:(?>.*?ab)(?>.*?cd).*12)\Z'),
-            ('*ab*cd*12*', r'(?s:(?>.*?ab)(?>.*?cd)(?>.*?12).*)\Z'),
-            ('*ab*cd*12*34', r'(?s:(?>.*?ab)(?>.*?cd)(?>.*?12).*34)\Z'),
-            ('*ab*cd*12*34*', 
r'(?s:(?>.*?ab)(?>.*?cd)(?>.*?12)(?>.*?34).*)\Z'),
+            ('*ab', r'(?s:.*ab)\z'),
+            ('*ab*', r'(?s:(?>.*?ab).*)\z'),
+            ('*ab*cd', r'(?s:(?>.*?ab).*cd)\z'),
+            ('*ab*cd*', r'(?s:(?>.*?ab)(?>.*?cd).*)\z'),
+            ('*ab*cd*12', r'(?s:(?>.*?ab)(?>.*?cd).*12)\z'),
+            ('*ab*cd*12*', r'(?s:(?>.*?ab)(?>.*?cd)(?>.*?12).*)\z'),
+            ('*ab*cd*12*34', r'(?s:(?>.*?ab)(?>.*?cd)(?>.*?12).*34)\z'),
+            ('*ab*cd*12*34*', 
r'(?s:(?>.*?ab)(?>.*?cd)(?>.*?12)(?>.*?34).*)\z'),
         ]:
             with self.subTest(pattern):
                 translated = translate(pattern)
@@ -277,28 +277,28 @@ def test_translate_wildcards(self):
 
     def test_translate_expressions(self):
         for pattern, expect in [
-            ('[', r'(?s:\[)\Z'),
-            ('[!', r'(?s:\[!)\Z'),
-            ('[]', r'(?s:\[\])\Z'),
-            ('[abc', r'(?s:\[abc)\Z'),
-            ('[!abc', r'(?s:\[!abc)\Z'),
-            ('[abc]', r'(?s:[abc])\Z'),
-            ('[!abc]', r'(?s:[^abc])\Z'),
-            ('[!abc][!def]', r'(?s:[^abc][^def])\Z'),
+            ('[', r'(?s:\[)\z'),
+            ('[!', r'(?s:\[!)\z'),
+            ('[]', r'(?s:\[\])\z'),
+            ('[abc', r'(?s:\[abc)\z'),
+            ('[!abc', r'(?s:\[!abc)\z'),
+            ('[abc]', r'(?s:[abc])\z'),
+            ('[!abc]', r'(?s:[^abc])\z'),
+            ('[!abc][!def]', r'(?s:[^abc][^def])\z'),
             # with [[
-            ('[[', r'(?s:\[\[)\Z'),
-            ('[[a', r'(?s:\[\[a)\Z'),
-            ('[[]', r'(?s:[\[])\Z'),
-            ('[[]a', r'(?s:[\[]a)\Z'),
-            ('[[]]', r'(?s:[\[]\])\Z'),
-            ('[[]a]', r'(?s:[\[]a\])\Z'),
-            ('[[a]', r'(?s:[\[a])\Z'),
-            ('[[a]]', r'(?s:[\[a]\])\Z'),
-            ('[[a]b', r'(?s:[\[a]b)\Z'),
+            ('[[', r'(?s:\[\[)\z'),
+            ('[[a', r'(?s:\[\[a)\z'),
+            ('[[]', r'(?s:[\[])\z'),
+            ('[[]a', r'(?s:[\[]a)\z'),
+            ('[[]]', r'(?s:[\[]\])\z'),
+            ('[[]a]', r'(?s:[\[]a\])\z'),
+            ('[[a]', r'(?s:[\[a])\z'),
+            ('[[a]]', r'(?s:[\[a]\])\z'),
+            ('[[a]b', r'(?s:[\[a]b)\z'),
             # backslashes
-            ('[\\', r'(?s:\[\\)\Z'),
-            (r'[\]', r'(?s:[\\])\Z'),
-            (r'[\\]', r'(?s:[\\\\])\Z'),
+            ('[\\', r'(?s:\[\\)\z'),
+            (r'[\]', r'(?s:[\\])\z'),
+            (r'[\\]', r'(?s:[\\\\])\z'),
         ]:
             with self.subTest(pattern):
                 translated = translate(pattern)
diff --git a/Lib/test/test_glob.py b/Lib/test/test_glob.py
index 6e5fc2939c6f2c..d0ed5129253cdf 100644
--- a/Lib/test/test_glob.py
+++ b/Lib/test/test_glob.py
@@ -459,59 +459,59 @@ def test_translate_matching(self):
     def test_translate(self):
         def fn(pat):
             return glob.translate(pat, seps='/')
-        self.assertEqual(fn('foo'), r'(?s:foo)\Z')
-        self.assertEqual(fn('foo/bar'), r'(?s:foo/bar)\Z')
-        self.assertEqual(fn('*'), r'(?s:[^/.][^/]*)\Z')
-        self.assertEqual(fn('?'), r'(?s:(?!\.)[^/])\Z')
-        self.assertEqual(fn('a*'), r'(?s:a[^/]*)\Z')
-        self.assertEqual(fn('*a'), r'(?s:(?!\.)[^/]*a)\Z')
-        self.assertEqual(fn('.*'), r'(?s:\.[^/]*)\Z')
-        self.assertEqual(fn('?aa'), r'(?s:(?!\.)[^/]aa)\Z')
-        self.assertEqual(fn('aa?'), r'(?s:aa[^/])\Z')
-        self.assertEqual(fn('aa[ab]'), r'(?s:aa[ab])\Z')
-        self.assertEqual(fn('**'), r'(?s:(?!\.)[^/]*)\Z')
-        self.assertEqual(fn('***'), r'(?s:(?!\.)[^/]*)\Z')
-        self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z')
-        self.assertEqual(fn('**b'), r'(?s:(?!\.)[^/]*b)\Z')
+        self.assertEqual(fn('foo'), r'(?s:foo)\z')
+        self.assertEqual(fn('foo/bar'), r'(?s:foo/bar)\z')
+        self.assertEqual(fn('*'), r'(?s:[^/.][^/]*)\z')
+        self.assertEqual(fn('?'), r'(?s:(?!\.)[^/])\z')
+        self.assertEqual(fn('a*'), r'(?s:a[^/]*)\z')
+        self.assertEqual(fn('*a'), r'(?s:(?!\.)[^/]*a)\z')
+        self.assertEqual(fn('.*'), r'(?s:\.[^/]*)\z')
+        self.assertEqual(fn('?aa'), r'(?s:(?!\.)[^/]aa)\z')
+        self.assertEqual(fn('aa?'), r'(?s:aa[^/])\z')
+        self.assertEqual(fn('aa[ab]'), r'(?s:aa[ab])\z')
+        self.assertEqual(fn('**'), r'(?s:(?!\.)[^/]*)\z')
+        self.assertEqual(fn('***'), r'(?s:(?!\.)[^/]*)\z')
+        self.assertEqual(fn('a**'), r'(?s:a[^/]*)\z')
+        self.assertEqual(fn('**b'), r'(?s:(?!\.)[^/]*b)\z')
         self.assertEqual(fn('/**/*/*.*/**'),
-                         
r'(?s:/(?!\.)[^/]*/[^/.][^/]*/(?!\.)[^/]*\.[^/]*/(?!\.)[^/]*)\Z')
+                         
r'(?s:/(?!\.)[^/]*/[^/.][^/]*/(?!\.)[^/]*\.[^/]*/(?!\.)[^/]*)\z')
 
     def test_translate_include_hidden(self):
         def fn(pat):
             return glob.translate(pat, include_hidden=True, seps='/')
-        self.assertEqual(fn('foo'), r'(?s:foo)\Z')
-        self.assertEqual(fn('foo/bar'), r'(?s:foo/bar)\Z')
-        self.assertEqual(fn('*'), r'(?s:[^/]+)\Z')
-        self.assertEqual(fn('?'), r'(?s:[^/])\Z')
-        self.assertEqual(fn('a*'), r'(?s:a[^/]*)\Z')
-        self.assertEqual(fn('*a'), r'(?s:[^/]*a)\Z')
-        self.assertEqual(fn('.*'), r'(?s:\.[^/]*)\Z')
-        self.assertEqual(fn('?aa'), r'(?s:[^/]aa)\Z')
-        self.assertEqual(fn('aa?'), r'(?s:aa[^/])\Z')
-        self.assertEqual(fn('aa[ab]'), r'(?s:aa[ab])\Z')
-        self.assertEqual(fn('**'), r'(?s:[^/]*)\Z')
-        self.assertEqual(fn('***'), r'(?s:[^/]*)\Z')
-        self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z')
-        self.assertEqual(fn('**b'), r'(?s:[^/]*b)\Z')
-        self.assertEqual(fn('/**/*/*.*/**'), 
r'(?s:/[^/]*/[^/]+/[^/]*\.[^/]*/[^/]*)\Z')
+        self.assertEqual(fn('foo'), r'(?s:foo)\z')
+        self.assertEqual(fn('foo/bar'), r'(?s:foo/bar)\z')
+        self.assertEqual(fn('*'), r'(?s:[^/]+)\z')
+        self.assertEqual(fn('?'), r'(?s:[^/])\z')
+        self.assertEqual(fn('a*'), r'(?s:a[^/]*)\z')
+        self.assertEqual(fn('*a'), r'(?s:[^/]*a)\z')
+        self.assertEqual(fn('.*'), r'(?s:\.[^/]*)\z')
+        self.assertEqual(fn('?aa'), r'(?s:[^/]aa)\z')
+        self.assertEqual(fn('aa?'), r'(?s:aa[^/])\z')
+        self.assertEqual(fn('aa[ab]'), r'(?s:aa[ab])\z')
+        self.assertEqual(fn('**'), r'(?s:[^/]*)\z')
+        self.assertEqual(fn('***'), r'(?s:[^/]*)\z')
+        self.assertEqual(fn('a**'), r'(?s:a[^/]*)\z')
+        self.assertEqual(fn('**b'), r'(?s:[^/]*b)\z')
+        self.assertEqual(fn('/**/*/*.*/**'), 
r'(?s:/[^/]*/[^/]+/[^/]*\.[^/]*/[^/]*)\z')
 
     def test_translate_recursive(self):
         def fn(pat):
             return glob.translate(pat, recursive=True, include_hidden=True, 
seps='/')
-        self.assertEqual(fn('*'), r'(?s:[^/]+)\Z')
-        self.assertEqual(fn('?'), r'(?s:[^/])\Z')
-        self.assertEqual(fn('**'), r'(?s:.*)\Z')
-        self.assertEqual(fn('**/**'), r'(?s:.*)\Z')
-        self.assertEqual(fn('***'), r'(?s:[^/]*)\Z')
-        self.assertEqual(fn('a**'), r'(?s:a[^/]*)\Z')
-        self.assertEqual(fn('**b'), r'(?s:[^/]*b)\Z')
-        self.assertEqual(fn('/**/*/*.*/**'), 
r'(?s:/(?:.+/)?[^/]+/[^/]*\.[^/]*/.*)\Z')
+        self.assertEqual(fn('*'), r'(?s:[^/]+)\z')
+        self.assertEqual(fn('?'), r'(?s:[^/])\z')
+        self.assertEqual(fn('**'), r'(?s:.*)\z')
+        self.assertEqual(fn('**/**'), r'(?s:.*)\z')
+        self.assertEqual(fn('***'), r'(?s:[^/]*)\z')
+        self.assertEqual(fn('a**'), r'(?s:a[^/]*)\z')
+        self.assertEqual(fn('**b'), r'(?s:[^/]*b)\z')
+        self.assertEqual(fn('/**/*/*.*/**'), 
r'(?s:/(?:.+/)?[^/]+/[^/]*\.[^/]*/.*)\z')
 
     def test_translate_seps(self):
         def fn(pat):
             return glob.translate(pat, recursive=True, include_hidden=True, 
seps=['/', '\\'])
-        self.assertEqual(fn('foo/bar\\baz'), r'(?s:foo[/\\]bar[/\\]baz)\Z')
-        self.assertEqual(fn('**/*'), r'(?s:(?:.+[/\\])?[^/\\]+)\Z')
+        self.assertEqual(fn('foo/bar\\baz'), r'(?s:foo[/\\]bar[/\\]baz)\z')
+        self.assertEqual(fn('**/*'), r'(?s:(?:.+[/\\])?[^/\\]+)\z')
 
 
 if __name__ == "__main__":
diff --git 
a/Misc/NEWS.d/next/Library/2025-05-03-13-19-22.gh-issue-133306.ustKV3.rst 
b/Misc/NEWS.d/next/Library/2025-05-03-13-19-22.gh-issue-133306.ustKV3.rst
new file mode 100644
index 00000000000000..5ec4860f553fc5
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-05-03-13-19-22.gh-issue-133306.ustKV3.rst
@@ -0,0 +1,2 @@
+Use ``\z`` instead of ``\Z`` in :func:`fnmatch.translate` and
+:func:`glob.translate`.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to