Skip to content

Commit 26a218c

Browse files
committed
reactivate Template-Strings
1 parent f0e2a06 commit 26a218c

File tree

2 files changed

+116
-10
lines changed

2 files changed

+116
-10
lines changed

src/RestrictedPython/transformer.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -577,9 +577,10 @@ def visit_FormattedValue(self, node: ast.FormattedValue) -> ast.AST:
577577

578578
def visit_TemplateStr(self, node: ast.AST) -> ast.AST:
579579
"""Template strings are not allowed by default.
580-
Even so, that template strings can be useful in context of Template Engines
581-
A Template String itself is not executed itself, but it contain expressions
582-
and need additional template rendering logic applied to it to be useful.
580+
Even so, that template strings can be useful in context of Template
581+
Engines. A Template String itself is not executed itself, but it
582+
contain expressions and need additional template rendering logic
583+
applied to it to be useful.
583584
Those rendering logic would be affected by RestrictedPython as well.
584585
585586
TODO: Deeper review of security implications of template strings.
@@ -588,21 +589,23 @@ def visit_TemplateStr(self, node: ast.AST) -> ast.AST:
588589
"""
589590
self.warn(
590591
node,
591-
'TemplateStr statements are not yet allowed, please use f-strings or a real template engine instead.')
592-
self.not_allowed(node)
593-
# return self.node_contents_visit(node)
592+
'TemplateStr statements are not yet allowed, '
593+
'please use f-strings or a real template engine instead.')
594+
# self.not_allowed(node)
595+
return self.node_contents_visit(node)
594596

595597
def visit_Interpolation(self, node: ast.AST) -> ast.AST:
596598
"""Interpolations are not allowed by default.
597-
As Interpolations are part of Template Strings, they will not be reached in
598-
context of RestrictedPython as Template Strings are not allowed.
599+
As Interpolations are part of Template Strings, they will not be
600+
reached in the context of RestrictedPython as Template Strings
601+
‚‚are not allowed.
599602
600603
TODO: Deeper review of security implications of interpolated strings.
601604
TODO: Change Type Annotation to ast.Interpolation when
602605
Support for Python 3.13 is dropped.
603606
"""
604-
self.not_allowed(node)
605-
# return self.node_contents_visit(node)
607+
# self.not_allowed(node)
608+
return self.node_contents_visit(node)
606609

607610
def visit_JoinedStr(self, node: ast.JoinedStr) -> ast.AST:
608611
"""Allow joined string without restrictions."""

tests/transformer/test_tstring.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
from string.templatelib import Template
2+
3+
import pytest
4+
5+
from RestrictedPython import compile_restricted_exec
6+
from RestrictedPython._compat import IS_PY314_OR_GREATER
7+
from RestrictedPython.Eval import default_guarded_getattr
8+
from RestrictedPython.Eval import default_guarded_getiter
9+
from RestrictedPython.PrintCollector import PrintCollector
10+
11+
12+
@pytest.mark.skipif(
13+
not IS_PY314_OR_GREATER,
14+
reason="t-strings were added in Python 3.14.",
15+
)
16+
def test_transform():
17+
"""It compiles a function call successfully and returns the used name."""
18+
19+
result = compile_restricted_exec('a = t"{max([1, 2, 3])}"')
20+
assert result.errors == ()
21+
assert result.warnings == [
22+
'Line 1: TemplateStr statements are not yet allowed, please use f-strings or a real template engine instead.'] # NOQA: E501
23+
assert result.code is not None
24+
loc = {}
25+
exec(result.code, {}, loc)
26+
template = loc['a']
27+
assert isinstance(template, Template)
28+
assert template.values == (3, )
29+
assert result.used_names == {'max': True}
30+
31+
32+
@pytest.mark.skipif(
33+
not IS_PY314_OR_GREATER,
34+
reason="t-strings were added in Python 3.14.",
35+
)
36+
def test_visit_invalid_variable_name():
37+
"""Accessing private attributes is forbidden.
38+
39+
This is just a smoke test to validate that restricted exec is used
40+
in the run-time evaluation of t-strings.
41+
"""
42+
result = compile_restricted_exec('t"{__init__}"')
43+
assert result.errors == (
44+
'Line 1: "__init__" is an invalid variable name because it starts with "_"', # NOQA: E501
45+
)
46+
47+
48+
t_string_self_documenting_expressions_example = """
49+
from datetime import date
50+
from string.templatelib import Template, Interpolation
51+
52+
user = 'eric_idle'
53+
member_since = date(1975, 7, 31)
54+
55+
def render_template(template: Template) -> str:
56+
result = ''
57+
for part in template:
58+
if isinstance(part, Interpolation):
59+
if isinstance(part.value, str):
60+
result += part.value.upper()
61+
else:
62+
result += str(part.value)
63+
else:
64+
result += part.lower()
65+
return result
66+
67+
print(render_template(t'The User {user} is a member since {member_since}'))
68+
"""
69+
70+
71+
@pytest.mark.skipif(
72+
not IS_PY314_OR_GREATER,
73+
reason="t-strings were added in Python 3.14.",
74+
)
75+
def test_t_string_self_documenting_expressions():
76+
"""Checks if t-string self-documenting expressions is checked."""
77+
result = compile_restricted_exec(
78+
t_string_self_documenting_expressions_example,
79+
)
80+
# assert result.errors == (
81+
# 'Line 13: TemplateStr statements are not allowed.',
82+
# )
83+
# assert result.warnings == [
84+
# 'Line 13: TemplateStr statements are not yet allowed, please use '
85+
# 'f-strings or a real template engine instead.',
86+
# "Line None: Prints, but never reads 'printed' variable."
87+
# ]
88+
# assert result.code is None
89+
assert result.errors == ()
90+
assert result.warnings == [
91+
'Line 20: TemplateStr statements are not yet allowed, '
92+
'please use f-strings or a real template engine instead.',
93+
"Line None: Prints, but never reads 'printed' variable."]
94+
assert result.code is not None
95+
96+
glb = {
97+
'_print_': PrintCollector,
98+
'_getattr_': default_guarded_getattr,
99+
'_getiter_': default_guarded_getiter,
100+
'_inplacevar_': lambda x: x,
101+
}
102+
exec(result.code, glb)
103+
assert glb['_print']() == "user='eric_idle' member_since=datetime.date(1975, 7, 31)\n" # NOQA: E501

0 commit comments

Comments
 (0)