From fd11a24dd4abce3af87fa92c189cdb0c812dca89 Mon Sep 17 00:00:00 2001 From: Jens Vagelpohl Date: Tue, 8 Oct 2024 14:09:30 +0200 Subject: [PATCH] - remove all pre-Python 38 compatibility code --- pyproject.toml | 2 +- src/RestrictedPython/_compat.py | 1 - src/RestrictedPython/transformer.py | 108 +++++-------------- tests/test_NamedExpr.py | 3 - tests/test_compile.py | 5 +- tests/test_compile_restricted_function.py | 7 +- tests/transformer/test_dict_comprehension.py | 21 ++-- tests/transformer/test_fstring.py | 7 -- 8 files changed, 36 insertions(+), 118 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index fe3e43c..75267ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,7 @@ branch = true source = ["RestrictedPython"] [tool.coverage.report] -fail_under = 97 +fail_under = 97.9 precision = 2 ignore_errors = true show_missing = true diff --git a/src/RestrictedPython/_compat.py b/src/RestrictedPython/_compat.py index 56fe03d..2d85cc4 100644 --- a/src/RestrictedPython/_compat.py +++ b/src/RestrictedPython/_compat.py @@ -3,7 +3,6 @@ _version = sys.version_info -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_PY312_OR_GREATER = _version.major == 3 and _version.minor >= 12 diff --git a/src/RestrictedPython/transformer.py b/src/RestrictedPython/transformer.py index 9a205cc..2034205 100644 --- a/src/RestrictedPython/transformer.py +++ b/src/RestrictedPython/transformer.py @@ -22,16 +22,6 @@ import contextlib import textwrap -from ._compat import IS_PY38_OR_GREATER - - -# Avoid DeprecationWarnings under Python 3.12 and up -if IS_PY38_OR_GREATER: - astStr = ast.Constant - astNum = ast.Constant -else: # pragma: no cover - astStr = ast.Str - astNum = ast.Num # For AugAssign the operator must be converted to a string. IOPERATOR_TO_STR = { @@ -127,16 +117,14 @@ def copy_locations(new_node, old_node): assert 'lineno' in new_node._attributes new_node.lineno = old_node.lineno - if IS_PY38_OR_GREATER: - assert 'end_lineno' in new_node._attributes - new_node.end_lineno = old_node.end_lineno + assert 'end_lineno' in new_node._attributes + new_node.end_lineno = old_node.end_lineno assert 'col_offset' in new_node._attributes new_node.col_offset = old_node.col_offset - if IS_PY38_OR_GREATER: - assert 'end_col_offset' in new_node._attributes - new_node.end_col_offset = old_node.end_col_offset + assert 'end_col_offset' in new_node._attributes + new_node.end_col_offset = old_node.end_col_offset ast.fix_missing_locations(new_node) @@ -280,7 +268,7 @@ def gen_unpack_spec(self, tpl): """ spec = ast.Dict(keys=[], values=[]) - spec.keys.append(astStr('childs')) + spec.keys.append(ast.Constant('childs')) spec.values.append(ast.Tuple([], ast.Load())) # starred elements in a sequence do not contribute into the min_len. @@ -300,12 +288,12 @@ def gen_unpack_spec(self, tpl): elif isinstance(val, ast.Tuple): el = ast.Tuple([], ast.Load()) - el.elts.append(astNum(idx - offset)) + el.elts.append(ast.Constant(idx - offset)) el.elts.append(self.gen_unpack_spec(val)) spec.values[0].elts.append(el) - spec.keys.append(astStr('min_len')) - spec.values.append(astNum(min_len)) + spec.keys.append(ast.Constant('min_len')) + spec.values.append(ast.Constant(min_len)) return spec @@ -492,9 +480,8 @@ def inject_print_collector(self, node, position=0): if isinstance(node, ast.Module): _print.lineno = position _print.col_offset = position - if IS_PY38_OR_GREATER: - _print.end_lineno = position - _print.end_col_offset = position + _print.end_lineno = position + _print.end_col_offset = position ast.fix_missing_locations(_print) else: copy_locations(_print, node) @@ -535,63 +522,22 @@ def node_contents_visit(self, node): # ast for Literals - if IS_PY38_OR_GREATER: - - def visit_Constant(self, node): - """Allow constant literals with restriction for Ellipsis. - - Constant replaces Num, Str, Bytes, NameConstant and Ellipsis in - Python 3.8+. - :see: https://docs.python.org/dev/whatsnew/3.8.html#deprecated - """ - if node.value is Ellipsis: - # Deny using `...`. - # Special handling necessary as ``self.not_allowed(node)`` - # would return the Error Message: - # 'Constant statements are not allowed.' - # which is only partial true. - self.error(node, 'Ellipsis statements are not allowed.') - return - return self.node_contents_visit(node) - - else: - - def visit_Num(self, node): - """Allow integer numbers without restrictions. - - Replaced by Constant in Python 3.8. - """ - return self.node_contents_visit(node) - - def visit_Str(self, node): - """Allow string literals without restrictions. - - Replaced by Constant in Python 3.8. - """ - return self.node_contents_visit(node) + def visit_Constant(self, node): + """Allow constant literals with restriction for Ellipsis. - def visit_Bytes(self, node): - """Allow bytes literals without restrictions. - - Replaced by Constant in Python 3.8. - """ - return self.node_contents_visit(node) - - def visit_Ellipsis(self, node): - """Deny using `...`. - - Replaced by Constant in Python 3.8. - """ - return self.not_allowed(node) - - def visit_NameConstant(self, node): - """Allow constant literals (True, False, None) without ... - - restrictions. - - Replaced by Constant in Python 3.8. - """ - return self.node_contents_visit(node) + Constant replaces Num, Str, Bytes, NameConstant and Ellipsis in + Python 3.8+. + :see: https://docs.python.org/dev/whatsnew/3.8.html#deprecated + """ + if node.value is Ellipsis: + # Deny using `...`. + # Special handling necessary as ``self.not_allowed(node)`` + # would return the Error Message: + # 'Constant statements are not allowed.' + # which is only partial true. + self.error(node, 'Ellipsis statements are not allowed.') + return + return self.node_contents_visit(node) def visit_Interactive(self, node): """Allow single mode without restrictions.""" @@ -915,7 +861,7 @@ def visit_Attribute(self, node): node = self.node_contents_visit(node) new_node = ast.Call( func=ast.Name('_getattr_', ast.Load()), - args=[node.value, astStr(node.attr)], + args=[node.value, ast.Constant(node.attr)], keywords=[]) copy_locations(new_node, node) @@ -1119,7 +1065,7 @@ def visit_AugAssign(self, node): value=ast.Call( func=ast.Name('_inplacevar_', ast.Load()), args=[ - astStr(IOPERATOR_TO_STR[type(node.op)]), + ast.Constant(IOPERATOR_TO_STR[type(node.op)]), ast.Name(node.target.id, ast.Load()), node.value ], diff --git a/tests/test_NamedExpr.py b/tests/test_NamedExpr.py index 308d0dd..2da22ad 100644 --- a/tests/test_NamedExpr.py +++ b/tests/test_NamedExpr.py @@ -4,14 +4,11 @@ from ast import NodeTransformer from ast import parse from unittest import TestCase -from unittest import skipUnless from RestrictedPython import compile_restricted from RestrictedPython import safe_globals -from RestrictedPython._compat import IS_PY38_OR_GREATER -@skipUnless(IS_PY38_OR_GREATER, "Feature available for Python 3.8+") class TestNamedExpr(TestCase): def test_works(self): code, gs = compile_str("if x:= x + 1: True\n") diff --git a/tests/test_compile.py b/tests/test_compile.py index 603ad84..c4ea7de 100644 --- a/tests/test_compile.py +++ b/tests/test_compile.py @@ -8,7 +8,6 @@ from RestrictedPython import compile_restricted_eval from RestrictedPython import compile_restricted_exec from RestrictedPython import compile_restricted_single -from RestrictedPython._compat import IS_PY38_OR_GREATER from RestrictedPython._compat import IS_PY310_OR_GREATER from RestrictedPython._compat import IS_PY311_OR_GREATER from tests.helper import restricted_eval @@ -43,10 +42,8 @@ def test_compile__invalid_syntax(): compile_restricted(INVALID_ASSINGMENT, '', 'exec') if IS_PY310_OR_GREATER: assert "SyntaxError: cannot assign to literal here." in str(err.value) - elif IS_PY38_OR_GREATER: - assert "cannot assign to literal at statement:" in str(err.value) else: - assert "can't assign to literal at statement:" in str(err.value) + assert "cannot assign to literal at statement:" in str(err.value) def test_compile__compile_restricted_exec__1(): diff --git a/tests/test_compile_restricted_function.py b/tests/test_compile_restricted_function.py index d5b89b3..0bf0437 100644 --- a/tests/test_compile_restricted_function.py +++ b/tests/test_compile_restricted_function.py @@ -3,7 +3,6 @@ from RestrictedPython import PrintCollector from RestrictedPython import compile_restricted_function from RestrictedPython import safe_builtins -from RestrictedPython._compat import IS_PY38_OR_GREATER from RestrictedPython._compat import IS_PY310_OR_GREATER @@ -242,11 +241,7 @@ def test_compile_restricted_function_invalid_syntax(): assert error_msg.startswith( "Line 1: SyntaxError: cannot assign to literal here. Maybe " ) - elif IS_PY38_OR_GREATER: - assert error_msg.startswith( - "Line 1: SyntaxError: cannot assign to literal at statement:" - ) else: assert error_msg.startswith( - "Line 1: SyntaxError: can't assign to literal at statement:" + "Line 1: SyntaxError: cannot assign to literal at statement:" ) diff --git a/tests/transformer/test_dict_comprehension.py b/tests/transformer/test_dict_comprehension.py index 17b8c45..ccc5ce0 100644 --- a/tests/transformer/test_dict_comprehension.py +++ b/tests/transformer/test_dict_comprehension.py @@ -1,4 +1,3 @@ -from RestrictedPython._compat import IS_PY38_OR_GREATER from tests.helper import restricted_exec @@ -29,19 +28,11 @@ def test_dict_comprehension_with_attrs(mocker): calls = [mocker.call(seq, 'z')] # Note: Order changed in PEP 572, starting with Python 3.8. - if IS_PY38_OR_GREATER: - calls.extend([ - mocker.call(z[0], 'k'), - mocker.call(z[1], 'k'), - mocker.call(z[1], 'k'), - mocker.call(z[1], 'v'), - ]) - else: - calls.extend([ - mocker.call(z[0], 'k'), - mocker.call(z[1], 'k'), - mocker.call(z[1], 'v'), - mocker.call(z[1], 'k'), - ]) + calls.extend([ + mocker.call(z[0], 'k'), + mocker.call(z[1], 'k'), + mocker.call(z[1], 'k'), + mocker.call(z[1], 'v'), + ]) _getattr_.assert_has_calls(calls) diff --git a/tests/transformer/test_fstring.py b/tests/transformer/test_fstring.py index 68b1743..2b31c83 100644 --- a/tests/transformer/test_fstring.py +++ b/tests/transformer/test_fstring.py @@ -1,7 +1,4 @@ -import pytest - from RestrictedPython import compile_restricted_exec -from RestrictedPython._compat import IS_PY38_OR_GREATER from RestrictedPython.PrintCollector import PrintCollector @@ -37,10 +34,6 @@ def test_visit_invalid_variable_name(): """ -@pytest.mark.skipif( - not IS_PY38_OR_GREATER, - reason="f-string self-documenting expressions added in Python 3.8.", -) def test_f_string_self_documenting_expressions(): """Checks if f-string self-documenting expressions is checked.""" result = compile_restricted_exec(