Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Python 3.9+ #142

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand All @@ -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
Expand Down
5 changes: 2 additions & 3 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
4 changes: 1 addition & 3 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ classifiers =

[options]
py_modules = simpleeval

[bdist_wheel]
universal=1
python_requires = >=3.9

[pycodestyle]
max_line_length = 99
37 changes: 9 additions & 28 deletions simpleeval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -103,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'

Expand All @@ -124,19 +122,14 @@
# 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
):
# PyInstaller environment doesn't include this module.
DISALLOW_FUNCTIONS.add(help)


if PYTHON3:
# exec is not a function in Python2...
exec("DISALLOW_FUNCTIONS.add(exec)") # pylint: disable=exec-used


########################################
# Exceptions:

Expand Down Expand Up @@ -317,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}
Expand Down Expand Up @@ -374,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
Expand Down Expand Up @@ -668,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:
Expand All @@ -680,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))
Expand Down
21 changes: 4 additions & 17 deletions test_simpleeval.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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})
Expand All @@ -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}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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"""
Expand Down