simpleeval 0.9.12__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.12 → simpleeval-1.0.0}/.gitignore +1 -0
- {simpleeval-0.9.12 → simpleeval-1.0.0}/LICENCE +1 -1
- {simpleeval-0.9.12 → simpleeval-1.0.0}/Makefile +6 -5
- {simpleeval-0.9.12 → simpleeval-1.0.0}/PKG-INFO +113 -40
- {simpleeval-0.9.12 → simpleeval-1.0.0}/README.rst +109 -34
- {simpleeval-0.9.12 → simpleeval-1.0.0}/pyproject.toml +6 -1
- {simpleeval-0.9.12 → simpleeval-1.0.0}/setup.cfg +4 -5
- {simpleeval-0.9.12 → simpleeval-1.0.0}/simpleeval.egg-info/PKG-INFO +113 -40
- {simpleeval-0.9.12 → simpleeval-1.0.0}/simpleeval.py +168 -83
- {simpleeval-0.9.12 → simpleeval-1.0.0}/test_simpleeval.py +223 -37
- {simpleeval-0.9.12 → simpleeval-1.0.0}/MANIFEST.in +0 -0
- {simpleeval-0.9.12 → simpleeval-1.0.0}/setup.py +0 -0
- {simpleeval-0.9.12 → simpleeval-1.0.0}/simpleeval.egg-info/SOURCES.txt +0 -0
- {simpleeval-0.9.12 → simpleeval-1.0.0}/simpleeval.egg-info/dependency_links.txt +0 -0
- {simpleeval-0.9.12 → 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,9 +22,10 @@ coverage:
|
|
|
22
22
|
coverage run test_simpleeval.py
|
|
23
23
|
|
|
24
24
|
lint:
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
ruff check simpleeval.py test_simpleeval.py
|
|
26
|
+
ruff format --check simpleeval.py test_simpleeval.py
|
|
27
|
+
mypy simpleeval.py test_simpleeval.py
|
|
27
28
|
|
|
28
29
|
format:
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
ruff check --fix-only simpleeval.py test_simpleeval.py
|
|
31
|
+
ruff format simpleeval.py test_simpleeval.py
|
|
@@ -1,37 +1,49 @@
|
|
|
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:
|
|
8
|
+
License: MIT
|
|
9
9
|
Keywords: eval,simple,expression,parse,ast
|
|
10
|
-
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
11
|
Classifier: Intended Audience :: Developers
|
|
13
12
|
Classifier: License :: OSI Approved :: MIT License
|
|
14
13
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
15
14
|
Classifier: Programming Language :: Python
|
|
15
|
+
Requires-Python: >=3.9
|
|
16
16
|
Description-Content-Type: text/x-rst
|
|
17
17
|
License-File: LICENCE
|
|
18
18
|
|
|
19
19
|
simpleeval (Simple Eval)
|
|
20
20
|
========================
|
|
21
21
|
|
|
22
|
-
.. 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
|
|
23
23
|
:target: https://github.com/danthedeckie/simpleeval/actions/
|
|
24
24
|
:alt: Build Status
|
|
25
25
|
|
|
26
|
-
.. 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
|
|
27
27
|
:target: https://codecov.io/gh/danthedeckie/simpleeval
|
|
28
|
-
:alt: Code Coverage
|
|
28
|
+
:alt: Code Coverage Status
|
|
29
29
|
|
|
30
|
-
.. image:: https://badge.fury.io/py/simpleeval.svg
|
|
30
|
+
.. |pypi-version| image:: https://badge.fury.io/py/simpleeval.svg
|
|
31
31
|
:target: https://badge.fury.io/py/simpleeval
|
|
32
32
|
:alt: PyPI Version
|
|
33
33
|
|
|
34
|
-
|
|
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
|
|
42
|
+
|
|
43
|
+
|build-status| |code-coverage| |pypi-version| |python-versions| |pypi-monthly-downloads| |formatting-with-ruff|
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
A single file library for easily adding evaluatable expressions into
|
|
35
47
|
python projects. Say you want to allow a user to set an alarm volume, which
|
|
36
48
|
could depend on the time of day, alarm level, how many previous alarms had gone
|
|
37
49
|
off, and if there is music playing at the time.
|
|
@@ -39,8 +51,9 @@ off, and if there is music playing at the time.
|
|
|
39
51
|
Or if you want to allow simple formulae in a web application, but don't want to
|
|
40
52
|
give full eval() access, or don't want to run in javascript on the client side.
|
|
41
53
|
|
|
42
|
-
It's deliberately
|
|
43
|
-
even just a single file you can dump
|
|
54
|
+
It's deliberately trying to stay simple to use and not have millions of features,
|
|
55
|
+
pull it in from PyPI (pip or easy_install), or even just a single file you can dump
|
|
56
|
+
into a project.
|
|
44
57
|
|
|
45
58
|
Internally, it's using the amazing python ``ast`` module to parse the
|
|
46
59
|
expression, which allows very fine control of what is and isn't allowed. It
|
|
@@ -55,7 +68,7 @@ Operators_ section below)
|
|
|
55
68
|
|
|
56
69
|
You should be aware of this when deploying in a public setting.
|
|
57
70
|
|
|
58
|
-
The defaults are pretty locked down and basic, and it's
|
|
71
|
+
The defaults are pretty locked down and basic, and it's easy to add
|
|
59
72
|
whatever extra specific functionality you need (your own functions,
|
|
60
73
|
variable/name lookup, etc).
|
|
61
74
|
|
|
@@ -149,23 +162,36 @@ the defaults:
|
|
|
149
162
|
| | ``"spam" in "my breakfast"`` |
|
|
150
163
|
| | -> ``False`` |
|
|
151
164
|
+--------+------------------------------------+
|
|
165
|
+
| ``^`` | "bitwise exclusive OR" (xor) |
|
|
166
|
+
| | ``62 ^ 20`` -> ``42`` |
|
|
167
|
+
+--------+------------------------------------+
|
|
168
|
+
| ``|`` | "bitwise OR" |
|
|
169
|
+
| | ``8 | 34`` -> ``42`` |
|
|
170
|
+
+--------+------------------------------------+
|
|
171
|
+
| ``&`` | "bitwise AND" |
|
|
172
|
+
| | ``100 & 63`` -> ``36`` |
|
|
173
|
+
+--------+------------------------------------+
|
|
174
|
+
| ``~`` | "bitwise invert" |
|
|
175
|
+
| | ``~ -43`` -> ``42`` |
|
|
176
|
+
+--------+------------------------------------+
|
|
152
177
|
|
|
153
178
|
|
|
154
|
-
The ``^`` operator is
|
|
155
|
-
is
|
|
156
|
-
|
|
157
|
-
based evaluator explained below):
|
|
179
|
+
The ``^`` operator is often mistaken for a exponent operator, not the bitwise
|
|
180
|
+
operation that it is in python, so if you want ``3 ^ 2`` to equal ``9``, you can
|
|
181
|
+
replace the operator like this:
|
|
158
182
|
|
|
159
|
-
.. code-block::
|
|
183
|
+
.. code-block:: pycon
|
|
160
184
|
|
|
161
185
|
>>> import ast
|
|
162
|
-
>>> import
|
|
186
|
+
>>> from simpleeval import safe_power
|
|
163
187
|
|
|
164
188
|
>>> s = SimpleEval()
|
|
165
|
-
>>> s.operators[ast.BitXor] =
|
|
189
|
+
>>> s.operators[ast.BitXor] = safe_power
|
|
166
190
|
|
|
167
|
-
>>> s.eval("
|
|
168
|
-
|
|
191
|
+
>>> s.eval("3 ^ 2")
|
|
192
|
+
9
|
|
193
|
+
|
|
194
|
+
for example.
|
|
169
195
|
|
|
170
196
|
Limited Power
|
|
171
197
|
~~~~~~~~~~~~~
|
|
@@ -173,13 +199,13 @@ Limited Power
|
|
|
173
199
|
Also note, the ``**`` operator has been locked down by default to have a
|
|
174
200
|
maximum input value of ``4000000``, which makes it somewhat harder to make
|
|
175
201
|
expressions which go on for ever. You can change this limit by changing the
|
|
176
|
-
``simpleeval.
|
|
202
|
+
``simpleeval.MAX_POWER`` module level value to whatever is an appropriate value
|
|
177
203
|
for you (and the hardware that you're running on) or if you want to completely
|
|
178
204
|
remove all limitations, you can set the ``s.operators[ast.Pow] = operator.pow``
|
|
179
205
|
or make your own function.
|
|
180
206
|
|
|
181
207
|
On my computer, ``9**9**5`` evaluates almost instantly, but ``9**9**6`` takes
|
|
182
|
-
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``
|
|
183
209
|
limit, it throws a ``NumberTooHigh`` exception for you. (Otherwise it would go
|
|
184
210
|
on for hours, or until the computer runs out of memory)
|
|
185
211
|
|
|
@@ -197,7 +223,7 @@ If Expressions
|
|
|
197
223
|
|
|
198
224
|
You can use python style ``if x then y else z`` type expressions:
|
|
199
225
|
|
|
200
|
-
.. code-block::
|
|
226
|
+
.. code-block:: pycon
|
|
201
227
|
|
|
202
228
|
>>> simple_eval("'equal' if x == y else 'not equal'",
|
|
203
229
|
names={"x": 1, "y": 2})
|
|
@@ -205,7 +231,7 @@ You can use python style ``if x then y else z`` type expressions:
|
|
|
205
231
|
|
|
206
232
|
which, of course, can be nested:
|
|
207
233
|
|
|
208
|
-
.. code-block::
|
|
234
|
+
.. code-block:: pycon
|
|
209
235
|
|
|
210
236
|
>>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
|
|
211
237
|
'c'
|
|
@@ -216,7 +242,7 @@ Functions
|
|
|
216
242
|
|
|
217
243
|
You can define functions which you'd like the expresssions to have access to:
|
|
218
244
|
|
|
219
|
-
.. code-block::
|
|
245
|
+
.. code-block:: pycon
|
|
220
246
|
|
|
221
247
|
>>> simple_eval("double(21)", functions={"double": lambda x:x*2})
|
|
222
248
|
42
|
|
@@ -224,7 +250,7 @@ You can define functions which you'd like the expresssions to have access to:
|
|
|
224
250
|
You can define "real" functions to pass in rather than lambdas, of course too,
|
|
225
251
|
and even re-name them so that expressions can be shorter
|
|
226
252
|
|
|
227
|
-
.. code-block::
|
|
253
|
+
.. code-block:: pycon
|
|
228
254
|
|
|
229
255
|
>>> def double(x):
|
|
230
256
|
return x * 2
|
|
@@ -243,13 +269,13 @@ are provided in the ``DEFAULT_FUNCTIONS`` dict:
|
|
|
243
269
|
+----------------+--------------------------------------------------+
|
|
244
270
|
| ``float(x)`` | Convert ``x`` to a ``float``. |
|
|
245
271
|
+----------------+--------------------------------------------------+
|
|
246
|
-
| ``str(x)`` | Convert ``x`` to a ``str``
|
|
272
|
+
| ``str(x)`` | Convert ``x`` to a ``str`` |
|
|
247
273
|
+----------------+--------------------------------------------------+
|
|
248
274
|
|
|
249
275
|
If you want to provide a list of functions, but want to keep these as well,
|
|
250
276
|
then you can do a normal python ``.copy()`` & ``.update``:
|
|
251
277
|
|
|
252
|
-
.. code-block::
|
|
278
|
+
.. code-block:: pycon
|
|
253
279
|
|
|
254
280
|
>>> my_functions = simpleeval.DEFAULT_FUNCTIONS.copy()
|
|
255
281
|
>>> my_functions.update(
|
|
@@ -264,7 +290,7 @@ Names
|
|
|
264
290
|
Sometimes it's useful to have variables available, which in python terminology
|
|
265
291
|
are called 'names'.
|
|
266
292
|
|
|
267
|
-
.. code-block::
|
|
293
|
+
.. code-block:: pycon
|
|
268
294
|
|
|
269
295
|
>>> simple_eval("a + b", names={"a": 11, "b": 100})
|
|
270
296
|
111
|
|
@@ -272,7 +298,7 @@ are called 'names'.
|
|
|
272
298
|
You can also hand the handling of names over to a function, if you prefer:
|
|
273
299
|
|
|
274
300
|
|
|
275
|
-
.. code-block::
|
|
301
|
+
.. code-block:: pycon
|
|
276
302
|
|
|
277
303
|
>>> def name_handler(node):
|
|
278
304
|
return ord(node.id[0].lower(a))-96
|
|
@@ -281,9 +307,34 @@ You can also hand the handling of names over to a function, if you prefer:
|
|
|
281
307
|
3
|
|
282
308
|
|
|
283
309
|
That was a bit of a silly example, but you could use this for pulling values
|
|
284
|
-
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
|
|
285
318
|
|
|
286
|
-
|
|
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).
|
|
287
338
|
|
|
288
339
|
Creating an Evaluator Class
|
|
289
340
|
---------------------------
|
|
@@ -293,7 +344,7 @@ evaluations, you can create a SimpleEval object, and pass it expressions each
|
|
|
293
344
|
time (which should be a bit quicker, and certainly more convenient for some use
|
|
294
345
|
cases):
|
|
295
346
|
|
|
296
|
-
.. code-block::
|
|
347
|
+
.. code-block:: pycon
|
|
297
348
|
|
|
298
349
|
>>> s = SimpleEval()
|
|
299
350
|
|
|
@@ -305,6 +356,22 @@ cases):
|
|
|
305
356
|
|
|
306
357
|
# and so on...
|
|
307
358
|
|
|
359
|
+
One useful feature of using the ``SimpleEval`` object is that you can parse an expression
|
|
360
|
+
once, and then evaluate it mulitple times using different ``names``:
|
|
361
|
+
|
|
362
|
+
.. code-block:: python
|
|
363
|
+
|
|
364
|
+
# Set up & Cache the parse tree:
|
|
365
|
+
expression = "foo + bar"
|
|
366
|
+
parsed = s.parse(expression)
|
|
367
|
+
|
|
368
|
+
# evaluate the expression multiple times:
|
|
369
|
+
for names in [{"foo": 1, "bar": 10}, {"foo": 100, "bar": 42}]:
|
|
370
|
+
s.names = names
|
|
371
|
+
print(s.eval(expression, previously_parsed=parsed))
|
|
372
|
+
|
|
373
|
+
for instance. This may help with performance.
|
|
374
|
+
|
|
308
375
|
You can assign / edit the various options of the ``SimpleEval`` object if you
|
|
309
376
|
want to. Either assign them during creation (like the ``simple_eval``
|
|
310
377
|
function)
|
|
@@ -356,7 +423,7 @@ comprehensions.
|
|
|
356
423
|
Since the primary intention of this library is short expressions - an extra 'sweetener' is
|
|
357
424
|
enabled by default. You can access a dict (or similar's) keys using the .attr syntax:
|
|
358
425
|
|
|
359
|
-
.. code-block::
|
|
426
|
+
.. code-block:: pycon
|
|
360
427
|
|
|
361
428
|
>>> simple_eval("foo.bar", names={"foo": {"bar": 42}})
|
|
362
429
|
42
|
|
@@ -386,8 +453,7 @@ and then use ``EvalNoMethods`` instead of the ``SimpleEval`` class.
|
|
|
386
453
|
Other...
|
|
387
454
|
--------
|
|
388
455
|
|
|
389
|
-
The library supports
|
|
390
|
-
with python 2.7 as well.
|
|
456
|
+
The library supports Python 3.9 and higher.
|
|
391
457
|
|
|
392
458
|
Object attributes that start with ``_`` or ``func_`` are disallowed by default.
|
|
393
459
|
If you really need that (BE CAREFUL!), then modify the module global
|
|
@@ -418,11 +484,18 @@ Or to set the tests running on every file change:
|
|
|
418
484
|
|
|
419
485
|
(requires ``entr``)
|
|
420
486
|
|
|
487
|
+
I'm trying to keep the codebase relatively clean with Black, isort, pylint & mypy.
|
|
488
|
+
See::
|
|
489
|
+
|
|
490
|
+
$ make format
|
|
491
|
+
|
|
492
|
+
and::
|
|
493
|
+
|
|
494
|
+
$ make lint
|
|
495
|
+
|
|
421
496
|
BEWARE
|
|
422
497
|
------
|
|
423
498
|
|
|
424
|
-
I've done the best I can with this library - but there's no
|
|
499
|
+
I've done the best I can with this library - but there's no warranty, no guarantee, nada. A lot of
|
|
425
500
|
very clever people think the whole idea of trying to sandbox CPython is impossible. Read the code
|
|
426
501
|
yourself, and use it at your own risk.
|
|
427
|
-
|
|
428
|
-
|
|
@@ -1,19 +1,31 @@
|
|
|
1
1
|
simpleeval (Simple Eval)
|
|
2
2
|
========================
|
|
3
3
|
|
|
4
|
-
.. 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
|
|
5
5
|
:target: https://github.com/danthedeckie/simpleeval/actions/
|
|
6
6
|
:alt: Build Status
|
|
7
7
|
|
|
8
|
-
.. 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
|
|
9
9
|
:target: https://codecov.io/gh/danthedeckie/simpleeval
|
|
10
|
-
:alt: Code Coverage
|
|
10
|
+
:alt: Code Coverage Status
|
|
11
11
|
|
|
12
|
-
.. image:: https://badge.fury.io/py/simpleeval.svg
|
|
12
|
+
.. |pypi-version| image:: https://badge.fury.io/py/simpleeval.svg
|
|
13
13
|
:target: https://badge.fury.io/py/simpleeval
|
|
14
14
|
:alt: PyPI Version
|
|
15
15
|
|
|
16
|
-
|
|
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
|
|
24
|
+
|
|
25
|
+
|build-status| |code-coverage| |pypi-version| |python-versions| |pypi-monthly-downloads| |formatting-with-ruff|
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
A single file library for easily adding evaluatable expressions into
|
|
17
29
|
python projects. Say you want to allow a user to set an alarm volume, which
|
|
18
30
|
could depend on the time of day, alarm level, how many previous alarms had gone
|
|
19
31
|
off, and if there is music playing at the time.
|
|
@@ -21,8 +33,9 @@ off, and if there is music playing at the time.
|
|
|
21
33
|
Or if you want to allow simple formulae in a web application, but don't want to
|
|
22
34
|
give full eval() access, or don't want to run in javascript on the client side.
|
|
23
35
|
|
|
24
|
-
It's deliberately
|
|
25
|
-
even just a single file you can dump
|
|
36
|
+
It's deliberately trying to stay simple to use and not have millions of features,
|
|
37
|
+
pull it in from PyPI (pip or easy_install), or even just a single file you can dump
|
|
38
|
+
into a project.
|
|
26
39
|
|
|
27
40
|
Internally, it's using the amazing python ``ast`` module to parse the
|
|
28
41
|
expression, which allows very fine control of what is and isn't allowed. It
|
|
@@ -37,7 +50,7 @@ Operators_ section below)
|
|
|
37
50
|
|
|
38
51
|
You should be aware of this when deploying in a public setting.
|
|
39
52
|
|
|
40
|
-
The defaults are pretty locked down and basic, and it's
|
|
53
|
+
The defaults are pretty locked down and basic, and it's easy to add
|
|
41
54
|
whatever extra specific functionality you need (your own functions,
|
|
42
55
|
variable/name lookup, etc).
|
|
43
56
|
|
|
@@ -131,23 +144,36 @@ the defaults:
|
|
|
131
144
|
| | ``"spam" in "my breakfast"`` |
|
|
132
145
|
| | -> ``False`` |
|
|
133
146
|
+--------+------------------------------------+
|
|
147
|
+
| ``^`` | "bitwise exclusive OR" (xor) |
|
|
148
|
+
| | ``62 ^ 20`` -> ``42`` |
|
|
149
|
+
+--------+------------------------------------+
|
|
150
|
+
| ``|`` | "bitwise OR" |
|
|
151
|
+
| | ``8 | 34`` -> ``42`` |
|
|
152
|
+
+--------+------------------------------------+
|
|
153
|
+
| ``&`` | "bitwise AND" |
|
|
154
|
+
| | ``100 & 63`` -> ``36`` |
|
|
155
|
+
+--------+------------------------------------+
|
|
156
|
+
| ``~`` | "bitwise invert" |
|
|
157
|
+
| | ``~ -43`` -> ``42`` |
|
|
158
|
+
+--------+------------------------------------+
|
|
134
159
|
|
|
135
160
|
|
|
136
|
-
The ``^`` operator is
|
|
137
|
-
is
|
|
138
|
-
|
|
139
|
-
based evaluator explained below):
|
|
161
|
+
The ``^`` operator is often mistaken for a exponent operator, not the bitwise
|
|
162
|
+
operation that it is in python, so if you want ``3 ^ 2`` to equal ``9``, you can
|
|
163
|
+
replace the operator like this:
|
|
140
164
|
|
|
141
|
-
.. code-block::
|
|
165
|
+
.. code-block:: pycon
|
|
142
166
|
|
|
143
167
|
>>> import ast
|
|
144
|
-
>>> import
|
|
168
|
+
>>> from simpleeval import safe_power
|
|
145
169
|
|
|
146
170
|
>>> s = SimpleEval()
|
|
147
|
-
>>> s.operators[ast.BitXor] =
|
|
171
|
+
>>> s.operators[ast.BitXor] = safe_power
|
|
148
172
|
|
|
149
|
-
>>> s.eval("
|
|
150
|
-
|
|
173
|
+
>>> s.eval("3 ^ 2")
|
|
174
|
+
9
|
|
175
|
+
|
|
176
|
+
for example.
|
|
151
177
|
|
|
152
178
|
Limited Power
|
|
153
179
|
~~~~~~~~~~~~~
|
|
@@ -155,13 +181,13 @@ Limited Power
|
|
|
155
181
|
Also note, the ``**`` operator has been locked down by default to have a
|
|
156
182
|
maximum input value of ``4000000``, which makes it somewhat harder to make
|
|
157
183
|
expressions which go on for ever. You can change this limit by changing the
|
|
158
|
-
``simpleeval.
|
|
184
|
+
``simpleeval.MAX_POWER`` module level value to whatever is an appropriate value
|
|
159
185
|
for you (and the hardware that you're running on) or if you want to completely
|
|
160
186
|
remove all limitations, you can set the ``s.operators[ast.Pow] = operator.pow``
|
|
161
187
|
or make your own function.
|
|
162
188
|
|
|
163
189
|
On my computer, ``9**9**5`` evaluates almost instantly, but ``9**9**6`` takes
|
|
164
|
-
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``
|
|
165
191
|
limit, it throws a ``NumberTooHigh`` exception for you. (Otherwise it would go
|
|
166
192
|
on for hours, or until the computer runs out of memory)
|
|
167
193
|
|
|
@@ -179,7 +205,7 @@ If Expressions
|
|
|
179
205
|
|
|
180
206
|
You can use python style ``if x then y else z`` type expressions:
|
|
181
207
|
|
|
182
|
-
.. code-block::
|
|
208
|
+
.. code-block:: pycon
|
|
183
209
|
|
|
184
210
|
>>> simple_eval("'equal' if x == y else 'not equal'",
|
|
185
211
|
names={"x": 1, "y": 2})
|
|
@@ -187,7 +213,7 @@ You can use python style ``if x then y else z`` type expressions:
|
|
|
187
213
|
|
|
188
214
|
which, of course, can be nested:
|
|
189
215
|
|
|
190
|
-
.. code-block::
|
|
216
|
+
.. code-block:: pycon
|
|
191
217
|
|
|
192
218
|
>>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
|
|
193
219
|
'c'
|
|
@@ -198,7 +224,7 @@ Functions
|
|
|
198
224
|
|
|
199
225
|
You can define functions which you'd like the expresssions to have access to:
|
|
200
226
|
|
|
201
|
-
.. code-block::
|
|
227
|
+
.. code-block:: pycon
|
|
202
228
|
|
|
203
229
|
>>> simple_eval("double(21)", functions={"double": lambda x:x*2})
|
|
204
230
|
42
|
|
@@ -206,7 +232,7 @@ You can define functions which you'd like the expresssions to have access to:
|
|
|
206
232
|
You can define "real" functions to pass in rather than lambdas, of course too,
|
|
207
233
|
and even re-name them so that expressions can be shorter
|
|
208
234
|
|
|
209
|
-
.. code-block::
|
|
235
|
+
.. code-block:: pycon
|
|
210
236
|
|
|
211
237
|
>>> def double(x):
|
|
212
238
|
return x * 2
|
|
@@ -225,13 +251,13 @@ are provided in the ``DEFAULT_FUNCTIONS`` dict:
|
|
|
225
251
|
+----------------+--------------------------------------------------+
|
|
226
252
|
| ``float(x)`` | Convert ``x`` to a ``float``. |
|
|
227
253
|
+----------------+--------------------------------------------------+
|
|
228
|
-
| ``str(x)`` | Convert ``x`` to a ``str``
|
|
254
|
+
| ``str(x)`` | Convert ``x`` to a ``str`` |
|
|
229
255
|
+----------------+--------------------------------------------------+
|
|
230
256
|
|
|
231
257
|
If you want to provide a list of functions, but want to keep these as well,
|
|
232
258
|
then you can do a normal python ``.copy()`` & ``.update``:
|
|
233
259
|
|
|
234
|
-
.. code-block::
|
|
260
|
+
.. code-block:: pycon
|
|
235
261
|
|
|
236
262
|
>>> my_functions = simpleeval.DEFAULT_FUNCTIONS.copy()
|
|
237
263
|
>>> my_functions.update(
|
|
@@ -246,7 +272,7 @@ Names
|
|
|
246
272
|
Sometimes it's useful to have variables available, which in python terminology
|
|
247
273
|
are called 'names'.
|
|
248
274
|
|
|
249
|
-
.. code-block::
|
|
275
|
+
.. code-block:: pycon
|
|
250
276
|
|
|
251
277
|
>>> simple_eval("a + b", names={"a": 11, "b": 100})
|
|
252
278
|
111
|
|
@@ -254,7 +280,7 @@ are called 'names'.
|
|
|
254
280
|
You can also hand the handling of names over to a function, if you prefer:
|
|
255
281
|
|
|
256
282
|
|
|
257
|
-
.. code-block::
|
|
283
|
+
.. code-block:: pycon
|
|
258
284
|
|
|
259
285
|
>>> def name_handler(node):
|
|
260
286
|
return ord(node.id[0].lower(a))-96
|
|
@@ -263,9 +289,34 @@ You can also hand the handling of names over to a function, if you prefer:
|
|
|
263
289
|
3
|
|
264
290
|
|
|
265
291
|
That was a bit of a silly example, but you could use this for pulling values
|
|
266
|
-
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:
|
|
267
298
|
|
|
268
|
-
|
|
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
|
|
309
|
+
|
|
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).
|
|
269
320
|
|
|
270
321
|
Creating an Evaluator Class
|
|
271
322
|
---------------------------
|
|
@@ -275,7 +326,7 @@ evaluations, you can create a SimpleEval object, and pass it expressions each
|
|
|
275
326
|
time (which should be a bit quicker, and certainly more convenient for some use
|
|
276
327
|
cases):
|
|
277
328
|
|
|
278
|
-
.. code-block::
|
|
329
|
+
.. code-block:: pycon
|
|
279
330
|
|
|
280
331
|
>>> s = SimpleEval()
|
|
281
332
|
|
|
@@ -287,6 +338,22 @@ cases):
|
|
|
287
338
|
|
|
288
339
|
# and so on...
|
|
289
340
|
|
|
341
|
+
One useful feature of using the ``SimpleEval`` object is that you can parse an expression
|
|
342
|
+
once, and then evaluate it mulitple times using different ``names``:
|
|
343
|
+
|
|
344
|
+
.. code-block:: python
|
|
345
|
+
|
|
346
|
+
# Set up & Cache the parse tree:
|
|
347
|
+
expression = "foo + bar"
|
|
348
|
+
parsed = s.parse(expression)
|
|
349
|
+
|
|
350
|
+
# evaluate the expression multiple times:
|
|
351
|
+
for names in [{"foo": 1, "bar": 10}, {"foo": 100, "bar": 42}]:
|
|
352
|
+
s.names = names
|
|
353
|
+
print(s.eval(expression, previously_parsed=parsed))
|
|
354
|
+
|
|
355
|
+
for instance. This may help with performance.
|
|
356
|
+
|
|
290
357
|
You can assign / edit the various options of the ``SimpleEval`` object if you
|
|
291
358
|
want to. Either assign them during creation (like the ``simple_eval``
|
|
292
359
|
function)
|
|
@@ -338,7 +405,7 @@ comprehensions.
|
|
|
338
405
|
Since the primary intention of this library is short expressions - an extra 'sweetener' is
|
|
339
406
|
enabled by default. You can access a dict (or similar's) keys using the .attr syntax:
|
|
340
407
|
|
|
341
|
-
.. code-block::
|
|
408
|
+
.. code-block:: pycon
|
|
342
409
|
|
|
343
410
|
>>> simple_eval("foo.bar", names={"foo": {"bar": 42}})
|
|
344
411
|
42
|
|
@@ -368,8 +435,7 @@ and then use ``EvalNoMethods`` instead of the ``SimpleEval`` class.
|
|
|
368
435
|
Other...
|
|
369
436
|
--------
|
|
370
437
|
|
|
371
|
-
The library supports
|
|
372
|
-
with python 2.7 as well.
|
|
438
|
+
The library supports Python 3.9 and higher.
|
|
373
439
|
|
|
374
440
|
Object attributes that start with ``_`` or ``func_`` are disallowed by default.
|
|
375
441
|
If you really need that (BE CAREFUL!), then modify the module global
|
|
@@ -400,9 +466,18 @@ Or to set the tests running on every file change:
|
|
|
400
466
|
|
|
401
467
|
(requires ``entr``)
|
|
402
468
|
|
|
469
|
+
I'm trying to keep the codebase relatively clean with Black, isort, pylint & mypy.
|
|
470
|
+
See::
|
|
471
|
+
|
|
472
|
+
$ make format
|
|
473
|
+
|
|
474
|
+
and::
|
|
475
|
+
|
|
476
|
+
$ make lint
|
|
477
|
+
|
|
403
478
|
BEWARE
|
|
404
479
|
------
|
|
405
480
|
|
|
406
|
-
I've done the best I can with this library - but there's no
|
|
481
|
+
I've done the best I can with this library - but there's no warranty, no guarantee, nada. A lot of
|
|
407
482
|
very clever people think the whole idea of trying to sandbox CPython is impossible. Read the code
|
|
408
483
|
yourself, and use it at your own risk.
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
[build-system]
|
|
2
|
-
requires = ["setuptools>=30.3.0", "wheel"
|
|
2
|
+
requires = ["setuptools>=30.3.0", "wheel"]
|
|
3
3
|
build-backend = "setuptools.build_meta"
|
|
4
4
|
|
|
5
5
|
[tool.black]
|
|
6
6
|
line-length = 99
|
|
7
7
|
target-version = ['py310']
|
|
8
8
|
|
|
9
|
+
[tool.ruff]
|
|
10
|
+
line-length = 99
|
|
11
|
+
|
|
9
12
|
[tool.isort]
|
|
10
13
|
combine_as_imports = true
|
|
11
14
|
float_to_top = true
|
|
@@ -16,7 +19,9 @@ multi_line_output = 3
|
|
|
16
19
|
|
|
17
20
|
[tool.pylint.messages_control]
|
|
18
21
|
disable = [
|
|
22
|
+
"fixme",
|
|
19
23
|
"consider-using-f-string",
|
|
24
|
+
"raise-missing-from",
|
|
20
25
|
"invalid-name",
|
|
21
26
|
"too-few-public-methods",
|
|
22
27
|
"too-many-public-methods",
|