Skip to content

Commit 7994b41

Browse files
warsawtonghuaroot
andauthored
[3.13] gh-153056: Backport the relevant bits of (#153057) (#153369)
gh-153056: Backport the relevant bits of (#153057) The data race for compiling the string.Template pattern in free-threading builds is not relevant for 3.13, due to the older string.Template and string.py module implementation. However, the secondary bug identified by that isue is still relevant here, and fixed in this branch: As a side effect, a subclass that supplies an already-compiled pattern now works too; previously it raised the same ValueError at class definition. * Document that the pattern attribute accepts a string or a compiled regex * Comment the three states of pattern and note the documented-behavior fix in NEWS * Update Doc/library/string.rst --------- (cherry picked from commit 4572903) Co-authored-by: tonghuaroot (童话) <tonghuaroot@gmail.com>
1 parent 70c8b9f commit 7994b41

4 files changed

Lines changed: 24 additions & 1 deletion

File tree

Doc/library/string.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -943,7 +943,8 @@ attributes:
943943

944944
Alternatively, you can provide the entire regular expression pattern by
945945
overriding the class attribute *pattern*. If you do this, the value must be a
946-
regular expression object with four named capturing groups. The capturing
946+
regular expression pattern string, or a compiled regular expression
947+
object, with four named capturing groups. The capturing
947948
groups correspond to the rules given above, along with the invalid placeholder
948949
rule:
949950

Lib/string.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ def __init_subclass__(cls):
7070
super().__init_subclass__()
7171
if 'pattern' in cls.__dict__:
7272
pattern = cls.pattern
73+
if isinstance(pattern, _re.Pattern):
74+
# An already-compiled pattern (which the documentation allows)
75+
# is used as-is; re.compile() rejects flags on a compiled
76+
# pattern.
77+
return
7378
else:
7479
delim = _re.escape(cls.delimiter)
7580
id = cls.idpattern

Lib/test/test_string.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,20 @@ def test_SafeTemplate(self):
272272
eq(s.safe_substitute(dict(who='tim', what='ham', meal='dinner')),
273273
'tim likes ham for dinner')
274274

275+
def test_precompiled_pattern(self):
276+
# A subclass may supply an already-compiled pattern; it must be reused,
277+
# not recompiled (re.compile() rejects flags on a compiled pattern).
278+
import re
279+
compiled = re.compile(
280+
r'\$(?:(?P<escaped>\$)|(?P<named>[a-z]+)|'
281+
r'\{(?P<braced>[a-z]+)\}|(?P<invalid>))')
282+
class MyTemplate(Template):
283+
pattern = compiled
284+
self.assertIs(MyTemplate.pattern, compiled)
285+
self.assertEqual(
286+
MyTemplate('$who likes $what').substitute(who='tim', what='ham'),
287+
'tim likes ham')
288+
275289
def test_invalid_placeholders(self):
276290
raises = self.assertRaises
277291
s = Template('$who likes $')
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :class:`string.Template` raising a spurious :exc:`ValueError` when the
2+
*pattern* attribute is a compiled regular expression object, which the
3+
documentation allows.

0 commit comments

Comments
 (0)