From 376f303dd80ae2a408c1b1f5fd785e66d7239acb Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Tue, 18 Jun 2024 19:53:40 -0500 Subject: [PATCH 1/2] Support Python 3.9+ --- .github/workflows/ci.yml | 11 ++++++++++- README.rst | 3 +-- setup.cfg | 4 +--- simpleeval.py | 1 + 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fd41c6..bb2c2f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,7 +18,14 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest] - python: ['2.7', '3.7', '3.8', '3.9', '3.10', '3.11', '3.12-dev', 'pypy-3.8'] + python: + - '3.9' + - '3.10' + - '3.11' + - '3.12' + - '3.13' + - 'pypy-3.9' + - 'pypy-3.10' env: OS: ${{ matrix.os }} PYTHON: ${{ matrix.python }} @@ -29,6 +36,8 @@ jobs: uses: actions/setup-python@master with: python-version: ${{ matrix.python }} + allow-prereleases: true + - name: Generate Report run: | pip install coverage diff --git a/README.rst b/README.rst index 6a98f50..9b03c26 100644 --- a/README.rst +++ b/README.rst @@ -405,8 +405,7 @@ and then use ``EvalNoMethods`` instead of the ``SimpleEval`` class. Other... -------- -The library supports python 3 - but should be mostly compatible (and tested before 0.9.11) -with python 2.7 as well. +The library supports Python 3.9 and higher. Object attributes that start with ``_`` or ``func_`` are disallowed by default. If you really need that (BE CAREFUL!), then modify the module global diff --git a/setup.cfg b/setup.cfg index ed290d4..db34688 100644 --- a/setup.cfg +++ b/setup.cfg @@ -17,9 +17,7 @@ classifiers = [options] py_modules = simpleeval - -[bdist_wheel] -universal=1 +python_requires = >=3.9 [pycodestyle] max_line_length = 99 diff --git a/simpleeval.py b/simpleeval.py index df75d6a..e0f5741 100644 --- a/simpleeval.py +++ b/simpleeval.py @@ -58,6 +58,7 @@ - daxamin (Dax Amin) Better error for attempting to eval empty string - smurfix (Matthias Urlichs) Allow clearing functions / operators / etc completely - koenigsley (Mikhail Yeremeyev) documentation typos correction. +- kurtmckee (Kurt McKee) Infrastructure updates ------------------------------------- Basic Usage: From 47fdf5ec01ec5b8c2389c5c6e5056c13f8c2fafa Mon Sep 17 00:00:00 2001 From: Kurt McKee Date: Mon, 5 Aug 2024 09:51:55 -0500 Subject: [PATCH 2/2] Remove old Python compatibility conditionals and references --- README.rst | 2 +- simpleeval.py | 36 ++++++++---------------------------- test_simpleeval.py | 21 ++++----------------- 3 files changed, 13 insertions(+), 46 deletions(-) diff --git a/README.rst b/README.rst index 9b03c26..041fd61 100644 --- a/README.rst +++ b/README.rst @@ -246,7 +246,7 @@ are provided in the ``DEFAULT_FUNCTIONS`` dict: +----------------+--------------------------------------------------+ | ``float(x)`` | Convert ``x`` to a ``float``. | +----------------+--------------------------------------------------+ -| ``str(x)`` | Convert ``x`` to a ``str`` (``unicode`` in py2) | +| ``str(x)`` | Convert ``x`` to a ``str`` | +----------------+--------------------------------------------------+ If you want to provide a list of functions, but want to keep these as well, diff --git a/simpleeval.py b/simpleeval.py index e0f5741..a98623f 100644 --- a/simpleeval.py +++ b/simpleeval.py @@ -104,9 +104,6 @@ import warnings from random import random -PYTHON3 = sys.version_info[0] == 3 -PYTHON35 = PYTHON3 and sys.version_info > (3, 5) - ######################################## # Module wide 'globals' @@ -125,7 +122,7 @@ # their functionality is required, then please wrap them up in a safe container. And think # very hard about it first. And don't say I didn't warn you. # builtins is a dict in python >3.6 but a module before -DISALLOW_FUNCTIONS = {type, isinstance, eval, getattr, setattr, repr, compile, open} +DISALLOW_FUNCTIONS = {type, isinstance, eval, getattr, setattr, repr, compile, open, exec} if hasattr(__builtins__, "help") or ( hasattr(__builtins__, "__contains__") and "help" in __builtins__ # type: ignore ): @@ -133,11 +130,6 @@ DISALLOW_FUNCTIONS.add(help) -if PYTHON3: - # exec is not a function in Python2... - exec("DISALLOW_FUNCTIONS.add(exec)") # pylint: disable=exec-used - - ######################################## # Exceptions: @@ -318,8 +310,7 @@ def safe_lshift(a, b): # pylint: disable=invalid-name "randint": random_int, "int": int, "float": float, - # pylint: disable=undefined-variable - "str": str if PYTHON3 else unicode, # type: ignore + "str": str, } DEFAULT_NAMES = {"True": True, "False": False, "None": None} @@ -375,23 +366,12 @@ def __init__(self, operators=None, functions=None, names=None): ast.Attribute: self._eval_attribute, ast.Index: self._eval_index, ast.Slice: self._eval_slice, + ast.NameConstant: self._eval_constant, + ast.JoinedStr: self._eval_joinedstr, + ast.FormattedValue: self._eval_formattedvalue, + ast.Constant: self._eval_constant, } - # py3k stuff: - if hasattr(ast, "NameConstant"): - self.nodes[ast.NameConstant] = self._eval_constant - - # py3.6, f-strings - if hasattr(ast, "JoinedStr"): - self.nodes[ast.JoinedStr] = self._eval_joinedstr # f-string - self.nodes[ - ast.FormattedValue - ] = self._eval_formattedvalue # formatted value in f-string - - # py3.8 uses ast.Constant instead of ast.Num, ast.Str, ast.NameConstant - if hasattr(ast, "Constant"): - self.nodes[ast.Constant] = self._eval_constant - # Defaults: self.ATTR_INDEX_FALLBACK = ATTR_INDEX_FALLBACK @@ -669,7 +649,7 @@ def _eval_dict(self, node): result = {} for key, value in zip(node.keys, node.values): - if PYTHON35 and key is None: + if key is None: # "{**x}" gets parsed as a key-value pair of (None, Name(x)) result.update(self._eval(value)) else: @@ -681,7 +661,7 @@ def _eval_list(self, node): result = [] for item in node.elts: - if PYTHON3 and isinstance(item, ast.Starred): + if isinstance(item, ast.Starred): result.extend(self._eval(item.value)) else: result.append(self._eval(item)) diff --git a/test_simpleeval.py b/test_simpleeval.py index bc83d50..0454f70 100644 --- a/test_simpleeval.py +++ b/test_simpleeval.py @@ -429,9 +429,8 @@ def test_large_shifts(self): def test_encode_bignums(self): # thanks gk - if hasattr(1, "from_bytes"): # python3 only - with self.assertRaises(simpleeval.IterableTooLong): - self.t('(1).from_bytes(("123123123123123123123123").encode()*999999, "big")', 0) + with self.assertRaises(simpleeval.IterableTooLong): + self.t('(1).from_bytes(("123123123123123123123123").encode()*999999, "big")', 0) def test_string_length(self): with self.assertRaises(simpleeval.IterableTooLong): @@ -612,7 +611,6 @@ def test_dict_contains(self): self.t('{"a": 24}.get("b", 11)', 11) self.t('"a" in {"a": 24}', True) - @unittest.skipIf(not simpleeval.PYTHON35, "feature not supported") def test_dict_star_expression(self): self.s.names["x"] = {"a": 1, "b": 2} self.t('{"a": 0, **x, "c": 3}', {"a": 1, "b": 2, "c": 3}) @@ -621,7 +619,6 @@ def test_dict_star_expression(self): self.s.names["y"] = {"x": 1, "y": 2} self.t('{"a": 0, **x, **y, "c": 3}', {"a": 1, "b": 2, "c": 3, "x": 1, "y": 2}) - @unittest.skipIf(not simpleeval.PYTHON35, "feature not supported") def test_dict_invalid_star_expression(self): self.s.names["x"] = {"a": 1, "b": 2} self.s.names["y"] = {"x": 1, "y": 2} @@ -660,12 +657,10 @@ def test_list_contains(self): self.t('"b" in ["a","b"]', True) - @unittest.skipIf(not simpleeval.PYTHON3, "feature not supported") def test_list_star_expression(self): self.s.names["x"] = [1, 2, 3] self.t('["a", *x, "b"]', ["a", 1, 2, 3, "b"]) - @unittest.skipIf(not simpleeval.PYTHON3, "feature not supported") def test_list_invalid_star_expression(self): self.s.names["x"] = [1, 2, 3] self.s.names["y"] = 42 @@ -1200,10 +1195,7 @@ def foo(y): class TestDisallowedFunctions(DRYTest): def test_functions_are_disallowed_at_init(self): - DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, compile, open] - if simpleeval.PYTHON3: - # pylint: disable=exec-used - exec("DISALLOWED.append(exec)") # exec is not a function in Python2... + DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, compile, open, exec] for f in simpleeval.DISALLOW_FUNCTIONS: assert f in DISALLOWED @@ -1213,11 +1205,7 @@ def test_functions_are_disallowed_at_init(self): SimpleEval(functions={"foo": x}) def test_functions_are_disallowed_in_expressions(self): - DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, compile, open] - - if simpleeval.PYTHON3: - # pylint: disable=exec-used - exec("DISALLOWED.append(exec)") # exec is not a function in Python2... + DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, compile, open, exec] for f in simpleeval.DISALLOW_FUNCTIONS: assert f in DISALLOWED @@ -1234,7 +1222,6 @@ def test_functions_are_disallowed_in_expressions(self): simpleeval.DEFAULT_FUNCTIONS = DF.copy() -@unittest.skipIf(simpleeval.PYTHON3 is not True, "Python2 fails - but it's not supported anyway.") @unittest.skipIf(platform.python_implementation() == "PyPy", "GC set_debug not available in PyPy") class TestReferenceCleanup(DRYTest): """Test cleanup without cyclic references"""