Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ jobs:
with:
persist-credentials: false
- name: Install uv + caching
uses: astral-sh/setup-uv@v6
# astral/setup-uv@7
uses: astral-sh/setup-uv@3259c6206f993105e3a61b142c2d97bf4b9ef83d
with:
enable-cache: true
cache-dependency-glob: |
Expand Down
4 changes: 2 additions & 2 deletions .meta.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -55,7 +55,7 @@ coverage-setenv = [
]

[coverage]
fail-under = 97.3
fail-under = 97.2

[isort]
additional-sources = "{toxinidir}/tests"
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Changes
8.1 (unreleased)
----------------

- Nothing changed yet.
- Allow to use the package with Python 3.14 including t-string support.


8.1a1.dev0 (2025-03-20)
Expand Down
5 changes: 3 additions & 2 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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:
Expand Down
196 changes: 196 additions & 0 deletions docs/contributing/ast/python3_14.ast
Original file line number Diff line number Diff line change
@@ -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)
}
5 changes: 5 additions & 0 deletions docs/contributing/changes_from313to314.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Changes from Python 3.13 to Python 3.14
---------------------------------------

.. literalinclude:: ast/python3_14.ast
:diff: ast/python3_13.ast
6 changes: 6 additions & 0 deletions docs/contributing/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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:

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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',
],
Expand Down
2 changes: 2 additions & 0 deletions src/RestrictedPython/_compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'
23 changes: 23 additions & 0 deletions src/RestrictedPython/transformer.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"f_back",
"f_builtins",
"f_code",
"f_generator",
"f_globals",
# "f_lasti", # int
# "f_lineno", # int
Expand All @@ -99,6 +100,7 @@
# on generator objects:
"gi_frame",
# "gi_running", # bool
# "gi_suspended", # bool
"gi_code",
"gi_yieldfrom",
# on coroutine objects:
Expand Down Expand Up @@ -563,6 +565,27 @@ 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.
"""
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.
"""
return self.node_contents_visit(node)

def visit_JoinedStr(self, node):
"""Allow joined string without restrictions."""
return self.node_contents_visit(node)
Expand Down
Loading