diff --git a/.meta.toml b/.meta.toml index a7903ca..7e125ed 100644 --- a/.meta.toml +++ b/.meta.toml @@ -57,7 +57,7 @@ coverage-setenv = [ ] [coverage] -fail-under = 98.8 +fail-under = 98.5 [isort] additional-sources = "{toxinidir}/tests" diff --git a/CHANGES.rst b/CHANGES.rst index 29bec8c..32f7501 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -14,6 +14,8 @@ Features - Officially support Python 3.11. +- Add test for trystar syntax. + 5.2 (2021-11-19) ---------------- diff --git a/src/RestrictedPython/Guards.py b/src/RestrictedPython/Guards.py index faaaa18..9b70ae3 100644 --- a/src/RestrictedPython/Guards.py +++ b/src/RestrictedPython/Guards.py @@ -17,6 +17,8 @@ import builtins +from RestrictedPython._compat import IS_PY311_OR_GREATER + safe_builtins = {} @@ -103,6 +105,9 @@ 'ZeroDivisionError', ] +if IS_PY311_OR_GREATER: + _safe_exceptions.append("ExceptionGroup") + for name in _safe_names: safe_builtins[name] = getattr(builtins, name) diff --git a/src/RestrictedPython/_compat.py b/src/RestrictedPython/_compat.py index a41e8a2..690c535 100644 --- a/src/RestrictedPython/_compat.py +++ b/src/RestrictedPython/_compat.py @@ -6,5 +6,6 @@ IS_PY37_OR_GREATER = _version.major == 3 and _version.minor >= 7 IS_PY38_OR_GREATER = _version.major == 3 and _version.minor >= 8 IS_PY310_OR_GREATER = _version.major == 3 and _version.minor >= 10 +IS_PY311_OR_GREATER = _version.major == 3 and _version.minor >= 11 IS_CPYTHON = platform.python_implementation() == 'CPython' diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index 6e65635..f20650a 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -1127,6 +1127,10 @@ def visit_Try(self, node): """Allow `try` without restrictions.""" return self.node_contents_visit(node) + def visit_TryStar(self, node): + """Allow `ExceptionGroup` without restrictions.""" + return self.node_contents_visit(node) + def visit_ExceptHandler(self, node): """Protect exception handlers.""" node = self.node_contents_visit(node) diff --git a/tests/transformer/test_try.py b/tests/transformer/test_try.py index 4abe400..fcba0f6 100644 --- a/tests/transformer/test_try.py +++ b/tests/transformer/test_try.py @@ -1,4 +1,7 @@ +import pytest + from RestrictedPython import compile_restricted_exec +from RestrictedPython._compat import IS_PY311_OR_GREATER from tests.helper import restricted_exec @@ -47,6 +50,39 @@ def test_RestrictingNodeTransformer__visit_Try__2( ]) +TRY_EXCEPT_STAR = """ +def try_except_star(m): + try: + m('try') + raise ExceptionGroup("group", [IndentationError('f1'), ValueError(65)]) + except* IndentationError: + m('IndentationError') + except* ValueError: + m('ValueError') + except* RuntimeError: + m('RuntimeError') +""" + + +@pytest.mark.skipif( + not IS_PY311_OR_GREATER, + reason="ExceptionGroup class was added in Python 3.11.", +) +def test_RestrictingNodeTransformer__visit_TryStar__1(mocker): + """It allows try-except* PEP 654 statements.""" + trace = mocker.stub() + restricted_exec(TRY_EXCEPT_STAR)['try_except_star'](trace) + + trace.assert_has_calls([ + mocker.call('try'), + mocker.call('IndentationError'), + mocker.call('ValueError') + ]) + + with pytest.raises(AssertionError): + trace.assert_has_calls([mocker.call('RuntimeError')]) + + TRY_FINALLY = """ def try_finally(m): try: diff --git a/tox.ini b/tox.ini index e338d78..ba87297 100644 --- a/tox.ini +++ b/tox.ini @@ -96,7 +96,7 @@ commands = pytest --cov=src --cov=tests --cov-report= {posargs} coverage run -a -m sphinx -b doctest -d {envdir}/.cache/doctrees docs {envdir}/.cache/doctest coverage html - coverage report -m --fail-under=98.8 + coverage report -m --fail-under=98.5 [coverage:run] branch = True