From 4dd49c1fbce61bd2439c3b1adafa7e1fe930cccf Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 11:41:43 +0200 Subject: [PATCH 1/9] - transferring Python 3.14-related changes from the typing branch --- CHANGES.rst | 2 +- docs/contributing/ast/python3_14.ast | 196 +++++++++++++++++++++ docs/contributing/changes_from313to314.rst | 5 + docs/contributing/index.rst | 6 + setup.py | 1 + src/RestrictedPython/_compat.py | 2 + src/RestrictedPython/transformer.py | 27 +++ tests/transformer/test_tstring.py | 105 +++++++++++ 8 files changed, 343 insertions(+), 1 deletion(-) create mode 100644 docs/contributing/ast/python3_14.ast create mode 100644 docs/contributing/changes_from313to314.rst create mode 100644 tests/transformer/test_tstring.py diff --git a/CHANGES.rst b/CHANGES.rst index 8823a78..a603aac 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Changes 8.1 (unreleased) ---------------- -- Nothing changed yet. +- Allow to use the package with Python 3.14. 8.1a1.dev0 (2025-03-20) diff --git a/docs/contributing/ast/python3_14.ast b/docs/contributing/ast/python3_14.ast new file mode 100644 index 0000000..2c1e0b7 --- /dev/null +++ b/docs/contributing/ast/python3_14.ast @@ -0,0 +1,196 @@ +-- Python 3.14 AST +-- ASDL's 4 builtin types are: +-- identifier, int, string, constant + +module Python version "3.14" +{ + mod = Module(stmt* body, type_ignore* type_ignores) + | Interactive(stmt* body) + | Expression(expr body) + | FunctionType(expr* argtypes, expr returns) + + stmt = FunctionDef(identifier name, + arguments args, + stmt* body, + expr* decorator_list, + expr? returns, + string? type_comment, + type_param* type_params) + | AsyncFunctionDef(identifier name, + arguments args, + stmt* body, + expr* decorator_list, + expr? returns, + string? type_comment, + type_param* type_params) + + | ClassDef(identifier name, + expr* bases, + keyword* keywords, + stmt* body, + expr* decorator_list, + type_param* type_params) + | Return(expr? value) + + | Delete(expr* targets) + | Assign(expr* targets, expr value, string? type_comment) + | TypeAlias(expr name, type_param* type_params, expr value) + | AugAssign(expr target, operator op, expr value) + -- 'simple' indicates that we annotate simple name without parens + | AnnAssign(expr target, expr annotation, expr? value, int simple) + + -- use 'orelse' because else is a keyword in target languages + | For(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment) + | AsyncFor(expr target, expr iter, stmt* body, stmt* orelse, string? type_comment) + | While(expr test, stmt* body, stmt* orelse) + | If(expr test, stmt* body, stmt* orelse) + | With(withitem* items, stmt* body, string? type_comment) + | AsyncWith(withitem* items, stmt* body, string? type_comment) + + | Match(expr subject, match_case* cases) + + | Raise(expr? exc, expr? cause) + | Try(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody) + | TryStar(stmt* body, excepthandler* handlers, stmt* orelse, stmt* finalbody) + | Assert(expr test, expr? msg) + + | Import(alias* names) + | ImportFrom(identifier? module, alias* names, int? level) + + | Global(identifier* names) + | Nonlocal(identifier* names) + | Expr(expr value) + | Pass + | Break + | Continue + + -- col_offset is the byte offset in the utf8 string the parser uses + attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset) + + -- BoolOp() can use left & right? + expr = BoolOp(boolop op, expr* values) + | NamedExpr(expr target, expr value) + | BinOp(expr left, operator op, expr right) + | UnaryOp(unaryop op, expr operand) + | Lambda(arguments args, expr body) + | IfExp(expr test, expr body, expr orelse) + | Dict(expr?* keys, expr* values) + | Set(expr* elts) + | ListComp(expr elt, comprehension* generators) + | SetComp(expr elt, comprehension* generators) + | DictComp(expr key, expr value, comprehension* generators) + | GeneratorExp(expr elt, comprehension* generators) + -- the grammar constrains where yield expressions can occur + | Await(expr value) + | Yield(expr? value) + | YieldFrom(expr value) + -- need sequences for compare to distinguish between + -- x < 4 < 3 and (x < 4) < 3 + | Compare(expr left, cmpop* ops, expr* comparators) + | Call(expr func, expr* args, keyword* keywords) + | FormattedValue(expr value, int conversion, expr? format_spec) + | Interpolation(expr value, constant str, int conversion, expr? format_spec) + | JoinedStr(expr* values) + | TemplateStr(expr* values) + | Constant(constant value, string? kind) + + -- the following expression can appear in assignment context + | Attribute(expr value, identifier attr, expr_context ctx) + | Subscript(expr value, expr slice, expr_context ctx) + | Starred(expr value, expr_context ctx) + | Name(identifier id, expr_context ctx) + | List(expr* elts, expr_context ctx) + | Tuple(expr* elts, expr_context ctx) + + -- can appear only in Subscript + | Slice(expr? lower, expr? upper, expr? step) + + -- col_offset is the byte offset in the utf8 string the parser uses + attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset) + + expr_context = Load + | Store + | Del + + boolop = And + | Or + + operator = Add + | Sub + | Mult + | MatMult + | Div + | Mod + | Pow + | LShift + | RShift + | BitOr + | BitXor + | BitAnd + | FloorDiv + + unaryop = Invert + | Not + | UAdd + | USub + + cmpop = Eq + | NotEq + | Lt + | LtE + | Gt + | GtE + | Is + | IsNot + | In + | NotIn + + comprehension = (expr target, expr iter, expr* ifs, int is_async) + + excepthandler = ExceptHandler(expr? type, identifier? name, stmt* body) + attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset) + + arguments = (arg* posonlyargs, + arg* args, + arg? vararg, + arg* kwonlyargs, + expr* kw_defaults, + arg? kwarg, + expr* defaults) + + arg = (identifier arg, expr? annotation, string? type_comment) + attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset) + + -- keyword arguments supplied to call (NULL identifier for **kwargs) + keyword = (identifier? arg, expr value) + attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset) + + -- import name with optional 'as' alias. + alias = (identifier name, identifier? asname) + attributes (int lineno, int col_offset, int? end_lineno, int? end_col_offset) + + withitem = (expr context_expr, expr? optional_vars) + + match_case = (pattern pattern, expr? guard, stmt* body) + + pattern = MatchValue(expr value) + | MatchSingleton(constant value) + | MatchSequence(pattern* patterns) + | MatchMapping(expr* keys, pattern* patterns, identifier? rest) + | MatchClass(expr cls, pattern* patterns, identifier* kwd_attrs, pattern* kwd_patterns) + + | MatchStar(identifier? name) + -- The optional "rest" MatchMapping parameter handles capturing extra mapping keys + + | MatchAs(pattern? pattern, identifier? name) + | MatchOr(pattern* patterns) + + attributes (int lineno, int col_offset, int end_lineno, int end_col_offset) + + type_ignore = TypeIgnore(int lineno, string tag) + + type_param = TypeVar(identifier name, expr? bound, expr? default_value) + | ParamSpec(identifier name, expr? default_value) + | TypeVarTuple(identifier name, expr? default_value) + attributes (int lineno, int col_offset, int end_lineno, int end_col_offset) +} diff --git a/docs/contributing/changes_from313to314.rst b/docs/contributing/changes_from313to314.rst new file mode 100644 index 0000000..2690a88 --- /dev/null +++ b/docs/contributing/changes_from313to314.rst @@ -0,0 +1,5 @@ +Changes from Python 3.13 to Python 3.14 +--------------------------------------- + +.. literalinclude:: ast/python3_14.ast + :diff: ast/python3_13.ast diff --git a/docs/contributing/index.rst b/docs/contributing/index.rst index 23e93b2..69909e4 100644 --- a/docs/contributing/index.rst +++ b/docs/contributing/index.rst @@ -103,6 +103,7 @@ A (modified style) Copy of all Abstract Grammar Definitions for the Python versi changes_from310to311 changes_from311to312 changes_from312to313 + changes_from313to314 .. _understand: @@ -235,6 +236,7 @@ Technical Backgrounds - Links to External Documentation * AST Grammar of Python (`Status of Python Versions`_) + * `Python 3.14 AST`_ (EOL 2030-10) * `Python 3.13 AST`_ (EOL 2029-10) * `Python 3.12 AST`_ (EOL 2028-10) * `Python 3.11 AST`_ (EOL 2027-10) @@ -257,6 +259,8 @@ Todos .. _`What's new in Python`: https://docs.python.org/3/whatsnew/ +.. _`What's new in Python 3.14`: https://docs.python.org/3.14/whatsnew/3.14.html + .. _`What's new in Python 3.13`: https://docs.python.org/3.13/whatsnew/3.13.html .. _`What's new in Python 3.12`: https://docs.python.org/3.12/whatsnew/3.12.html @@ -281,6 +285,8 @@ Todos .. _`Python 3 AST`: https://docs.python.org/3/library/ast.html#abstract-grammar +.. _`Python 3.14 AST`: https://docs.python.org/3.14/library/ast.html#abstract-grammar + .. _`Python 3.13 AST`: https://docs.python.org/3.13/library/ast.html#abstract-grammar .. _`Python 3.12 AST`: https://docs.python.org/3.12/library/ast.html#abstract-grammar diff --git a/setup.py b/setup.py index 45bf79d..3ffa9d7 100644 --- a/setup.py +++ b/setup.py @@ -44,6 +44,7 @@ def read(*rnames): 'Programming Language :: Python :: 3.11', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', + 'Programming Language :: Python :: 3.14', 'Programming Language :: Python :: Implementation :: CPython', 'Topic :: Security', ], diff --git a/src/RestrictedPython/_compat.py b/src/RestrictedPython/_compat.py index 2d85cc4..2c5641e 100644 --- a/src/RestrictedPython/_compat.py +++ b/src/RestrictedPython/_compat.py @@ -6,5 +6,7 @@ IS_PY310_OR_GREATER = _version.major == 3 and _version.minor >= 10 IS_PY311_OR_GREATER = _version.major == 3 and _version.minor >= 11 IS_PY312_OR_GREATER = _version.major == 3 and _version.minor >= 12 +IS_PY313_OR_GREATER = _version.major == 3 and _version.minor >= 13 +IS_PY314_OR_GREATER = _version.major == 3 and _version.minor >= 14 IS_CPYTHON = platform.python_implementation() == 'CPython' diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index 7fd3016..fa6a559 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -563,6 +563,33 @@ def visit_FormattedValue(self, node): """Allow f-strings without restrictions.""" return self.node_contents_visit(node) + def visit_TemplateStr(self, node): + """Template strings are allowed by default. + + As Template strings are a very basic template mechanism, that needs + additional rendering logic to be useful, they are not blocked by + default. + Those rendering logic would be affected by RestrictedPython as well. + """ + self.warn( + node, + 'TemplateStr statements are not yet allowed, ' + 'please use f-strings or a real template engine instead.') + # self.not_allowed(node) + return self.node_contents_visit(node) + + def visit_Interpolation(self, node): + """Interpolations are allowed by default. + + As Interpolations are part of Template Strings, they are needed + to be reached in the context of RestrictedPython as Template Strings + are allowed. As a user has to provide additional rendering logic + to make use of Template Strings, the security implications of + Interpolations are limited in the context of RestrictedPython. + """ + # self.not_allowed(node) + return self.node_contents_visit(node) + def visit_JoinedStr(self, node): """Allow joined string without restrictions.""" return self.node_contents_visit(node) diff --git a/tests/transformer/test_tstring.py b/tests/transformer/test_tstring.py new file mode 100644 index 0000000..de0ed35 --- /dev/null +++ b/tests/transformer/test_tstring.py @@ -0,0 +1,105 @@ +import pytest + +from RestrictedPython import compile_restricted_exec +from RestrictedPython._compat import IS_PY314_OR_GREATER +from RestrictedPython.Eval import default_guarded_getattr +from RestrictedPython.Eval import default_guarded_getiter +from RestrictedPython.PrintCollector import PrintCollector + + +if IS_PY314_OR_GREATER: + from string.templatelib import Template + + +@pytest.mark.skipif( + not IS_PY314_OR_GREATER, + reason="t-strings were added in Python 3.14.", +) +def test_transform(): + """It compiles a function call successfully and returns the used name.""" + + result = compile_restricted_exec('a = t"{max([1, 2, 3])}"') + assert result.errors == () + assert result.warnings == [ + 'Line 1: TemplateStr statements are not yet allowed, please use f-strings or a real template engine instead.'] # NOQA: E501 + assert result.code is not None + loc = {} + exec(result.code, {}, loc) + template = loc['a'] + assert isinstance(template, Template) + assert template.values == (3, ) + assert result.used_names == {'max': True} + + +@pytest.mark.skipif( + not IS_PY314_OR_GREATER, + reason="t-strings were added in Python 3.14.", +) +def test_visit_invalid_variable_name(): + """Accessing private attributes is forbidden. + + This is just a smoke test to validate that restricted exec is used + in the run-time evaluation of t-strings. + """ + result = compile_restricted_exec('t"{__init__}"') + assert result.errors == ( + 'Line 1: "__init__" is an invalid variable name because it starts with "_"', # NOQA: E501 + ) + + +t_string_self_documenting_expressions_example = """ +from datetime import date +from string.templatelib import Template, Interpolation + +user = 'eric_idle' +member_since = date(1975, 7, 31) + +def render_template(template: Template) -> str: + result = '' + for part in template: + if isinstance(part, Interpolation): + if isinstance(part.value, str): + result += part.value.upper() + else: + result += str(part.value) + else: + result += part.lower() + return result + +print(render_template(t'The User {user} is a member since {member_since}')) +""" + + +@pytest.mark.skipif( + not IS_PY314_OR_GREATER, + reason="t-strings were added in Python 3.14.", +) +def test_t_string_self_documenting_expressions(): + """Checks if t-string self-documenting expressions is checked.""" + result = compile_restricted_exec( + t_string_self_documenting_expressions_example, + ) + # assert result.errors == ( + # 'Line 13: TemplateStr statements are not allowed.', + # ) + # assert result.warnings == [ + # 'Line 13: TemplateStr statements are not yet allowed, please use ' + # 'f-strings or a real template engine instead.', + # "Line None: Prints, but never reads 'printed' variable." + # ] + # assert result.code is None + assert result.errors == () + assert result.warnings == [ + 'Line 20: TemplateStr statements are not yet allowed, ' + 'please use f-strings or a real template engine instead.', + "Line None: Prints, but never reads 'printed' variable."] + assert result.code is not None + + glb = { + '_print_': PrintCollector, + '_getattr_': default_guarded_getattr, + '_getiter_': default_guarded_getiter, + '_inplacevar_': lambda x, y, z: y + z, + } + exec(result.code, glb) + assert glb['_print']() == "the user ERIC_IDLE is a member since 1975-07-31\n" # NOQA: E501 From 3bf119de16099d3842b8fc501202bd86d73f78fe Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 11:43:10 +0200 Subject: [PATCH 2/9] - apply latest zope.meta templates --- .github/workflows/tests.yml | 2 +- .meta.toml | 2 +- .pre-commit-config.yaml | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8cb5ba8..4f30682 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,7 @@ jobs: with: persist-credentials: false - name: Install uv + caching - uses: astral-sh/setup-uv@v6 + uses: astral-sh/setup-uv@v7 with: enable-cache: true cache-dependency-glob: | diff --git a/.meta.toml b/.meta.toml index c5b68d7..57b51a9 100644 --- a/.meta.toml +++ b/.meta.toml @@ -2,7 +2,7 @@ # https://github.com/zopefoundation/meta/tree/master/config/pure-python [meta] template = "pure-python" -commit-id = "7dcce077" +commit-id = "72252845" [python] with-pypy = false diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d2607c9..cbb2541 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,7 +3,7 @@ minimum_pre_commit_version: '3.6' repos: - repo: https://github.com/pycqa/isort - rev: "6.1.0" + rev: "7.0.0" hooks: - id: isort - repo: https://github.com/hhatto/autopep8 @@ -12,7 +12,7 @@ repos: - id: autopep8 args: [--in-place, --aggressive, --aggressive] - repo: https://github.com/asottile/pyupgrade - rev: v3.20.0 + rev: v3.21.0 hooks: - id: pyupgrade args: [--py39-plus] From 5434160829aeacd501109fef3d9dbba1b75f89f8 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 11:49:16 +0200 Subject: [PATCH 3/9] - fix test-related issues --- .meta.toml | 2 +- docs/conf.py | 1 + pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.meta.toml b/.meta.toml index 57b51a9..04360f6 100644 --- a/.meta.toml +++ b/.meta.toml @@ -55,7 +55,7 @@ coverage-setenv = [ ] [coverage] -fail-under = 97.3 +fail-under = 97.2 [isort] additional-sources = "{toxinidir}/tests" diff --git a/docs/conf.py b/docs/conf.py index eb7dc5e..05f4c16 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -117,6 +117,7 @@ 'python311': ('https://docs.python.org/3.11', None), 'python312': ('https://docs.python.org/3.12', None), 'python313': ('https://docs.python.org/3.13', None), + 'python314': ('https://docs.python.org/3.14', None), } # Options for sphinx.ext.todo: diff --git a/pyproject.toml b/pyproject.toml index 89faa35..7b94ef6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ branch = true source = ["RestrictedPython"] [tool.coverage.report] -fail_under = 97.3 +fail_under = 97.2 precision = 2 ignore_errors = true show_missing = true From c1b43c5c67f951dff3580fad8f3a5218c64374d4 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 11:52:35 +0200 Subject: [PATCH 4/9] - fix version number in Sphinx docs --- docs/conf.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 05f4c16..deebf27 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -60,9 +60,9 @@ # built documents. # # The short X.Y version. -version = '7.5' +version = '8.1' # The full version, including alpha/beta/rc tags. -release = '7.5' +release = '8.1' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From cb7fcef1e4bb8899bcc25a63d20d9ed787178fcd Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 12:07:37 +0200 Subject: [PATCH 5/9] - update transformer.INSPECT_ATTRIBUTES --- src/RestrictedPython/transformer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index fa6a559..e868b2f 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -73,6 +73,7 @@ "f_back", "f_builtins", "f_code", + "f_generator", "f_globals", # "f_lasti", # int # "f_lineno", # int @@ -99,6 +100,7 @@ # on generator objects: "gi_frame", # "gi_running", # bool + # "gi_suspended", # bool "gi_code", "gi_yieldfrom", # on coroutine objects: From c081b10ac6ac31b33a7095def3f48e5e085ca403 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 15:55:20 +0200 Subject: [PATCH 6/9] - use a pinned commit hash for astral/setup-uv action --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4f30682..ace0d07 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,8 @@ jobs: with: persist-credentials: false - name: Install uv + caching - uses: astral-sh/setup-uv@v7 + # astral/setup-uv@7 + uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d with: enable-cache: true cache-dependency-glob: | From 088a8a510a4b725631f96bfdd1264ebcab59e81b Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 15:59:42 +0200 Subject: [PATCH 7/9] - remove outdated code and expand change log entry --- CHANGES.rst | 2 +- src/RestrictedPython/transformer.py | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index a603aac..bf3c7de 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -4,7 +4,7 @@ Changes 8.1 (unreleased) ---------------- -- Allow to use the package with Python 3.14. +- Allow to use the package with Python 3.14 including t-string support. 8.1a1.dev0 (2025-03-20) diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index e868b2f..fa71e48 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -573,11 +573,6 @@ def visit_TemplateStr(self, node): default. Those rendering logic would be affected by RestrictedPython as well. """ - self.warn( - node, - 'TemplateStr statements are not yet allowed, ' - 'please use f-strings or a real template engine instead.') - # self.not_allowed(node) return self.node_contents_visit(node) def visit_Interpolation(self, node): @@ -589,7 +584,6 @@ def visit_Interpolation(self, node): to make use of Template Strings, the security implications of Interpolations are limited in the context of RestrictedPython. """ - # self.not_allowed(node) return self.node_contents_visit(node) def visit_JoinedStr(self, node): From 21cd7ed2f201855e5db6d0a276e470de0a4a67ad Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 16:03:18 +0200 Subject: [PATCH 8/9] - fix test code --- tests/transformer/test_tstring.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/transformer/test_tstring.py b/tests/transformer/test_tstring.py index de0ed35..06ccdd4 100644 --- a/tests/transformer/test_tstring.py +++ b/tests/transformer/test_tstring.py @@ -79,20 +79,7 @@ def test_t_string_self_documenting_expressions(): result = compile_restricted_exec( t_string_self_documenting_expressions_example, ) - # assert result.errors == ( - # 'Line 13: TemplateStr statements are not allowed.', - # ) - # assert result.warnings == [ - # 'Line 13: TemplateStr statements are not yet allowed, please use ' - # 'f-strings or a real template engine instead.', - # "Line None: Prints, but never reads 'printed' variable." - # ] - # assert result.code is None assert result.errors == () - assert result.warnings == [ - 'Line 20: TemplateStr statements are not yet allowed, ' - 'please use f-strings or a real template engine instead.', - "Line None: Prints, but never reads 'printed' variable."] assert result.code is not None glb = { From 5b58b0c8c5a3525e585a1e28860973bc0181dca6 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Sun, 19 Oct 2025 16:05:37 +0200 Subject: [PATCH 9/9] - fix test code --- tests/transformer/test_tstring.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/transformer/test_tstring.py b/tests/transformer/test_tstring.py index 06ccdd4..3a2bf2d 100644 --- a/tests/transformer/test_tstring.py +++ b/tests/transformer/test_tstring.py @@ -20,8 +20,7 @@ def test_transform(): result = compile_restricted_exec('a = t"{max([1, 2, 3])}"') assert result.errors == () - assert result.warnings == [ - 'Line 1: TemplateStr statements are not yet allowed, please use f-strings or a real template engine instead.'] # NOQA: E501 + assert result.warnings == [] assert result.code is not None loc = {} exec(result.code, {}, loc)