simpleeval 0.9.13__tar.gz → 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {simpleeval-0.9.13 → simpleeval-1.0.0}/.gitignore +1 -0
- {simpleeval-0.9.13 → simpleeval-1.0.0}/LICENCE +1 -1
- {simpleeval-0.9.13 → simpleeval-1.0.0}/Makefile +5 -6
- {simpleeval-0.9.13/simpleeval.egg-info → simpleeval-1.0.0}/PKG-INFO +58 -27
- simpleeval-0.9.13/PKG-INFO → simpleeval-1.0.0/README.rst +54 -41
- {simpleeval-0.9.13 → simpleeval-1.0.0}/pyproject.toml +3 -0
- {simpleeval-0.9.13 → simpleeval-1.0.0}/setup.cfg +4 -5
- simpleeval-0.9.13/README.rst → simpleeval-1.0.0/simpleeval.egg-info/PKG-INFO +72 -25
- {simpleeval-0.9.13 → simpleeval-1.0.0}/simpleeval.py +63 -43
- {simpleeval-0.9.13 → simpleeval-1.0.0}/test_simpleeval.py +78 -20
- {simpleeval-0.9.13 → simpleeval-1.0.0}/MANIFEST.in +0 -0
- {simpleeval-0.9.13 → simpleeval-1.0.0}/setup.py +0 -0
- {simpleeval-0.9.13 → simpleeval-1.0.0}/simpleeval.egg-info/SOURCES.txt +0 -0
- {simpleeval-0.9.13 → simpleeval-1.0.0}/simpleeval.egg-info/dependency_links.txt +0 -0
- {simpleeval-0.9.13 → simpleeval-1.0.0}/simpleeval.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
test:
|
|
2
|
-
python test_simpleeval.py
|
|
2
|
+
python -Werror test_simpleeval.py
|
|
3
3
|
|
|
4
4
|
autotest:
|
|
5
5
|
find . -name \*.py -not -path .\/.v\* | entr make test
|
|
@@ -22,11 +22,10 @@ coverage:
|
|
|
22
22
|
coverage run test_simpleeval.py
|
|
23
23
|
|
|
24
24
|
lint:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
pylint simpleeval.py test_simpleeval.py
|
|
25
|
+
ruff check simpleeval.py test_simpleeval.py
|
|
26
|
+
ruff format --check simpleeval.py test_simpleeval.py
|
|
28
27
|
mypy simpleeval.py test_simpleeval.py
|
|
29
28
|
|
|
30
29
|
format:
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
ruff check --fix-only simpleeval.py test_simpleeval.py
|
|
31
|
+
ruff format simpleeval.py test_simpleeval.py
|
|
@@ -1,39 +1,46 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: simpleeval
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: A simple, safe single expression evaluator library.
|
|
5
5
|
Home-page: https://github.com/danthedeckie/simpleeval
|
|
6
6
|
Author: Daniel Fairhead
|
|
7
7
|
Author-email: danthedeckie@gmail.com
|
|
8
|
+
License: MIT
|
|
8
9
|
Keywords: eval,simple,expression,parse,ast
|
|
9
|
-
Classifier: Development Status ::
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
11
|
Classifier: Intended Audience :: Developers
|
|
11
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
14
|
Classifier: Programming Language :: Python
|
|
15
|
+
Requires-Python: >=3.9
|
|
14
16
|
Description-Content-Type: text/x-rst
|
|
15
17
|
License-File: LICENCE
|
|
16
18
|
|
|
17
19
|
simpleeval (Simple Eval)
|
|
18
20
|
========================
|
|
19
21
|
|
|
20
|
-
.. image:: https://github.com/danthedeckie/simpleeval/actions/workflows/ci.yml/badge.svg?branch=gh-actions-build
|
|
22
|
+
.. |build-status| image:: https://github.com/danthedeckie/simpleeval/actions/workflows/ci.yml/badge.svg?branch=gh-actions-build
|
|
21
23
|
:target: https://github.com/danthedeckie/simpleeval/actions/
|
|
22
24
|
:alt: Build Status
|
|
23
25
|
|
|
24
|
-
.. image:: https://codecov.io/gh/danthedeckie/simpleeval/branch/master/graph/badge.svg?token=isRnN1yrca
|
|
26
|
+
.. |code-coverage| image:: https://codecov.io/gh/danthedeckie/simpleeval/branch/master/graph/badge.svg?token=isRnN1yrca
|
|
25
27
|
:target: https://codecov.io/gh/danthedeckie/simpleeval
|
|
26
|
-
:alt: Code Coverage
|
|
28
|
+
:alt: Code Coverage Status
|
|
27
29
|
|
|
28
|
-
.. image:: https://badge.fury.io/py/simpleeval.svg
|
|
30
|
+
.. |pypi-version| image:: https://badge.fury.io/py/simpleeval.svg
|
|
29
31
|
:target: https://badge.fury.io/py/simpleeval
|
|
30
32
|
:alt: PyPI Version
|
|
31
33
|
|
|
32
|
-
.. image:: https://img.shields.io/badge/
|
|
33
|
-
:
|
|
34
|
+
.. |python-versions| image:: https://img.shields.io/badge/python-3.9_%7C_3.10_%7C_3.11_%7C_3.12_%7C_3.13_%7C_PyPy3.9_%7C_PyPy3.10-blue
|
|
35
|
+
:alt: Static Badge
|
|
34
36
|
|
|
35
|
-
.. image:: https://img.shields.io/
|
|
36
|
-
:
|
|
37
|
+
.. |pypi-monthly-downloads| image:: https://img.shields.io/pypi/dm/SimpleEval
|
|
38
|
+
:alt: PyPI - Downloads
|
|
39
|
+
|
|
40
|
+
.. |formatting-with-ruff| image:: https://img.shields.io/badge/-ruff-black?logo=lightning&logoColor=%2300ff00&link=https%3A%2F%2Fdocs.astral.sh%2Fruff%2F
|
|
41
|
+
:alt: Static Badge
|
|
42
|
+
|
|
43
|
+
|build-status| |code-coverage| |pypi-version| |python-versions| |pypi-monthly-downloads| |formatting-with-ruff|
|
|
37
44
|
|
|
38
45
|
|
|
39
46
|
A single file library for easily adding evaluatable expressions into
|
|
@@ -173,7 +180,7 @@ The ``^`` operator is often mistaken for a exponent operator, not the bitwise
|
|
|
173
180
|
operation that it is in python, so if you want ``3 ^ 2`` to equal ``9``, you can
|
|
174
181
|
replace the operator like this:
|
|
175
182
|
|
|
176
|
-
.. code-block::
|
|
183
|
+
.. code-block:: pycon
|
|
177
184
|
|
|
178
185
|
>>> import ast
|
|
179
186
|
>>> from simpleeval import safe_power
|
|
@@ -192,13 +199,13 @@ Limited Power
|
|
|
192
199
|
Also note, the ``**`` operator has been locked down by default to have a
|
|
193
200
|
maximum input value of ``4000000``, which makes it somewhat harder to make
|
|
194
201
|
expressions which go on for ever. You can change this limit by changing the
|
|
195
|
-
``simpleeval.
|
|
202
|
+
``simpleeval.MAX_POWER`` module level value to whatever is an appropriate value
|
|
196
203
|
for you (and the hardware that you're running on) or if you want to completely
|
|
197
204
|
remove all limitations, you can set the ``s.operators[ast.Pow] = operator.pow``
|
|
198
205
|
or make your own function.
|
|
199
206
|
|
|
200
207
|
On my computer, ``9**9**5`` evaluates almost instantly, but ``9**9**6`` takes
|
|
201
|
-
over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``
|
|
208
|
+
over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``MAX_POWER``
|
|
202
209
|
limit, it throws a ``NumberTooHigh`` exception for you. (Otherwise it would go
|
|
203
210
|
on for hours, or until the computer runs out of memory)
|
|
204
211
|
|
|
@@ -216,7 +223,7 @@ If Expressions
|
|
|
216
223
|
|
|
217
224
|
You can use python style ``if x then y else z`` type expressions:
|
|
218
225
|
|
|
219
|
-
.. code-block::
|
|
226
|
+
.. code-block:: pycon
|
|
220
227
|
|
|
221
228
|
>>> simple_eval("'equal' if x == y else 'not equal'",
|
|
222
229
|
names={"x": 1, "y": 2})
|
|
@@ -224,7 +231,7 @@ You can use python style ``if x then y else z`` type expressions:
|
|
|
224
231
|
|
|
225
232
|
which, of course, can be nested:
|
|
226
233
|
|
|
227
|
-
.. code-block::
|
|
234
|
+
.. code-block:: pycon
|
|
228
235
|
|
|
229
236
|
>>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
|
|
230
237
|
'c'
|
|
@@ -235,7 +242,7 @@ Functions
|
|
|
235
242
|
|
|
236
243
|
You can define functions which you'd like the expresssions to have access to:
|
|
237
244
|
|
|
238
|
-
.. code-block::
|
|
245
|
+
.. code-block:: pycon
|
|
239
246
|
|
|
240
247
|
>>> simple_eval("double(21)", functions={"double": lambda x:x*2})
|
|
241
248
|
42
|
|
@@ -243,7 +250,7 @@ You can define functions which you'd like the expresssions to have access to:
|
|
|
243
250
|
You can define "real" functions to pass in rather than lambdas, of course too,
|
|
244
251
|
and even re-name them so that expressions can be shorter
|
|
245
252
|
|
|
246
|
-
.. code-block::
|
|
253
|
+
.. code-block:: pycon
|
|
247
254
|
|
|
248
255
|
>>> def double(x):
|
|
249
256
|
return x * 2
|
|
@@ -262,13 +269,13 @@ are provided in the ``DEFAULT_FUNCTIONS`` dict:
|
|
|
262
269
|
+----------------+--------------------------------------------------+
|
|
263
270
|
| ``float(x)`` | Convert ``x`` to a ``float``. |
|
|
264
271
|
+----------------+--------------------------------------------------+
|
|
265
|
-
| ``str(x)`` | Convert ``x`` to a ``str``
|
|
272
|
+
| ``str(x)`` | Convert ``x`` to a ``str`` |
|
|
266
273
|
+----------------+--------------------------------------------------+
|
|
267
274
|
|
|
268
275
|
If you want to provide a list of functions, but want to keep these as well,
|
|
269
276
|
then you can do a normal python ``.copy()`` & ``.update``:
|
|
270
277
|
|
|
271
|
-
.. code-block::
|
|
278
|
+
.. code-block:: pycon
|
|
272
279
|
|
|
273
280
|
>>> my_functions = simpleeval.DEFAULT_FUNCTIONS.copy()
|
|
274
281
|
>>> my_functions.update(
|
|
@@ -283,7 +290,7 @@ Names
|
|
|
283
290
|
Sometimes it's useful to have variables available, which in python terminology
|
|
284
291
|
are called 'names'.
|
|
285
292
|
|
|
286
|
-
.. code-block::
|
|
293
|
+
.. code-block:: pycon
|
|
287
294
|
|
|
288
295
|
>>> simple_eval("a + b", names={"a": 11, "b": 100})
|
|
289
296
|
111
|
|
@@ -291,7 +298,7 @@ are called 'names'.
|
|
|
291
298
|
You can also hand the handling of names over to a function, if you prefer:
|
|
292
299
|
|
|
293
300
|
|
|
294
|
-
.. code-block::
|
|
301
|
+
.. code-block:: pycon
|
|
295
302
|
|
|
296
303
|
>>> def name_handler(node):
|
|
297
304
|
return ord(node.id[0].lower(a))-96
|
|
@@ -300,9 +307,34 @@ You can also hand the handling of names over to a function, if you prefer:
|
|
|
300
307
|
3
|
|
301
308
|
|
|
302
309
|
That was a bit of a silly example, but you could use this for pulling values
|
|
303
|
-
from a database or file, say, or doing some kind of caching system.
|
|
310
|
+
from a database or file, looking up spreadsheet cells, say, or doing some kind of caching system.
|
|
304
311
|
|
|
305
|
-
|
|
312
|
+
In general, when it attempts to find a variable by name, if it cannot find one,
|
|
313
|
+
then it will look in the ``functions`` for a function of that name. If you want your name handler
|
|
314
|
+
function to return an "I can't find that name!", then it should raise a ``simpleeval.NameNotDefined``
|
|
315
|
+
exception. Eg:
|
|
316
|
+
|
|
317
|
+
.. code-block:: pycon
|
|
318
|
+
|
|
319
|
+
>>> def name_handler(node):
|
|
320
|
+
... if node.id[0] == 'a':
|
|
321
|
+
... return 21
|
|
322
|
+
... raise NameNotDefined(node.id[0], "Not found")
|
|
323
|
+
...
|
|
324
|
+
... simple_eval('a + a', names=name_handler, functions={"b": 100})
|
|
325
|
+
|
|
326
|
+
42
|
|
327
|
+
|
|
328
|
+
>>> simple_eval('a + b', names=name_handler, functions={'b': 100})
|
|
329
|
+
121
|
|
330
|
+
|
|
331
|
+
(Note: in that example, putting a number directly into the ``functions`` dict was done just to
|
|
332
|
+
show the fall-back to functions. Normally only put actual callables in there.)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
The two default names that are provided are ``True`` and ``False``. So if you want to provide
|
|
336
|
+
your own names, but want ``True`` and ``False`` to keep working, either provide them yourself,
|
|
337
|
+
or ``.copy()`` and ``.update`` the ``DEFAULT_NAMES``. (See functions example above).
|
|
306
338
|
|
|
307
339
|
Creating an Evaluator Class
|
|
308
340
|
---------------------------
|
|
@@ -312,7 +344,7 @@ evaluations, you can create a SimpleEval object, and pass it expressions each
|
|
|
312
344
|
time (which should be a bit quicker, and certainly more convenient for some use
|
|
313
345
|
cases):
|
|
314
346
|
|
|
315
|
-
.. code-block::
|
|
347
|
+
.. code-block:: pycon
|
|
316
348
|
|
|
317
349
|
>>> s = SimpleEval()
|
|
318
350
|
|
|
@@ -391,7 +423,7 @@ comprehensions.
|
|
|
391
423
|
Since the primary intention of this library is short expressions - an extra 'sweetener' is
|
|
392
424
|
enabled by default. You can access a dict (or similar's) keys using the .attr syntax:
|
|
393
425
|
|
|
394
|
-
.. code-block::
|
|
426
|
+
.. code-block:: pycon
|
|
395
427
|
|
|
396
428
|
>>> simple_eval("foo.bar", names={"foo": {"bar": 42}})
|
|
397
429
|
42
|
|
@@ -421,8 +453,7 @@ and then use ``EvalNoMethods`` instead of the ``SimpleEval`` class.
|
|
|
421
453
|
Other...
|
|
422
454
|
--------
|
|
423
455
|
|
|
424
|
-
The library supports
|
|
425
|
-
with python 2.7 as well.
|
|
456
|
+
The library supports Python 3.9 and higher.
|
|
426
457
|
|
|
427
458
|
Object attributes that start with ``_`` or ``func_`` are disallowed by default.
|
|
428
459
|
If you really need that (BE CAREFUL!), then modify the module global
|
|
@@ -1,39 +1,28 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: simpleeval
|
|
3
|
-
Version: 0.9.13
|
|
4
|
-
Summary: A simple, safe single expression evaluator library.
|
|
5
|
-
Home-page: https://github.com/danthedeckie/simpleeval
|
|
6
|
-
Author: Daniel Fairhead
|
|
7
|
-
Author-email: danthedeckie@gmail.com
|
|
8
|
-
Keywords: eval,simple,expression,parse,ast
|
|
9
|
-
Classifier: Development Status :: 4 - Beta
|
|
10
|
-
Classifier: Intended Audience :: Developers
|
|
11
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
13
|
-
Classifier: Programming Language :: Python
|
|
14
|
-
Description-Content-Type: text/x-rst
|
|
15
|
-
License-File: LICENCE
|
|
16
|
-
|
|
17
1
|
simpleeval (Simple Eval)
|
|
18
2
|
========================
|
|
19
3
|
|
|
20
|
-
.. image:: https://github.com/danthedeckie/simpleeval/actions/workflows/ci.yml/badge.svg?branch=gh-actions-build
|
|
4
|
+
.. |build-status| image:: https://github.com/danthedeckie/simpleeval/actions/workflows/ci.yml/badge.svg?branch=gh-actions-build
|
|
21
5
|
:target: https://github.com/danthedeckie/simpleeval/actions/
|
|
22
6
|
:alt: Build Status
|
|
23
7
|
|
|
24
|
-
.. image:: https://codecov.io/gh/danthedeckie/simpleeval/branch/master/graph/badge.svg?token=isRnN1yrca
|
|
8
|
+
.. |code-coverage| image:: https://codecov.io/gh/danthedeckie/simpleeval/branch/master/graph/badge.svg?token=isRnN1yrca
|
|
25
9
|
:target: https://codecov.io/gh/danthedeckie/simpleeval
|
|
26
|
-
:alt: Code Coverage
|
|
10
|
+
:alt: Code Coverage Status
|
|
27
11
|
|
|
28
|
-
.. image:: https://badge.fury.io/py/simpleeval.svg
|
|
12
|
+
.. |pypi-version| image:: https://badge.fury.io/py/simpleeval.svg
|
|
29
13
|
:target: https://badge.fury.io/py/simpleeval
|
|
30
14
|
:alt: PyPI Version
|
|
31
15
|
|
|
32
|
-
.. image:: https://img.shields.io/badge/
|
|
33
|
-
:
|
|
16
|
+
.. |python-versions| image:: https://img.shields.io/badge/python-3.9_%7C_3.10_%7C_3.11_%7C_3.12_%7C_3.13_%7C_PyPy3.9_%7C_PyPy3.10-blue
|
|
17
|
+
:alt: Static Badge
|
|
18
|
+
|
|
19
|
+
.. |pypi-monthly-downloads| image:: https://img.shields.io/pypi/dm/SimpleEval
|
|
20
|
+
:alt: PyPI - Downloads
|
|
21
|
+
|
|
22
|
+
.. |formatting-with-ruff| image:: https://img.shields.io/badge/-ruff-black?logo=lightning&logoColor=%2300ff00&link=https%3A%2F%2Fdocs.astral.sh%2Fruff%2F
|
|
23
|
+
:alt: Static Badge
|
|
34
24
|
|
|
35
|
-
|
|
36
|
-
:target: https://github.com/PyCQA/pylint
|
|
25
|
+
|build-status| |code-coverage| |pypi-version| |python-versions| |pypi-monthly-downloads| |formatting-with-ruff|
|
|
37
26
|
|
|
38
27
|
|
|
39
28
|
A single file library for easily adding evaluatable expressions into
|
|
@@ -173,7 +162,7 @@ The ``^`` operator is often mistaken for a exponent operator, not the bitwise
|
|
|
173
162
|
operation that it is in python, so if you want ``3 ^ 2`` to equal ``9``, you can
|
|
174
163
|
replace the operator like this:
|
|
175
164
|
|
|
176
|
-
.. code-block::
|
|
165
|
+
.. code-block:: pycon
|
|
177
166
|
|
|
178
167
|
>>> import ast
|
|
179
168
|
>>> from simpleeval import safe_power
|
|
@@ -192,13 +181,13 @@ Limited Power
|
|
|
192
181
|
Also note, the ``**`` operator has been locked down by default to have a
|
|
193
182
|
maximum input value of ``4000000``, which makes it somewhat harder to make
|
|
194
183
|
expressions which go on for ever. You can change this limit by changing the
|
|
195
|
-
``simpleeval.
|
|
184
|
+
``simpleeval.MAX_POWER`` module level value to whatever is an appropriate value
|
|
196
185
|
for you (and the hardware that you're running on) or if you want to completely
|
|
197
186
|
remove all limitations, you can set the ``s.operators[ast.Pow] = operator.pow``
|
|
198
187
|
or make your own function.
|
|
199
188
|
|
|
200
189
|
On my computer, ``9**9**5`` evaluates almost instantly, but ``9**9**6`` takes
|
|
201
|
-
over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``
|
|
190
|
+
over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``MAX_POWER``
|
|
202
191
|
limit, it throws a ``NumberTooHigh`` exception for you. (Otherwise it would go
|
|
203
192
|
on for hours, or until the computer runs out of memory)
|
|
204
193
|
|
|
@@ -216,7 +205,7 @@ If Expressions
|
|
|
216
205
|
|
|
217
206
|
You can use python style ``if x then y else z`` type expressions:
|
|
218
207
|
|
|
219
|
-
.. code-block::
|
|
208
|
+
.. code-block:: pycon
|
|
220
209
|
|
|
221
210
|
>>> simple_eval("'equal' if x == y else 'not equal'",
|
|
222
211
|
names={"x": 1, "y": 2})
|
|
@@ -224,7 +213,7 @@ You can use python style ``if x then y else z`` type expressions:
|
|
|
224
213
|
|
|
225
214
|
which, of course, can be nested:
|
|
226
215
|
|
|
227
|
-
.. code-block::
|
|
216
|
+
.. code-block:: pycon
|
|
228
217
|
|
|
229
218
|
>>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
|
|
230
219
|
'c'
|
|
@@ -235,7 +224,7 @@ Functions
|
|
|
235
224
|
|
|
236
225
|
You can define functions which you'd like the expresssions to have access to:
|
|
237
226
|
|
|
238
|
-
.. code-block::
|
|
227
|
+
.. code-block:: pycon
|
|
239
228
|
|
|
240
229
|
>>> simple_eval("double(21)", functions={"double": lambda x:x*2})
|
|
241
230
|
42
|
|
@@ -243,7 +232,7 @@ You can define functions which you'd like the expresssions to have access to:
|
|
|
243
232
|
You can define "real" functions to pass in rather than lambdas, of course too,
|
|
244
233
|
and even re-name them so that expressions can be shorter
|
|
245
234
|
|
|
246
|
-
.. code-block::
|
|
235
|
+
.. code-block:: pycon
|
|
247
236
|
|
|
248
237
|
>>> def double(x):
|
|
249
238
|
return x * 2
|
|
@@ -262,13 +251,13 @@ are provided in the ``DEFAULT_FUNCTIONS`` dict:
|
|
|
262
251
|
+----------------+--------------------------------------------------+
|
|
263
252
|
| ``float(x)`` | Convert ``x`` to a ``float``. |
|
|
264
253
|
+----------------+--------------------------------------------------+
|
|
265
|
-
| ``str(x)`` | Convert ``x`` to a ``str``
|
|
254
|
+
| ``str(x)`` | Convert ``x`` to a ``str`` |
|
|
266
255
|
+----------------+--------------------------------------------------+
|
|
267
256
|
|
|
268
257
|
If you want to provide a list of functions, but want to keep these as well,
|
|
269
258
|
then you can do a normal python ``.copy()`` & ``.update``:
|
|
270
259
|
|
|
271
|
-
.. code-block::
|
|
260
|
+
.. code-block:: pycon
|
|
272
261
|
|
|
273
262
|
>>> my_functions = simpleeval.DEFAULT_FUNCTIONS.copy()
|
|
274
263
|
>>> my_functions.update(
|
|
@@ -283,7 +272,7 @@ Names
|
|
|
283
272
|
Sometimes it's useful to have variables available, which in python terminology
|
|
284
273
|
are called 'names'.
|
|
285
274
|
|
|
286
|
-
.. code-block::
|
|
275
|
+
.. code-block:: pycon
|
|
287
276
|
|
|
288
277
|
>>> simple_eval("a + b", names={"a": 11, "b": 100})
|
|
289
278
|
111
|
|
@@ -291,7 +280,7 @@ are called 'names'.
|
|
|
291
280
|
You can also hand the handling of names over to a function, if you prefer:
|
|
292
281
|
|
|
293
282
|
|
|
294
|
-
.. code-block::
|
|
283
|
+
.. code-block:: pycon
|
|
295
284
|
|
|
296
285
|
>>> def name_handler(node):
|
|
297
286
|
return ord(node.id[0].lower(a))-96
|
|
@@ -300,9 +289,34 @@ You can also hand the handling of names over to a function, if you prefer:
|
|
|
300
289
|
3
|
|
301
290
|
|
|
302
291
|
That was a bit of a silly example, but you could use this for pulling values
|
|
303
|
-
from a database or file, say, or doing some kind of caching system.
|
|
292
|
+
from a database or file, looking up spreadsheet cells, say, or doing some kind of caching system.
|
|
293
|
+
|
|
294
|
+
In general, when it attempts to find a variable by name, if it cannot find one,
|
|
295
|
+
then it will look in the ``functions`` for a function of that name. If you want your name handler
|
|
296
|
+
function to return an "I can't find that name!", then it should raise a ``simpleeval.NameNotDefined``
|
|
297
|
+
exception. Eg:
|
|
298
|
+
|
|
299
|
+
.. code-block:: pycon
|
|
300
|
+
|
|
301
|
+
>>> def name_handler(node):
|
|
302
|
+
... if node.id[0] == 'a':
|
|
303
|
+
... return 21
|
|
304
|
+
... raise NameNotDefined(node.id[0], "Not found")
|
|
305
|
+
...
|
|
306
|
+
... simple_eval('a + a', names=name_handler, functions={"b": 100})
|
|
307
|
+
|
|
308
|
+
42
|
|
304
309
|
|
|
305
|
-
|
|
310
|
+
>>> simple_eval('a + b', names=name_handler, functions={'b': 100})
|
|
311
|
+
121
|
|
312
|
+
|
|
313
|
+
(Note: in that example, putting a number directly into the ``functions`` dict was done just to
|
|
314
|
+
show the fall-back to functions. Normally only put actual callables in there.)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
The two default names that are provided are ``True`` and ``False``. So if you want to provide
|
|
318
|
+
your own names, but want ``True`` and ``False`` to keep working, either provide them yourself,
|
|
319
|
+
or ``.copy()`` and ``.update`` the ``DEFAULT_NAMES``. (See functions example above).
|
|
306
320
|
|
|
307
321
|
Creating an Evaluator Class
|
|
308
322
|
---------------------------
|
|
@@ -312,7 +326,7 @@ evaluations, you can create a SimpleEval object, and pass it expressions each
|
|
|
312
326
|
time (which should be a bit quicker, and certainly more convenient for some use
|
|
313
327
|
cases):
|
|
314
328
|
|
|
315
|
-
.. code-block::
|
|
329
|
+
.. code-block:: pycon
|
|
316
330
|
|
|
317
331
|
>>> s = SimpleEval()
|
|
318
332
|
|
|
@@ -391,7 +405,7 @@ comprehensions.
|
|
|
391
405
|
Since the primary intention of this library is short expressions - an extra 'sweetener' is
|
|
392
406
|
enabled by default. You can access a dict (or similar's) keys using the .attr syntax:
|
|
393
407
|
|
|
394
|
-
.. code-block::
|
|
408
|
+
.. code-block:: pycon
|
|
395
409
|
|
|
396
410
|
>>> simple_eval("foo.bar", names={"foo": {"bar": 42}})
|
|
397
411
|
42
|
|
@@ -421,8 +435,7 @@ and then use ``EvalNoMethods`` instead of the ``SimpleEval`` class.
|
|
|
421
435
|
Other...
|
|
422
436
|
--------
|
|
423
437
|
|
|
424
|
-
The library supports
|
|
425
|
-
with python 2.7 as well.
|
|
438
|
+
The library supports Python 3.9 and higher.
|
|
426
439
|
|
|
427
440
|
Object attributes that start with ``_`` or ``func_`` are disallowed by default.
|
|
428
441
|
If you really need that (BE CAREFUL!), then modify the module global
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = simpleeval
|
|
3
|
-
version = 0.
|
|
3
|
+
version = 1.0.0
|
|
4
4
|
author = Daniel Fairhead
|
|
5
5
|
author_email = danthedeckie@gmail.com
|
|
6
6
|
description = A simple, safe single expression evaluator library.
|
|
7
7
|
keywords = eval, simple, expression, parse, ast
|
|
8
8
|
url = https://github.com/danthedeckie/simpleeval
|
|
9
|
+
license = MIT
|
|
9
10
|
long_description = file: README.rst
|
|
10
11
|
long_description_content_type = text/x-rst
|
|
11
12
|
classifiers =
|
|
12
|
-
Development Status ::
|
|
13
|
+
Development Status :: 5 - Production/Stable
|
|
13
14
|
Intended Audience :: Developers
|
|
14
15
|
License :: OSI Approved :: MIT License
|
|
15
16
|
Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -17,9 +18,7 @@ classifiers =
|
|
|
17
18
|
|
|
18
19
|
[options]
|
|
19
20
|
py_modules = simpleeval
|
|
20
|
-
|
|
21
|
-
[bdist_wheel]
|
|
22
|
-
universal = 1
|
|
21
|
+
python_requires = >=3.9
|
|
23
22
|
|
|
24
23
|
[pycodestyle]
|
|
25
24
|
max_line_length = 99
|
|
@@ -1,23 +1,46 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: simpleeval
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A simple, safe single expression evaluator library.
|
|
5
|
+
Home-page: https://github.com/danthedeckie/simpleeval
|
|
6
|
+
Author: Daniel Fairhead
|
|
7
|
+
Author-email: danthedeckie@gmail.com
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: eval,simple,expression,parse,ast
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
|
+
Classifier: Programming Language :: Python
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
|
+
Description-Content-Type: text/x-rst
|
|
17
|
+
License-File: LICENCE
|
|
18
|
+
|
|
1
19
|
simpleeval (Simple Eval)
|
|
2
20
|
========================
|
|
3
21
|
|
|
4
|
-
.. image:: https://github.com/danthedeckie/simpleeval/actions/workflows/ci.yml/badge.svg?branch=gh-actions-build
|
|
22
|
+
.. |build-status| image:: https://github.com/danthedeckie/simpleeval/actions/workflows/ci.yml/badge.svg?branch=gh-actions-build
|
|
5
23
|
:target: https://github.com/danthedeckie/simpleeval/actions/
|
|
6
24
|
:alt: Build Status
|
|
7
25
|
|
|
8
|
-
.. image:: https://codecov.io/gh/danthedeckie/simpleeval/branch/master/graph/badge.svg?token=isRnN1yrca
|
|
26
|
+
.. |code-coverage| image:: https://codecov.io/gh/danthedeckie/simpleeval/branch/master/graph/badge.svg?token=isRnN1yrca
|
|
9
27
|
:target: https://codecov.io/gh/danthedeckie/simpleeval
|
|
10
|
-
:alt: Code Coverage
|
|
28
|
+
:alt: Code Coverage Status
|
|
11
29
|
|
|
12
|
-
.. image:: https://badge.fury.io/py/simpleeval.svg
|
|
30
|
+
.. |pypi-version| image:: https://badge.fury.io/py/simpleeval.svg
|
|
13
31
|
:target: https://badge.fury.io/py/simpleeval
|
|
14
32
|
:alt: PyPI Version
|
|
15
33
|
|
|
16
|
-
.. image:: https://img.shields.io/badge/
|
|
17
|
-
:
|
|
34
|
+
.. |python-versions| image:: https://img.shields.io/badge/python-3.9_%7C_3.10_%7C_3.11_%7C_3.12_%7C_3.13_%7C_PyPy3.9_%7C_PyPy3.10-blue
|
|
35
|
+
:alt: Static Badge
|
|
36
|
+
|
|
37
|
+
.. |pypi-monthly-downloads| image:: https://img.shields.io/pypi/dm/SimpleEval
|
|
38
|
+
:alt: PyPI - Downloads
|
|
39
|
+
|
|
40
|
+
.. |formatting-with-ruff| image:: https://img.shields.io/badge/-ruff-black?logo=lightning&logoColor=%2300ff00&link=https%3A%2F%2Fdocs.astral.sh%2Fruff%2F
|
|
41
|
+
:alt: Static Badge
|
|
18
42
|
|
|
19
|
-
|
|
20
|
-
:target: https://github.com/PyCQA/pylint
|
|
43
|
+
|build-status| |code-coverage| |pypi-version| |python-versions| |pypi-monthly-downloads| |formatting-with-ruff|
|
|
21
44
|
|
|
22
45
|
|
|
23
46
|
A single file library for easily adding evaluatable expressions into
|
|
@@ -157,7 +180,7 @@ The ``^`` operator is often mistaken for a exponent operator, not the bitwise
|
|
|
157
180
|
operation that it is in python, so if you want ``3 ^ 2`` to equal ``9``, you can
|
|
158
181
|
replace the operator like this:
|
|
159
182
|
|
|
160
|
-
.. code-block::
|
|
183
|
+
.. code-block:: pycon
|
|
161
184
|
|
|
162
185
|
>>> import ast
|
|
163
186
|
>>> from simpleeval import safe_power
|
|
@@ -176,13 +199,13 @@ Limited Power
|
|
|
176
199
|
Also note, the ``**`` operator has been locked down by default to have a
|
|
177
200
|
maximum input value of ``4000000``, which makes it somewhat harder to make
|
|
178
201
|
expressions which go on for ever. You can change this limit by changing the
|
|
179
|
-
``simpleeval.
|
|
202
|
+
``simpleeval.MAX_POWER`` module level value to whatever is an appropriate value
|
|
180
203
|
for you (and the hardware that you're running on) or if you want to completely
|
|
181
204
|
remove all limitations, you can set the ``s.operators[ast.Pow] = operator.pow``
|
|
182
205
|
or make your own function.
|
|
183
206
|
|
|
184
207
|
On my computer, ``9**9**5`` evaluates almost instantly, but ``9**9**6`` takes
|
|
185
|
-
over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``
|
|
208
|
+
over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``MAX_POWER``
|
|
186
209
|
limit, it throws a ``NumberTooHigh`` exception for you. (Otherwise it would go
|
|
187
210
|
on for hours, or until the computer runs out of memory)
|
|
188
211
|
|
|
@@ -200,7 +223,7 @@ If Expressions
|
|
|
200
223
|
|
|
201
224
|
You can use python style ``if x then y else z`` type expressions:
|
|
202
225
|
|
|
203
|
-
.. code-block::
|
|
226
|
+
.. code-block:: pycon
|
|
204
227
|
|
|
205
228
|
>>> simple_eval("'equal' if x == y else 'not equal'",
|
|
206
229
|
names={"x": 1, "y": 2})
|
|
@@ -208,7 +231,7 @@ You can use python style ``if x then y else z`` type expressions:
|
|
|
208
231
|
|
|
209
232
|
which, of course, can be nested:
|
|
210
233
|
|
|
211
|
-
.. code-block::
|
|
234
|
+
.. code-block:: pycon
|
|
212
235
|
|
|
213
236
|
>>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
|
|
214
237
|
'c'
|
|
@@ -219,7 +242,7 @@ Functions
|
|
|
219
242
|
|
|
220
243
|
You can define functions which you'd like the expresssions to have access to:
|
|
221
244
|
|
|
222
|
-
.. code-block::
|
|
245
|
+
.. code-block:: pycon
|
|
223
246
|
|
|
224
247
|
>>> simple_eval("double(21)", functions={"double": lambda x:x*2})
|
|
225
248
|
42
|
|
@@ -227,7 +250,7 @@ You can define functions which you'd like the expresssions to have access to:
|
|
|
227
250
|
You can define "real" functions to pass in rather than lambdas, of course too,
|
|
228
251
|
and even re-name them so that expressions can be shorter
|
|
229
252
|
|
|
230
|
-
.. code-block::
|
|
253
|
+
.. code-block:: pycon
|
|
231
254
|
|
|
232
255
|
>>> def double(x):
|
|
233
256
|
return x * 2
|
|
@@ -246,13 +269,13 @@ are provided in the ``DEFAULT_FUNCTIONS`` dict:
|
|
|
246
269
|
+----------------+--------------------------------------------------+
|
|
247
270
|
| ``float(x)`` | Convert ``x`` to a ``float``. |
|
|
248
271
|
+----------------+--------------------------------------------------+
|
|
249
|
-
| ``str(x)`` | Convert ``x`` to a ``str``
|
|
272
|
+
| ``str(x)`` | Convert ``x`` to a ``str`` |
|
|
250
273
|
+----------------+--------------------------------------------------+
|
|
251
274
|
|
|
252
275
|
If you want to provide a list of functions, but want to keep these as well,
|
|
253
276
|
then you can do a normal python ``.copy()`` & ``.update``:
|
|
254
277
|
|
|
255
|
-
.. code-block::
|
|
278
|
+
.. code-block:: pycon
|
|
256
279
|
|
|
257
280
|
>>> my_functions = simpleeval.DEFAULT_FUNCTIONS.copy()
|
|
258
281
|
>>> my_functions.update(
|
|
@@ -267,7 +290,7 @@ Names
|
|
|
267
290
|
Sometimes it's useful to have variables available, which in python terminology
|
|
268
291
|
are called 'names'.
|
|
269
292
|
|
|
270
|
-
.. code-block::
|
|
293
|
+
.. code-block:: pycon
|
|
271
294
|
|
|
272
295
|
>>> simple_eval("a + b", names={"a": 11, "b": 100})
|
|
273
296
|
111
|
|
@@ -275,7 +298,7 @@ are called 'names'.
|
|
|
275
298
|
You can also hand the handling of names over to a function, if you prefer:
|
|
276
299
|
|
|
277
300
|
|
|
278
|
-
.. code-block::
|
|
301
|
+
.. code-block:: pycon
|
|
279
302
|
|
|
280
303
|
>>> def name_handler(node):
|
|
281
304
|
return ord(node.id[0].lower(a))-96
|
|
@@ -284,9 +307,34 @@ You can also hand the handling of names over to a function, if you prefer:
|
|
|
284
307
|
3
|
|
285
308
|
|
|
286
309
|
That was a bit of a silly example, but you could use this for pulling values
|
|
287
|
-
from a database or file, say, or doing some kind of caching system.
|
|
310
|
+
from a database or file, looking up spreadsheet cells, say, or doing some kind of caching system.
|
|
311
|
+
|
|
312
|
+
In general, when it attempts to find a variable by name, if it cannot find one,
|
|
313
|
+
then it will look in the ``functions`` for a function of that name. If you want your name handler
|
|
314
|
+
function to return an "I can't find that name!", then it should raise a ``simpleeval.NameNotDefined``
|
|
315
|
+
exception. Eg:
|
|
316
|
+
|
|
317
|
+
.. code-block:: pycon
|
|
318
|
+
|
|
319
|
+
>>> def name_handler(node):
|
|
320
|
+
... if node.id[0] == 'a':
|
|
321
|
+
... return 21
|
|
322
|
+
... raise NameNotDefined(node.id[0], "Not found")
|
|
323
|
+
...
|
|
324
|
+
... simple_eval('a + a', names=name_handler, functions={"b": 100})
|
|
325
|
+
|
|
326
|
+
42
|
|
288
327
|
|
|
289
|
-
|
|
328
|
+
>>> simple_eval('a + b', names=name_handler, functions={'b': 100})
|
|
329
|
+
121
|
|
330
|
+
|
|
331
|
+
(Note: in that example, putting a number directly into the ``functions`` dict was done just to
|
|
332
|
+
show the fall-back to functions. Normally only put actual callables in there.)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
The two default names that are provided are ``True`` and ``False``. So if you want to provide
|
|
336
|
+
your own names, but want ``True`` and ``False`` to keep working, either provide them yourself,
|
|
337
|
+
or ``.copy()`` and ``.update`` the ``DEFAULT_NAMES``. (See functions example above).
|
|
290
338
|
|
|
291
339
|
Creating an Evaluator Class
|
|
292
340
|
---------------------------
|
|
@@ -296,7 +344,7 @@ evaluations, you can create a SimpleEval object, and pass it expressions each
|
|
|
296
344
|
time (which should be a bit quicker, and certainly more convenient for some use
|
|
297
345
|
cases):
|
|
298
346
|
|
|
299
|
-
.. code-block::
|
|
347
|
+
.. code-block:: pycon
|
|
300
348
|
|
|
301
349
|
>>> s = SimpleEval()
|
|
302
350
|
|
|
@@ -375,7 +423,7 @@ comprehensions.
|
|
|
375
423
|
Since the primary intention of this library is short expressions - an extra 'sweetener' is
|
|
376
424
|
enabled by default. You can access a dict (or similar's) keys using the .attr syntax:
|
|
377
425
|
|
|
378
|
-
.. code-block::
|
|
426
|
+
.. code-block:: pycon
|
|
379
427
|
|
|
380
428
|
>>> simple_eval("foo.bar", names={"foo": {"bar": 42}})
|
|
381
429
|
42
|
|
@@ -405,8 +453,7 @@ and then use ``EvalNoMethods`` instead of the ``SimpleEval`` class.
|
|
|
405
453
|
Other...
|
|
406
454
|
--------
|
|
407
455
|
|
|
408
|
-
The library supports
|
|
409
|
-
with python 2.7 as well.
|
|
456
|
+
The library supports Python 3.9 and higher.
|
|
410
457
|
|
|
411
458
|
Object attributes that start with ``_`` or ``func_`` are disallowed by default.
|
|
412
459
|
If you really need that (BE CAREFUL!), then modify the module global
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
|
-
SimpleEval - (C) 2013-
|
|
2
|
+
SimpleEval - (C) 2013-2024 Daniel Fairhead
|
|
3
3
|
-------------------------------------
|
|
4
4
|
|
|
5
5
|
An short, easy to use, safe and reasonably extensible expression evaluator.
|
|
@@ -57,6 +57,12 @@ Contributors:
|
|
|
57
57
|
- bozokopic (Bozo Kopic) Memory leak fix
|
|
58
58
|
- daxamin (Dax Amin) Better error for attempting to eval empty string
|
|
59
59
|
- smurfix (Matthias Urlichs) Allow clearing functions / operators / etc completely
|
|
60
|
+
- koenigsley (Mikhail Yeremeyev) documentation typos correction.
|
|
61
|
+
- kurtmckee (Kurt McKee) Infrastructure updates
|
|
62
|
+
- edgarrmondragon (Edgar Ramírez-Mondragón) Address Python 3.12+ deprecation warnings
|
|
63
|
+
- cedk (Cédric Krier) <ced@b2ck.com> Allow running tests with Werror
|
|
64
|
+
- decorator-factory <decorator-factory@protonmail.com> More security fixes
|
|
65
|
+
- lkruitwagen (Lucas Kruitwagen) Adding support for dict comprehensions
|
|
60
66
|
|
|
61
67
|
-------------------------------------
|
|
62
68
|
Basic Usage:
|
|
@@ -102,9 +108,6 @@ import sys
|
|
|
102
108
|
import warnings
|
|
103
109
|
from random import random
|
|
104
110
|
|
|
105
|
-
PYTHON3 = sys.version_info[0] == 3
|
|
106
|
-
PYTHON35 = PYTHON3 and sys.version_info > (3, 5)
|
|
107
|
-
|
|
108
111
|
########################################
|
|
109
112
|
# Module wide 'globals'
|
|
110
113
|
|
|
@@ -114,7 +117,16 @@ MAX_POWER = 4000000 # highest exponent
|
|
|
114
117
|
MAX_SHIFT = 10000 # highest << or >> (lshift / rshift)
|
|
115
118
|
MAX_SHIFT_BASE = int(sys.float_info.max) # highest on left side of << or >>
|
|
116
119
|
DISALLOW_PREFIXES = ["_", "func_"]
|
|
117
|
-
DISALLOW_METHODS = [
|
|
120
|
+
DISALLOW_METHODS = [
|
|
121
|
+
"format",
|
|
122
|
+
"format_map",
|
|
123
|
+
"mro",
|
|
124
|
+
"tb_frame",
|
|
125
|
+
"gi_frame",
|
|
126
|
+
"ag_frame",
|
|
127
|
+
"cr_frame",
|
|
128
|
+
"exec",
|
|
129
|
+
]
|
|
118
130
|
|
|
119
131
|
# Disallow functions:
|
|
120
132
|
# This, strictly speaking, is not necessary. These /should/ never be accessable anyway,
|
|
@@ -123,7 +135,7 @@ DISALLOW_METHODS = ["format", "format_map", "mro"]
|
|
|
123
135
|
# their functionality is required, then please wrap them up in a safe container. And think
|
|
124
136
|
# very hard about it first. And don't say I didn't warn you.
|
|
125
137
|
# builtins is a dict in python >3.6 but a module before
|
|
126
|
-
DISALLOW_FUNCTIONS = {type, isinstance, eval, getattr, setattr, repr, compile, open}
|
|
138
|
+
DISALLOW_FUNCTIONS = {type, isinstance, eval, getattr, setattr, repr, compile, open, exec}
|
|
127
139
|
if hasattr(__builtins__, "help") or (
|
|
128
140
|
hasattr(__builtins__, "__contains__") and "help" in __builtins__ # type: ignore
|
|
129
141
|
):
|
|
@@ -131,11 +143,6 @@ if hasattr(__builtins__, "help") or (
|
|
|
131
143
|
DISALLOW_FUNCTIONS.add(help)
|
|
132
144
|
|
|
133
145
|
|
|
134
|
-
if PYTHON3:
|
|
135
|
-
# exec is not a function in Python2...
|
|
136
|
-
exec("DISALLOW_FUNCTIONS.add(exec)") # pylint: disable=exec-used
|
|
137
|
-
|
|
138
|
-
|
|
139
146
|
########################################
|
|
140
147
|
# Exceptions:
|
|
141
148
|
|
|
@@ -316,8 +323,7 @@ DEFAULT_FUNCTIONS = {
|
|
|
316
323
|
"randint": random_int,
|
|
317
324
|
"int": int,
|
|
318
325
|
"float": float,
|
|
319
|
-
|
|
320
|
-
"str": str if PYTHON3 else unicode, # type: ignore
|
|
326
|
+
"str": str,
|
|
321
327
|
}
|
|
322
328
|
|
|
323
329
|
DEFAULT_NAMES = {"True": True, "False": False, "None": None}
|
|
@@ -359,8 +365,6 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
359
365
|
ast.Assign: self._eval_assign,
|
|
360
366
|
ast.AugAssign: self._eval_aug_assign,
|
|
361
367
|
ast.Import: self._eval_import,
|
|
362
|
-
ast.Num: self._eval_num,
|
|
363
|
-
ast.Str: self._eval_str,
|
|
364
368
|
ast.Name: self._eval_name,
|
|
365
369
|
ast.UnaryOp: self._eval_unaryop,
|
|
366
370
|
ast.BinOp: self._eval_binop,
|
|
@@ -373,22 +377,23 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
373
377
|
ast.Attribute: self._eval_attribute,
|
|
374
378
|
ast.Index: self._eval_index,
|
|
375
379
|
ast.Slice: self._eval_slice,
|
|
380
|
+
ast.JoinedStr: self._eval_joinedstr,
|
|
381
|
+
ast.FormattedValue: self._eval_formattedvalue,
|
|
382
|
+
ast.Constant: self._eval_constant,
|
|
376
383
|
}
|
|
377
384
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
385
|
+
with warnings.catch_warnings():
|
|
386
|
+
warnings.simplefilter("ignore")
|
|
387
|
+
# py3.12 deprecated ast.Num, ast.Str, ast.NameConstant
|
|
388
|
+
# https://docs.python.org/3.12/whatsnew/3.12.html#deprecated
|
|
389
|
+
if Num := getattr(ast, "Num"):
|
|
390
|
+
self.nodes[Num] = self._eval_num
|
|
381
391
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
self.nodes[ast.JoinedStr] = self._eval_joinedstr # f-string
|
|
385
|
-
self.nodes[
|
|
386
|
-
ast.FormattedValue
|
|
387
|
-
] = self._eval_formattedvalue # formatted value in f-string
|
|
392
|
+
if Str := getattr(ast, "Str"):
|
|
393
|
+
self.nodes[Str] = self._eval_str
|
|
388
394
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
self.nodes[ast.Constant] = self._eval_constant
|
|
395
|
+
if NameConstant := getattr(ast, "NameConstant"):
|
|
396
|
+
self.nodes[NameConstant] = self._eval_constant
|
|
392
397
|
|
|
393
398
|
# Defaults:
|
|
394
399
|
|
|
@@ -466,8 +471,9 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
466
471
|
def _eval_str(node):
|
|
467
472
|
if len(node.s) > MAX_STRING_LENGTH:
|
|
468
473
|
raise IterableTooLong(
|
|
469
|
-
"String Literal in statement is too long!"
|
|
470
|
-
|
|
474
|
+
"String Literal in statement is too long! ({0}, when {1} is max)".format(
|
|
475
|
+
len(node.s), MAX_STRING_LENGTH
|
|
476
|
+
)
|
|
471
477
|
)
|
|
472
478
|
return node.s
|
|
473
479
|
|
|
@@ -475,8 +481,9 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
475
481
|
def _eval_constant(node):
|
|
476
482
|
if hasattr(node.value, "__len__") and len(node.value) > MAX_STRING_LENGTH:
|
|
477
483
|
raise IterableTooLong(
|
|
478
|
-
"Literal in statement is too long!"
|
|
479
|
-
|
|
484
|
+
"Literal in statement is too long! ({0}, when {1} is max)".format(
|
|
485
|
+
len(node.value), MAX_STRING_LENGTH
|
|
486
|
+
)
|
|
480
487
|
)
|
|
481
488
|
return node.value
|
|
482
489
|
|
|
@@ -547,24 +554,30 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
|
|
|
547
554
|
try:
|
|
548
555
|
# This happens at least for slicing
|
|
549
556
|
# This is a safe thing to do because it is impossible
|
|
550
|
-
# that there is a true
|
|
557
|
+
# that there is a true expression assigning to none
|
|
551
558
|
# (the compiler rejects it, so you can't even
|
|
552
559
|
# pass that to ast.parse)
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
560
|
+
return self.names[node.id]
|
|
561
|
+
|
|
562
|
+
except (TypeError, KeyError):
|
|
563
|
+
pass
|
|
564
|
+
|
|
565
|
+
if callable(self.names):
|
|
566
|
+
try:
|
|
556
567
|
return self.names(node)
|
|
568
|
+
except NameNotDefined:
|
|
569
|
+
pass
|
|
570
|
+
elif not hasattr(self.names, "__getitem__"):
|
|
557
571
|
raise InvalidExpression(
|
|
558
572
|
'Trying to use name (variable) "{0}"'
|
|
559
573
|
' when no "names" defined for'
|
|
560
574
|
" evaluator".format(node.id)
|
|
561
575
|
)
|
|
562
576
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
return self.functions[node.id]
|
|
577
|
+
if node.id in self.functions:
|
|
578
|
+
return self.functions[node.id]
|
|
566
579
|
|
|
567
|
-
|
|
580
|
+
raise NameNotDefined(node.id, self.expr)
|
|
568
581
|
|
|
569
582
|
def _eval_subscript(self, node):
|
|
570
583
|
container = self._eval(node.value)
|
|
@@ -655,6 +668,7 @@ class EvalWithCompoundTypes(SimpleEval):
|
|
|
655
668
|
ast.Set: self._eval_set,
|
|
656
669
|
ast.ListComp: self._eval_comprehension,
|
|
657
670
|
ast.GeneratorExp: self._eval_comprehension,
|
|
671
|
+
ast.DictComp: self._eval_comprehension,
|
|
658
672
|
}
|
|
659
673
|
)
|
|
660
674
|
|
|
@@ -667,7 +681,7 @@ class EvalWithCompoundTypes(SimpleEval):
|
|
|
667
681
|
result = {}
|
|
668
682
|
|
|
669
683
|
for key, value in zip(node.keys, node.values):
|
|
670
|
-
if
|
|
684
|
+
if key is None:
|
|
671
685
|
# "{**x}" gets parsed as a key-value pair of (None, Name(x))
|
|
672
686
|
result.update(self._eval(value))
|
|
673
687
|
else:
|
|
@@ -679,7 +693,7 @@ class EvalWithCompoundTypes(SimpleEval):
|
|
|
679
693
|
result = []
|
|
680
694
|
|
|
681
695
|
for item in node.elts:
|
|
682
|
-
if
|
|
696
|
+
if isinstance(item, ast.Starred):
|
|
683
697
|
result.extend(self._eval(item.value))
|
|
684
698
|
else:
|
|
685
699
|
result.append(self._eval(item))
|
|
@@ -693,7 +707,10 @@ class EvalWithCompoundTypes(SimpleEval):
|
|
|
693
707
|
return set(self._eval(x) for x in node.elts)
|
|
694
708
|
|
|
695
709
|
def _eval_comprehension(self, node):
|
|
696
|
-
|
|
710
|
+
if isinstance(node, ast.DictComp):
|
|
711
|
+
to_return = {}
|
|
712
|
+
else:
|
|
713
|
+
to_return = []
|
|
697
714
|
|
|
698
715
|
extra_names = {}
|
|
699
716
|
|
|
@@ -732,7 +749,10 @@ class EvalWithCompoundTypes(SimpleEval):
|
|
|
732
749
|
if len(node.generators) > gi + 1:
|
|
733
750
|
do_generator(gi + 1)
|
|
734
751
|
else:
|
|
735
|
-
to_return
|
|
752
|
+
if isinstance(to_return, dict):
|
|
753
|
+
to_return[self._eval(node.key)] = self._eval(node.value)
|
|
754
|
+
elif isinstance(to_return, list):
|
|
755
|
+
to_return.append(self._eval(node.elt))
|
|
736
756
|
|
|
737
757
|
try:
|
|
738
758
|
do_generator()
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
# pylint: disable=too-many-public-methods, missing-docstring, eval-used, too-many-lines, no-self-use, disallowed-name, unspecified-encoding
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
Unit tests for simpleeval.
|
|
5
|
+
--------------------------
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Most of this stuff is pretty basic.
|
|
8
8
|
|
|
9
9
|
"""
|
|
10
|
+
|
|
10
11
|
import ast
|
|
11
12
|
import gc
|
|
12
13
|
import operator
|
|
@@ -209,6 +210,7 @@ class TestEvaluator(DRYTest):
|
|
|
209
210
|
def test_only_evalutate_first_statement(self):
|
|
210
211
|
# it only evaluates the first statement:
|
|
211
212
|
with warnings.catch_warnings(record=True) as ws:
|
|
213
|
+
warnings.simplefilter("always")
|
|
212
214
|
self.t("11; x = 21; x + x", 11)
|
|
213
215
|
self.assertIsInstance(ws[0].message, simpleeval.MultipleExpressions)
|
|
214
216
|
|
|
@@ -429,9 +431,8 @@ class TestTryingToBreakOut(DRYTest):
|
|
|
429
431
|
|
|
430
432
|
def test_encode_bignums(self):
|
|
431
433
|
# thanks gk
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
self.t('(1).from_bytes(("123123123123123123123123").encode()*999999, "big")', 0)
|
|
434
|
+
with self.assertRaises(simpleeval.IterableTooLong):
|
|
435
|
+
self.t('(1).from_bytes(("123123123123123123123123").encode()*999999, "big")', 0)
|
|
435
436
|
|
|
436
437
|
def test_string_length(self):
|
|
437
438
|
with self.assertRaises(simpleeval.IterableTooLong):
|
|
@@ -612,7 +613,6 @@ class TestCompoundTypes(DRYTest):
|
|
|
612
613
|
self.t('{"a": 24}.get("b", 11)', 11)
|
|
613
614
|
self.t('"a" in {"a": 24}', True)
|
|
614
615
|
|
|
615
|
-
@unittest.skipIf(not simpleeval.PYTHON35, "feature not supported")
|
|
616
616
|
def test_dict_star_expression(self):
|
|
617
617
|
self.s.names["x"] = {"a": 1, "b": 2}
|
|
618
618
|
self.t('{"a": 0, **x, "c": 3}', {"a": 1, "b": 2, "c": 3})
|
|
@@ -621,7 +621,6 @@ class TestCompoundTypes(DRYTest):
|
|
|
621
621
|
self.s.names["y"] = {"x": 1, "y": 2}
|
|
622
622
|
self.t('{"a": 0, **x, **y, "c": 3}', {"a": 1, "b": 2, "c": 3, "x": 1, "y": 2})
|
|
623
623
|
|
|
624
|
-
@unittest.skipIf(not simpleeval.PYTHON35, "feature not supported")
|
|
625
624
|
def test_dict_invalid_star_expression(self):
|
|
626
625
|
self.s.names["x"] = {"a": 1, "b": 2}
|
|
627
626
|
self.s.names["y"] = {"x": 1, "y": 2}
|
|
@@ -660,12 +659,10 @@ class TestCompoundTypes(DRYTest):
|
|
|
660
659
|
|
|
661
660
|
self.t('"b" in ["a","b"]', True)
|
|
662
661
|
|
|
663
|
-
@unittest.skipIf(not simpleeval.PYTHON3, "feature not supported")
|
|
664
662
|
def test_list_star_expression(self):
|
|
665
663
|
self.s.names["x"] = [1, 2, 3]
|
|
666
664
|
self.t('["a", *x, "b"]', ["a", 1, 2, 3, "b"])
|
|
667
665
|
|
|
668
|
-
@unittest.skipIf(not simpleeval.PYTHON3, "feature not supported")
|
|
669
666
|
def test_list_invalid_star_expression(self):
|
|
670
667
|
self.s.names["x"] = [1, 2, 3]
|
|
671
668
|
self.s.names["y"] = 42
|
|
@@ -732,6 +729,24 @@ class TestComprehensions(DRYTest):
|
|
|
732
729
|
def test_nested_unpack(self):
|
|
733
730
|
self.t("[a+b+c for a, (b, c) in ((1,(1,1)),(3,(2,2)))]", [3, 7])
|
|
734
731
|
|
|
732
|
+
def test_dictcomp_basic(self):
|
|
733
|
+
self.t("{a:a + 1 for a in [1,2,3]}", {1: 2, 2: 3, 3: 4})
|
|
734
|
+
|
|
735
|
+
def test_dictcomp_with_self_reference(self):
|
|
736
|
+
self.t("{a:a + a for a in [1,2,3]}", {1: 2, 2: 4, 3: 6})
|
|
737
|
+
|
|
738
|
+
def test_dictcomp_with_if(self):
|
|
739
|
+
self.t("{a:a for a in [1,2,3,4,5] if a <= 3}", {1: 1, 2: 2, 3: 3})
|
|
740
|
+
|
|
741
|
+
def test_dictcomp_with_multiple_if(self):
|
|
742
|
+
self.t("{a:a for a in [1,2,3,4,5] if a <= 3 and a > 1 }", {2: 2, 3: 3})
|
|
743
|
+
|
|
744
|
+
def test_dictcomp_unpack(self):
|
|
745
|
+
self.t("{a:a+b for a,b in ((1,2),(3,4))}", {1: 3, 3: 7})
|
|
746
|
+
|
|
747
|
+
def test_dictcomp_nested_unpack(self):
|
|
748
|
+
self.t("{a:a+b+c for a, (b, c) in ((1,(1,1)),(3,(2,2)))}", {1: 3, 3: 7})
|
|
749
|
+
|
|
735
750
|
def test_other_places(self):
|
|
736
751
|
self.s.functions = {"sum": sum}
|
|
737
752
|
self.t("sum([a+1 for a in [1,2,3,4,5]])", 20)
|
|
@@ -795,6 +810,7 @@ class TestNames(DRYTest):
|
|
|
795
810
|
# or if you attempt to assign an unknown name to another
|
|
796
811
|
with self.assertRaises(NameNotDefined):
|
|
797
812
|
with warnings.catch_warnings(record=True) as ws:
|
|
813
|
+
warnings.simplefilter("always")
|
|
798
814
|
self.t("s += a", 21)
|
|
799
815
|
self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
|
|
800
816
|
|
|
@@ -820,6 +836,7 @@ class TestNames(DRYTest):
|
|
|
820
836
|
|
|
821
837
|
# however, you can't assign to those names:
|
|
822
838
|
with warnings.catch_warnings(record=True) as ws:
|
|
839
|
+
warnings.simplefilter("always")
|
|
823
840
|
self.t("a = 200", 200)
|
|
824
841
|
self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
|
|
825
842
|
|
|
@@ -830,6 +847,7 @@ class TestNames(DRYTest):
|
|
|
830
847
|
self.s.names["b"] = [0]
|
|
831
848
|
|
|
832
849
|
with warnings.catch_warnings(record=True) as ws:
|
|
850
|
+
warnings.simplefilter("always")
|
|
833
851
|
self.t("b[0] = 11", 11)
|
|
834
852
|
self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
|
|
835
853
|
|
|
@@ -853,6 +871,7 @@ class TestNames(DRYTest):
|
|
|
853
871
|
# you still can't assign though:
|
|
854
872
|
|
|
855
873
|
with warnings.catch_warnings(record=True) as ws:
|
|
874
|
+
warnings.simplefilter("always")
|
|
856
875
|
self.t("c['b'] = 99", 99)
|
|
857
876
|
self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
|
|
858
877
|
|
|
@@ -863,6 +882,7 @@ class TestNames(DRYTest):
|
|
|
863
882
|
self.s.names["c"]["c"] = {"c": 11}
|
|
864
883
|
|
|
865
884
|
with warnings.catch_warnings(record=True) as ws:
|
|
885
|
+
warnings.simplefilter("always")
|
|
866
886
|
self.t("c['c']['c'] = 21", 21)
|
|
867
887
|
self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
|
|
868
888
|
|
|
@@ -878,6 +898,7 @@ class TestNames(DRYTest):
|
|
|
878
898
|
self.t("a.b.c*2", 84)
|
|
879
899
|
|
|
880
900
|
with warnings.catch_warnings(record=True) as ws:
|
|
901
|
+
warnings.simplefilter("always")
|
|
881
902
|
self.t("a.b.c = 11", 11)
|
|
882
903
|
self.assertIsInstance(ws[0].message, simpleeval.AssignmentAttempted)
|
|
883
904
|
|
|
@@ -885,6 +906,7 @@ class TestNames(DRYTest):
|
|
|
885
906
|
|
|
886
907
|
# TODO: Wat?
|
|
887
908
|
with warnings.catch_warnings(record=True) as ws:
|
|
909
|
+
warnings.simplefilter("always")
|
|
888
910
|
self.t("a.d = 11", 11)
|
|
889
911
|
|
|
890
912
|
with self.assertRaises(KeyError):
|
|
@@ -965,6 +987,36 @@ class TestNames(DRYTest):
|
|
|
965
987
|
self.t("a", 1)
|
|
966
988
|
self.t("a + b", 3)
|
|
967
989
|
|
|
990
|
+
def test_name_handler_name_not_found(self):
|
|
991
|
+
def name_handler(node):
|
|
992
|
+
if node.id[0] == "a":
|
|
993
|
+
return 21
|
|
994
|
+
raise NameNotDefined(node.id[0], "not found")
|
|
995
|
+
|
|
996
|
+
self.s.names = name_handler
|
|
997
|
+
self.s.functions = {"b": lambda: 100}
|
|
998
|
+
self.t("a + a", 42)
|
|
999
|
+
|
|
1000
|
+
self.t("b()", 100)
|
|
1001
|
+
|
|
1002
|
+
with self.assertRaises(NameNotDefined):
|
|
1003
|
+
self.t("c", None)
|
|
1004
|
+
|
|
1005
|
+
def test_name_handler_raises_error(self):
|
|
1006
|
+
# What happens if our name-handler raises a different kind of error?
|
|
1007
|
+
# we want it to ripple up all the way...
|
|
1008
|
+
|
|
1009
|
+
def name_handler(_node):
|
|
1010
|
+
return {}["test"]
|
|
1011
|
+
|
|
1012
|
+
self.s.names = name_handler
|
|
1013
|
+
|
|
1014
|
+
# This should never be accessed:
|
|
1015
|
+
self.s.functions = {"c": 42}
|
|
1016
|
+
|
|
1017
|
+
with self.assertRaises(KeyError):
|
|
1018
|
+
self.t("c", None)
|
|
1019
|
+
|
|
968
1020
|
|
|
969
1021
|
class TestWhitespace(DRYTest):
|
|
970
1022
|
"""test that incorrect whitespace (preceding/trailing) doesn't matter."""
|
|
@@ -1200,10 +1252,7 @@ class TestShortCircuiting(DRYTest):
|
|
|
1200
1252
|
|
|
1201
1253
|
class TestDisallowedFunctions(DRYTest):
|
|
1202
1254
|
def test_functions_are_disallowed_at_init(self):
|
|
1203
|
-
DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, compile, open]
|
|
1204
|
-
if simpleeval.PYTHON3:
|
|
1205
|
-
# pylint: disable=exec-used
|
|
1206
|
-
exec("DISALLOWED.append(exec)") # exec is not a function in Python2...
|
|
1255
|
+
DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, compile, open, exec]
|
|
1207
1256
|
|
|
1208
1257
|
for f in simpleeval.DISALLOW_FUNCTIONS:
|
|
1209
1258
|
assert f in DISALLOWED
|
|
@@ -1213,11 +1262,7 @@ class TestDisallowedFunctions(DRYTest):
|
|
|
1213
1262
|
SimpleEval(functions={"foo": x})
|
|
1214
1263
|
|
|
1215
1264
|
def test_functions_are_disallowed_in_expressions(self):
|
|
1216
|
-
DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, compile, open]
|
|
1217
|
-
|
|
1218
|
-
if simpleeval.PYTHON3:
|
|
1219
|
-
# pylint: disable=exec-used
|
|
1220
|
-
exec("DISALLOWED.append(exec)") # exec is not a function in Python2...
|
|
1265
|
+
DISALLOWED = [type, isinstance, eval, getattr, setattr, help, repr, compile, open, exec]
|
|
1221
1266
|
|
|
1222
1267
|
for f in simpleeval.DISALLOW_FUNCTIONS:
|
|
1223
1268
|
assert f in DISALLOWED
|
|
@@ -1233,8 +1278,21 @@ class TestDisallowedFunctions(DRYTest):
|
|
|
1233
1278
|
|
|
1234
1279
|
simpleeval.DEFAULT_FUNCTIONS = DF.copy()
|
|
1235
1280
|
|
|
1281
|
+
def test_breakout_via_generator(self):
|
|
1282
|
+
# Thanks decorator-factory
|
|
1283
|
+
class Foo:
|
|
1284
|
+
def bar(self):
|
|
1285
|
+
yield "Hello, world!"
|
|
1286
|
+
|
|
1287
|
+
# Test the genertor does work - also adds the `yield` to codecov...
|
|
1288
|
+
assert list(Foo().bar()) == ["Hello, world!"]
|
|
1289
|
+
|
|
1290
|
+
evil = "foo.bar().gi_frame.f_globals['__builtins__'].exec('raise RuntimeError(\"Oh no\")')"
|
|
1291
|
+
|
|
1292
|
+
with self.assertRaises(FeatureNotAvailable):
|
|
1293
|
+
simple_eval(evil, names={"foo": Foo()})
|
|
1294
|
+
|
|
1236
1295
|
|
|
1237
|
-
@unittest.skipIf(simpleeval.PYTHON3 is not True, "Python2 fails - but it's not supported anyway.")
|
|
1238
1296
|
@unittest.skipIf(platform.python_implementation() == "PyPy", "GC set_debug not available in PyPy")
|
|
1239
1297
|
class TestReferenceCleanup(DRYTest):
|
|
1240
1298
|
"""Test cleanup without cyclic references"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|