simpleeval 1.0.0__py3-none-any.whl

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.
@@ -0,0 +1,21 @@
1
+ simpleeval - Copyright (c) 2013-2024 Daniel Fairhead
2
+
3
+ (MIT Licence)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,501 @@
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
+
19
+ simpleeval (Simple Eval)
20
+ ========================
21
+
22
+ .. |build-status| image:: https://github.com/danthedeckie/simpleeval/actions/workflows/ci.yml/badge.svg?branch=gh-actions-build
23
+ :target: https://github.com/danthedeckie/simpleeval/actions/
24
+ :alt: Build Status
25
+
26
+ .. |code-coverage| image:: https://codecov.io/gh/danthedeckie/simpleeval/branch/master/graph/badge.svg?token=isRnN1yrca
27
+ :target: https://codecov.io/gh/danthedeckie/simpleeval
28
+ :alt: Code Coverage Status
29
+
30
+ .. |pypi-version| image:: https://badge.fury.io/py/simpleeval.svg
31
+ :target: https://badge.fury.io/py/simpleeval
32
+ :alt: PyPI Version
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
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
47
+ python projects. Say you want to allow a user to set an alarm volume, which
48
+ could depend on the time of day, alarm level, how many previous alarms had gone
49
+ off, and if there is music playing at the time.
50
+
51
+ Or if you want to allow simple formulae in a web application, but don't want to
52
+ give full eval() access, or don't want to run in javascript on the client side.
53
+
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.
57
+
58
+ Internally, it's using the amazing python ``ast`` module to parse the
59
+ expression, which allows very fine control of what is and isn't allowed. It
60
+ should be completely safe in terms of what operations can be performed by the
61
+ expression.
62
+
63
+ The only issue I know to be aware of is that you can create an expression which
64
+ takes a long time to evaluate, or which evaluating requires an awful lot of
65
+ memory, which leaves the potential for DOS attacks. There is basic protection
66
+ against this, and you can lock it down further if you desire. (see the
67
+ Operators_ section below)
68
+
69
+ You should be aware of this when deploying in a public setting.
70
+
71
+ The defaults are pretty locked down and basic, and it's easy to add
72
+ whatever extra specific functionality you need (your own functions,
73
+ variable/name lookup, etc).
74
+
75
+ Basic Usage
76
+ -----------
77
+
78
+ To get very simple evaluating:
79
+
80
+ .. code-block:: python
81
+
82
+ from simpleeval import simple_eval
83
+
84
+ simple_eval("21 + 21")
85
+
86
+ returns ``42``.
87
+
88
+ Expressions can be as complex and convoluted as you want:
89
+
90
+ .. code-block:: python
91
+
92
+ simple_eval("21 + 19 / 7 + (8 % 3) ** 9")
93
+
94
+ returns ``535.714285714``.
95
+
96
+ You can add your own functions in as well.
97
+
98
+ .. code-block:: python
99
+
100
+ simple_eval("square(11)", functions={"square": lambda x: x*x})
101
+
102
+ returns ``121``.
103
+
104
+ For more details of working with functions, read further down.
105
+
106
+ Note:
107
+ ~~~~~
108
+ all further examples use ``>>>`` to designate python code, as if you are using
109
+ the python interactive prompt.
110
+
111
+ .. _Operators:
112
+
113
+ Operators
114
+ ---------
115
+ You can add operators yourself, using the ``operators`` argument, but these are
116
+ the defaults:
117
+
118
+ +--------+------------------------------------+
119
+ | ``+`` | add two things. ``x + y`` |
120
+ | | ``1 + 1`` -> ``2`` |
121
+ +--------+------------------------------------+
122
+ | ``-`` | subtract two things ``x - y`` |
123
+ | | ``100 - 1`` -> ``99`` |
124
+ +--------+------------------------------------+
125
+ | ``/`` | divide one thing by another |
126
+ | | ``x / y`` |
127
+ | | ``100/10`` -> ``10`` |
128
+ +--------+------------------------------------+
129
+ | ``*`` | multiple one thing by another |
130
+ | | ``x * y`` |
131
+ | | ``10 * 10`` -> ``100`` |
132
+ +--------+------------------------------------+
133
+ | ``**`` | 'to the power of' ``x**y`` |
134
+ | | ``2 ** 10`` -> ``1024`` |
135
+ +--------+------------------------------------+
136
+ | ``%`` | modulus. (remainder) ``x % y`` |
137
+ | | ``15 % 4`` -> ``3`` |
138
+ +--------+------------------------------------+
139
+ | ``==`` | equals ``x == y`` |
140
+ | | ``15 == 4`` -> ``False`` |
141
+ +--------+------------------------------------+
142
+ | ``<`` | Less than. ``x < y`` |
143
+ | | ``1 < 4`` -> ``True`` |
144
+ +--------+------------------------------------+
145
+ | ``>`` | Greater than. ``x > y`` |
146
+ | | ``1 > 4`` -> ``False`` |
147
+ +--------+------------------------------------+
148
+ | ``<=`` | Less than or Equal to. ``x <= y`` |
149
+ | | ``1 < 4`` -> ``True`` |
150
+ +--------+------------------------------------+
151
+ | ``>=`` | Greater or Equal to ``x >= 21`` |
152
+ | | ``1 >= 4`` -> ``False`` |
153
+ +--------+------------------------------------+
154
+ | ``>>`` | "Right shift" the number. |
155
+ | | ``100 >> 2`` -> ``25`` |
156
+ +--------+------------------------------------+
157
+ | ``<<`` | "Left shift" the number. |
158
+ | | ``100 << 2`` -> ``400`` |
159
+ +--------+------------------------------------+
160
+ | ``in`` | is something contained within |
161
+ | | something else. |
162
+ | | ``"spam" in "my breakfast"`` |
163
+ | | -> ``False`` |
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
+ +--------+------------------------------------+
177
+
178
+
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:
182
+
183
+ .. code-block:: pycon
184
+
185
+ >>> import ast
186
+ >>> from simpleeval import safe_power
187
+
188
+ >>> s = SimpleEval()
189
+ >>> s.operators[ast.BitXor] = safe_power
190
+
191
+ >>> s.eval("3 ^ 2")
192
+ 9
193
+
194
+ for example.
195
+
196
+ Limited Power
197
+ ~~~~~~~~~~~~~
198
+
199
+ Also note, the ``**`` operator has been locked down by default to have a
200
+ maximum input value of ``4000000``, which makes it somewhat harder to make
201
+ expressions which go on for ever. You can change this limit by changing the
202
+ ``simpleeval.MAX_POWER`` module level value to whatever is an appropriate value
203
+ for you (and the hardware that you're running on) or if you want to completely
204
+ remove all limitations, you can set the ``s.operators[ast.Pow] = operator.pow``
205
+ or make your own function.
206
+
207
+ On my computer, ``9**9**5`` evaluates almost instantly, but ``9**9**6`` takes
208
+ over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``MAX_POWER``
209
+ limit, it throws a ``NumberTooHigh`` exception for you. (Otherwise it would go
210
+ on for hours, or until the computer runs out of memory)
211
+
212
+ Strings (and other Iterables) Safety
213
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
214
+
215
+ There are also limits on string length (100000 characters,
216
+ ``MAX_STRING_LENGTH``). This can be changed if you wish.
217
+
218
+ Related to this, if you try to create a silly long string/bytes/list, by doing
219
+ ``'i want to break free'.split() * 9999999999`` for instance, it will block you.
220
+
221
+ If Expressions
222
+ --------------
223
+
224
+ You can use python style ``if x then y else z`` type expressions:
225
+
226
+ .. code-block:: pycon
227
+
228
+ >>> simple_eval("'equal' if x == y else 'not equal'",
229
+ names={"x": 1, "y": 2})
230
+ 'not equal'
231
+
232
+ which, of course, can be nested:
233
+
234
+ .. code-block:: pycon
235
+
236
+ >>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
237
+ 'c'
238
+
239
+
240
+ Functions
241
+ ---------
242
+
243
+ You can define functions which you'd like the expresssions to have access to:
244
+
245
+ .. code-block:: pycon
246
+
247
+ >>> simple_eval("double(21)", functions={"double": lambda x:x*2})
248
+ 42
249
+
250
+ You can define "real" functions to pass in rather than lambdas, of course too,
251
+ and even re-name them so that expressions can be shorter
252
+
253
+ .. code-block:: pycon
254
+
255
+ >>> def double(x):
256
+ return x * 2
257
+ >>> simple_eval("d(100) + double(1)", functions={"d": double, "double":double})
258
+ 202
259
+
260
+ If you don't provide your own ``functions`` dict, then the the following defaults
261
+ are provided in the ``DEFAULT_FUNCTIONS`` dict:
262
+
263
+ +----------------+--------------------------------------------------+
264
+ | ``randint(x)`` | Return a random ``int`` below ``x`` |
265
+ +----------------+--------------------------------------------------+
266
+ | ``rand()`` | Return a random ``float`` between 0 and 1 |
267
+ +----------------+--------------------------------------------------+
268
+ | ``int(x)`` | Convert ``x`` to an ``int``. |
269
+ +----------------+--------------------------------------------------+
270
+ | ``float(x)`` | Convert ``x`` to a ``float``. |
271
+ +----------------+--------------------------------------------------+
272
+ | ``str(x)`` | Convert ``x`` to a ``str`` |
273
+ +----------------+--------------------------------------------------+
274
+
275
+ If you want to provide a list of functions, but want to keep these as well,
276
+ then you can do a normal python ``.copy()`` & ``.update``:
277
+
278
+ .. code-block:: pycon
279
+
280
+ >>> my_functions = simpleeval.DEFAULT_FUNCTIONS.copy()
281
+ >>> my_functions.update(
282
+ square=(lambda x:x*x),
283
+ double=(lambda x:x+x),
284
+ )
285
+ >>> simple_eval('square(randint(100))', functions=my_functions)
286
+
287
+ Names
288
+ -----
289
+
290
+ Sometimes it's useful to have variables available, which in python terminology
291
+ are called 'names'.
292
+
293
+ .. code-block:: pycon
294
+
295
+ >>> simple_eval("a + b", names={"a": 11, "b": 100})
296
+ 111
297
+
298
+ You can also hand the handling of names over to a function, if you prefer:
299
+
300
+
301
+ .. code-block:: pycon
302
+
303
+ >>> def name_handler(node):
304
+ return ord(node.id[0].lower(a))-96
305
+
306
+ >>> simple_eval('a + b', names=name_handler)
307
+ 3
308
+
309
+ That was a bit of a silly example, but you could use this for pulling values
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
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).
338
+
339
+ Creating an Evaluator Class
340
+ ---------------------------
341
+
342
+ Rather than creating a new evaluator each time, if you are doing a lot of
343
+ evaluations, you can create a SimpleEval object, and pass it expressions each
344
+ time (which should be a bit quicker, and certainly more convenient for some use
345
+ cases):
346
+
347
+ .. code-block:: pycon
348
+
349
+ >>> s = SimpleEval()
350
+
351
+ >>> s.eval("1 + 1")
352
+ 2
353
+
354
+ >>> s.eval('100 * 10')
355
+ 1000
356
+
357
+ # and so on...
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
+
375
+ You can assign / edit the various options of the ``SimpleEval`` object if you
376
+ want to. Either assign them during creation (like the ``simple_eval``
377
+ function)
378
+
379
+ .. code-block:: python
380
+
381
+ def boo():
382
+ return 'Boo!'
383
+
384
+ s = SimpleEval(functions={"boo": boo})
385
+
386
+ or edit them after creation:
387
+
388
+ .. code-block:: python
389
+
390
+ s.names['fortytwo'] = 42
391
+
392
+ this actually means you can modify names (or functions) with functions, if you
393
+ really feel so inclined:
394
+
395
+ .. code-block:: python
396
+
397
+ s = SimpleEval()
398
+ def set_val(name, value):
399
+ s.names[name.value] = value.value
400
+ return value.value
401
+
402
+ s.functions = {'set': set_val}
403
+
404
+ s.eval("set('age', 111)")
405
+
406
+ Say. This would allow a certain level of 'scriptyness' if you had these
407
+ evaluations happening as callbacks in a program. Although you really are
408
+ reaching the end of what this library is intended for at this stage.
409
+
410
+ Compound Types
411
+ --------------
412
+
413
+ Compound types (``dict``, ``tuple``, ``list``, ``set``) in general just work if
414
+ you pass them in as named objects. If you want to allow creation of these, the
415
+ ``EvalWithCompoundTypes`` class works. Just replace any use of ``SimpleEval`` with
416
+ that.
417
+
418
+ The ``EvalWithCompoundTypes`` class also contains support for simple comprehensions.
419
+ eg: ``[x + 1 for x in [1,2,3]]``. There's a safety `MAX_COMPREHENSION_LENGTH` to control
420
+ how many items it'll allow before bailing too. This also takes into account nested
421
+ comprehensions.
422
+
423
+ Since the primary intention of this library is short expressions - an extra 'sweetener' is
424
+ enabled by default. You can access a dict (or similar's) keys using the .attr syntax:
425
+
426
+ .. code-block:: pycon
427
+
428
+ >>> simple_eval("foo.bar", names={"foo": {"bar": 42}})
429
+ 42
430
+
431
+ for instance. You can turn this off either by setting the module global `ATTR_INDEX_FALLBACK`
432
+ to `False`, or on the ``SimpleEval`` instance itself. e.g. ``evaller.ATTR_INDEX_FALLBACK=False``.
433
+
434
+ Extending
435
+ ---------
436
+
437
+ The ``SimpleEval`` class is pretty easy to extend. For instance, to create a
438
+ version that disallows method invocation on objects:
439
+
440
+ .. code-block:: python
441
+
442
+ import ast
443
+ import simpleeval
444
+
445
+ class EvalNoMethods(simpleeval.SimpleEval):
446
+ def _eval_call(self, node):
447
+ if isinstance(node.func, ast.Attribute):
448
+ raise simpleeval.FeatureNotAvailable("No methods please, we're British")
449
+ return super(EvalNoMethods, self)._eval_call(node)
450
+
451
+ and then use ``EvalNoMethods`` instead of the ``SimpleEval`` class.
452
+
453
+ Other...
454
+ --------
455
+
456
+ The library supports Python 3.9 and higher.
457
+
458
+ Object attributes that start with ``_`` or ``func_`` are disallowed by default.
459
+ If you really need that (BE CAREFUL!), then modify the module global
460
+ ``simpleeval.DISALLOW_PREFIXES``.
461
+
462
+ A few builtin functions are listed in ``simpleeval.DISALLOW_FUNCTIONS``. ``type``, ``open``, etc.
463
+ If you need to give access to this kind of functionality to your expressions, then be very
464
+ careful. You'd be better wrapping the functions in your own safe wrappers.
465
+
466
+ The initial idea came from J.F. Sebastian on Stack Overflow
467
+ ( http://stackoverflow.com/a/9558001/1973500 ) with modifications and many improvements,
468
+ see the head of the main file for contributors list.
469
+
470
+ Please read the ``test_simpleeval.py`` file for other potential gotchas or
471
+ details. I'm very happy to accept pull requests, suggestions, or other issues.
472
+ Enjoy!
473
+
474
+ Developing
475
+ ----------
476
+
477
+ Run tests::
478
+
479
+ $ make test
480
+
481
+ Or to set the tests running on every file change:
482
+
483
+ $ make autotest
484
+
485
+ (requires ``entr``)
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
+
496
+ BEWARE
497
+ ------
498
+
499
+ I've done the best I can with this library - but there's no warranty, no guarantee, nada. A lot of
500
+ very clever people think the whole idea of trying to sandbox CPython is impossible. Read the code
501
+ yourself, and use it at your own risk.
@@ -0,0 +1,6 @@
1
+ simpleeval.py,sha256=0P98MgS8X5duSHNi8X6U3kGCnl74vujED534t9KzAZU,25136
2
+ simpleeval-1.0.0.dist-info/LICENCE,sha256=EI70efyvl0BAy3C1YjDMt1hts_NpYkUNGiRS8Un4Vpo,1092
3
+ simpleeval-1.0.0.dist-info/METADATA,sha256=5znuwlwIF43lOZpYXdboyFaOz0iJ4Qp3DaLsAMUD5gA,17141
4
+ simpleeval-1.0.0.dist-info/WHEEL,sha256=GV9aMThwP_4oNCtvEC2ec3qUYutgWeAzklro_0m4WJQ,91
5
+ simpleeval-1.0.0.dist-info/top_level.txt,sha256=TqPxgsG8isxdqUeKXTLPvXzD7RFvjtMHsYAiEenJ7_M,11
6
+ simpleeval-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (75.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ simpleeval
simpleeval.py ADDED
@@ -0,0 +1,768 @@
1
+ """
2
+ SimpleEval - (C) 2013-2024 Daniel Fairhead
3
+ -------------------------------------
4
+
5
+ An short, easy to use, safe and reasonably extensible expression evaluator.
6
+ Designed for things like in a website where you want to allow the user to
7
+ generate a string, or a number from some other input, without allowing full
8
+ eval() or other unsafe or needlessly complex linguistics.
9
+
10
+ -------------------------------------
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in
20
+ all copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
+ THE SOFTWARE.
29
+
30
+ -------------------------------------
31
+
32
+ Initial idea copied from J.F. Sebastian on Stack Overflow
33
+ ( http://stackoverflow.com/a/9558001/1973500 ) with
34
+ modifications and many improvements.
35
+
36
+ -------------------------------------
37
+ Contributors:
38
+ - corro (Robin Baumgartner) (py3k)
39
+ - dratchkov (David R) (nested dicts)
40
+ - marky1991 (Mark Young) (slicing)
41
+ - T045T (Nils Berg) (!=, py3kstr, obj.
42
+ - perkinslr (Logan Perkins) (.__globals__ or .func_ breakouts)
43
+ - impala2 (Kirill Stepanov) (massive _eval refactor)
44
+ - gk (ugik) (Other iterables than str can DOS too, and can be made)
45
+ - daveisfera (Dave Johansen) 'not' Boolean op, Pycharm, pep8, various other fixes
46
+ - xaled (Khalid Grandi) method chaining correctly, double-eval bugfix.
47
+ - EdwardBetts (Edward Betts) spelling correction.
48
+ - charlax (Charles-Axel Dein charlax) Makefile and cleanups
49
+ - mommothazaz123 (Andrew Zhu) f"string" support, Python 3.8 support
50
+ - lubieowoce (Uryga) various potential vulnerabilities
51
+ - JCavallo (Jean Cavallo) names dict shouldn't be modified
52
+ - Birne94 (Daniel Birnstiel) for fixing leaking generators, star expressions
53
+ - patricksurry (Patrick Surry) or should return last value, even if falsy.
54
+ - shughes-uk (Samantha Hughes) python w/o 'site' should not fail to import.
55
+ - KOLANICH packaging / deployment / setup help & << + >> & other bit ops
56
+ - graingert (Thomas Grainger) packaging / deployment / setup help
57
+ - bozokopic (Bozo Kopic) Memory leak fix
58
+ - daxamin (Dax Amin) Better error for attempting to eval empty string
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
66
+
67
+ -------------------------------------
68
+ Basic Usage:
69
+
70
+ >>> s = SimpleEval()
71
+ >>> s.eval("20 + 30")
72
+ 50
73
+
74
+ You can add your own functions easily too:
75
+
76
+ if file.txt contents is "11"
77
+
78
+ >>> def get_file():
79
+ ... with open("file.txt", 'r') as f:
80
+ ... return f.read()
81
+
82
+ >>> s.functions["get_file"] = get_file
83
+ >>> s.eval("int(get_file()) + 31")
84
+ 42
85
+
86
+ For more information, see the full package documentation on pypi, or the github
87
+ repo.
88
+
89
+ -----------
90
+
91
+ If you don't need to re-use the evaluator (with it's names, functions, etc),
92
+ then you can use the simple_eval() function:
93
+
94
+ >>> simple_eval("21 + 19")
95
+ 40
96
+
97
+ You can pass names, operators and functions to the simple_eval function as
98
+ well:
99
+
100
+ >>> simple_eval("40 + two", names={"two": 2})
101
+ 42
102
+
103
+ """
104
+
105
+ import ast
106
+ import operator as op
107
+ import sys
108
+ import warnings
109
+ from random import random
110
+
111
+ ########################################
112
+ # Module wide 'globals'
113
+
114
+ MAX_STRING_LENGTH = 100000
115
+ MAX_COMPREHENSION_LENGTH = 10000
116
+ MAX_POWER = 4000000 # highest exponent
117
+ MAX_SHIFT = 10000 # highest << or >> (lshift / rshift)
118
+ MAX_SHIFT_BASE = int(sys.float_info.max) # highest on left side of << or >>
119
+ DISALLOW_PREFIXES = ["_", "func_"]
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
+ ]
130
+
131
+ # Disallow functions:
132
+ # This, strictly speaking, is not necessary. These /should/ never be accessable anyway,
133
+ # if DISALLOW_PREFIXES and DISALLOW_METHODS are all right. This is here to try and help
134
+ # people not be stupid. Allowing these functions opens up all sorts of holes - if any of
135
+ # their functionality is required, then please wrap them up in a safe container. And think
136
+ # very hard about it first. And don't say I didn't warn you.
137
+ # builtins is a dict in python >3.6 but a module before
138
+ DISALLOW_FUNCTIONS = {type, isinstance, eval, getattr, setattr, repr, compile, open, exec}
139
+ if hasattr(__builtins__, "help") or (
140
+ hasattr(__builtins__, "__contains__") and "help" in __builtins__ # type: ignore
141
+ ):
142
+ # PyInstaller environment doesn't include this module.
143
+ DISALLOW_FUNCTIONS.add(help)
144
+
145
+
146
+ ########################################
147
+ # Exceptions:
148
+
149
+
150
+ class InvalidExpression(Exception):
151
+ """Generic Exception"""
152
+
153
+ pass
154
+
155
+
156
+ class FunctionNotDefined(InvalidExpression):
157
+ """sorry! That function isn't defined!"""
158
+
159
+ def __init__(self, func_name, expression):
160
+ self.message = "Function '{0}' not defined," " for expression '{1}'.".format(
161
+ func_name, expression
162
+ )
163
+ setattr(self, "func_name", func_name) # bypass 2to3 confusion.
164
+ self.expression = expression
165
+
166
+ super(InvalidExpression, self).__init__(self.message)
167
+
168
+
169
+ class NameNotDefined(InvalidExpression):
170
+ """a name isn't defined."""
171
+
172
+ def __init__(self, name, expression):
173
+ self.name = name
174
+ self.message = "'{0}' is not defined for expression '{1}'".format(name, expression)
175
+ self.expression = expression
176
+
177
+ super(InvalidExpression, self).__init__(self.message)
178
+
179
+
180
+ class AttributeDoesNotExist(InvalidExpression):
181
+ """attribute does not exist"""
182
+
183
+ def __init__(self, attr, expression):
184
+ self.message = "Attribute '{0}' does not exist in expression '{1}'".format(
185
+ attr, expression
186
+ )
187
+ self.attr = attr
188
+ self.expression = expression
189
+
190
+ super(InvalidExpression, self).__init__(self.message)
191
+
192
+
193
+ class OperatorNotDefined(InvalidExpression):
194
+ """operator does not exist"""
195
+
196
+ def __init__(self, attr, expression):
197
+ self.message = "Operator '{0}' does not exist in expression '{1}'".format(attr, expression)
198
+ self.attr = attr
199
+ self.expression = expression
200
+
201
+ super(InvalidExpression, self).__init__(self.message)
202
+
203
+
204
+ class FeatureNotAvailable(InvalidExpression):
205
+ """What you're trying to do is not allowed."""
206
+
207
+ pass
208
+
209
+
210
+ class NumberTooHigh(InvalidExpression):
211
+ """Sorry! That number is too high. I don't want to spend the
212
+ next 10 years evaluating this expression!"""
213
+
214
+ pass
215
+
216
+
217
+ class IterableTooLong(InvalidExpression):
218
+ """That iterable is **way** too long, baby."""
219
+
220
+ pass
221
+
222
+
223
+ class AssignmentAttempted(UserWarning):
224
+ """Assignment not allowed in SimpleEval"""
225
+
226
+ pass
227
+
228
+
229
+ class MultipleExpressions(UserWarning):
230
+ """Only the first expression parsed will be used"""
231
+
232
+ pass
233
+
234
+
235
+ ########################################
236
+ # Default simple functions to include:
237
+
238
+
239
+ def random_int(top):
240
+ """return a random int below <top>"""
241
+
242
+ return int(random() * top)
243
+
244
+
245
+ def safe_power(a, b): # pylint: disable=invalid-name
246
+ """a limited exponent/to-the-power-of function, for safety reasons"""
247
+
248
+ if abs(a) > MAX_POWER or abs(b) > MAX_POWER:
249
+ raise NumberTooHigh("Sorry! I don't want to evaluate {0} ** {1}".format(a, b))
250
+ return a**b
251
+
252
+
253
+ def safe_mult(a, b): # pylint: disable=invalid-name
254
+ """limit the number of times an iterable can be repeated..."""
255
+
256
+ if hasattr(a, "__len__") and b * len(a) > MAX_STRING_LENGTH:
257
+ raise IterableTooLong("Sorry, I will not evalute something that long.")
258
+ if hasattr(b, "__len__") and a * len(b) > MAX_STRING_LENGTH:
259
+ raise IterableTooLong("Sorry, I will not evalute something that long.")
260
+
261
+ return a * b
262
+
263
+
264
+ def safe_add(a, b): # pylint: disable=invalid-name
265
+ """iterable length limit again"""
266
+
267
+ if hasattr(a, "__len__") and hasattr(b, "__len__"):
268
+ if len(a) + len(b) > MAX_STRING_LENGTH:
269
+ raise IterableTooLong(
270
+ "Sorry, adding those two together would" " make something too long."
271
+ )
272
+ return a + b
273
+
274
+
275
+ def safe_rshift(a, b): # pylint: disable=invalid-name
276
+ """rshift, but with input limits"""
277
+ if abs(b) > MAX_SHIFT or abs(a) > MAX_SHIFT_BASE:
278
+ raise NumberTooHigh("Sorry! I don't want to evaluate {0} >> {1}".format(a, b))
279
+ return a >> b
280
+
281
+
282
+ def safe_lshift(a, b): # pylint: disable=invalid-name
283
+ """lshift, but with input limits"""
284
+ if abs(b) > MAX_SHIFT or abs(a) > MAX_SHIFT_BASE:
285
+ raise NumberTooHigh("Sorry! I don't want to evaluate {0} << {1}".format(a, b))
286
+ return a << b
287
+
288
+
289
+ ########################################
290
+ # Defaults for the evaluator:
291
+
292
+ DEFAULT_OPERATORS = {
293
+ ast.Add: safe_add,
294
+ ast.Sub: op.sub,
295
+ ast.Mult: safe_mult,
296
+ ast.Div: op.truediv,
297
+ ast.FloorDiv: op.floordiv,
298
+ ast.RShift: safe_rshift,
299
+ ast.LShift: safe_lshift,
300
+ ast.Pow: safe_power,
301
+ ast.Mod: op.mod,
302
+ ast.Eq: op.eq,
303
+ ast.NotEq: op.ne,
304
+ ast.Gt: op.gt,
305
+ ast.Lt: op.lt,
306
+ ast.GtE: op.ge,
307
+ ast.LtE: op.le,
308
+ ast.Not: op.not_,
309
+ ast.USub: op.neg,
310
+ ast.UAdd: op.pos,
311
+ ast.BitXor: op.xor,
312
+ ast.BitOr: op.or_,
313
+ ast.BitAnd: op.and_,
314
+ ast.Invert: op.invert,
315
+ ast.In: lambda x, y: op.contains(y, x),
316
+ ast.NotIn: lambda x, y: not op.contains(y, x),
317
+ ast.Is: lambda x, y: x is y,
318
+ ast.IsNot: lambda x, y: x is not y,
319
+ }
320
+
321
+ DEFAULT_FUNCTIONS = {
322
+ "rand": random,
323
+ "randint": random_int,
324
+ "int": int,
325
+ "float": float,
326
+ "str": str,
327
+ }
328
+
329
+ DEFAULT_NAMES = {"True": True, "False": False, "None": None}
330
+
331
+ ATTR_INDEX_FALLBACK = True
332
+
333
+
334
+ ########################################
335
+ # And the actual evaluator:
336
+
337
+
338
+ class SimpleEval(object): # pylint: disable=too-few-public-methods
339
+ """A very simple expression parser.
340
+ >>> s = SimpleEval()
341
+ >>> s.eval("20 + 30 - ( 10 * 5)")
342
+ 0
343
+ """
344
+
345
+ expr = ""
346
+
347
+ def __init__(self, operators=None, functions=None, names=None):
348
+ """
349
+ Create the evaluator instance. Set up valid operators (+,-, etc)
350
+ functions (add, random, get_val, whatever) and names."""
351
+
352
+ if operators is None:
353
+ operators = DEFAULT_OPERATORS.copy()
354
+ if functions is None:
355
+ functions = DEFAULT_FUNCTIONS.copy()
356
+ if names is None:
357
+ names = DEFAULT_NAMES.copy()
358
+
359
+ self.operators = operators
360
+ self.functions = functions
361
+ self.names = names
362
+
363
+ self.nodes = {
364
+ ast.Expr: self._eval_expr,
365
+ ast.Assign: self._eval_assign,
366
+ ast.AugAssign: self._eval_aug_assign,
367
+ ast.Import: self._eval_import,
368
+ ast.Name: self._eval_name,
369
+ ast.UnaryOp: self._eval_unaryop,
370
+ ast.BinOp: self._eval_binop,
371
+ ast.BoolOp: self._eval_boolop,
372
+ ast.Compare: self._eval_compare,
373
+ ast.IfExp: self._eval_ifexp,
374
+ ast.Call: self._eval_call,
375
+ ast.keyword: self._eval_keyword,
376
+ ast.Subscript: self._eval_subscript,
377
+ ast.Attribute: self._eval_attribute,
378
+ ast.Index: self._eval_index,
379
+ ast.Slice: self._eval_slice,
380
+ ast.JoinedStr: self._eval_joinedstr,
381
+ ast.FormattedValue: self._eval_formattedvalue,
382
+ ast.Constant: self._eval_constant,
383
+ }
384
+
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
391
+
392
+ if Str := getattr(ast, "Str"):
393
+ self.nodes[Str] = self._eval_str
394
+
395
+ if NameConstant := getattr(ast, "NameConstant"):
396
+ self.nodes[NameConstant] = self._eval_constant
397
+
398
+ # Defaults:
399
+
400
+ self.ATTR_INDEX_FALLBACK = ATTR_INDEX_FALLBACK
401
+
402
+ # Check for forbidden functions:
403
+
404
+ for f in self.functions.values():
405
+ if f in DISALLOW_FUNCTIONS:
406
+ raise FeatureNotAvailable("This function {} is a really bad idea.".format(f))
407
+
408
+ def __del__(self):
409
+ self.nodes = None
410
+
411
+ @staticmethod
412
+ def parse(expr):
413
+ """parse an expression into a node tree"""
414
+
415
+ parsed = ast.parse(expr.strip())
416
+
417
+ if not parsed.body:
418
+ raise InvalidExpression("Sorry, cannot evaluate empty string")
419
+ if len(parsed.body) > 1:
420
+ warnings.warn(
421
+ "'{}' contains multiple expressions. Only the first will be used.".format(expr),
422
+ MultipleExpressions,
423
+ )
424
+ return parsed.body[0]
425
+
426
+ def eval(self, expr, previously_parsed=None):
427
+ """evaluate an expresssion, using the operators, functions and
428
+ names previously set up."""
429
+
430
+ # set a copy of the expression aside, so we can give nice errors...
431
+ self.expr = expr
432
+
433
+ return self._eval(previously_parsed or self.parse(expr))
434
+
435
+ def _eval(self, node):
436
+ """The internal evaluator used on each node in the parsed tree."""
437
+
438
+ try:
439
+ handler = self.nodes[type(node)]
440
+ except KeyError:
441
+ raise FeatureNotAvailable(
442
+ "Sorry, {0} is not available in this " "evaluator".format(type(node).__name__)
443
+ )
444
+
445
+ return handler(node)
446
+
447
+ def _eval_expr(self, node):
448
+ return self._eval(node.value)
449
+
450
+ def _eval_assign(self, node):
451
+ warnings.warn(
452
+ "Assignment ({}) attempted, but this is ignored".format(self.expr), AssignmentAttempted
453
+ )
454
+ return self._eval(node.value)
455
+
456
+ def _eval_aug_assign(self, node):
457
+ warnings.warn(
458
+ "Assignment ({}) attempted, but this is ignored".format(self.expr), AssignmentAttempted
459
+ )
460
+ return self._eval(node.value)
461
+
462
+ @staticmethod
463
+ def _eval_import(node):
464
+ raise FeatureNotAvailable("Sorry, 'import' is not allowed.")
465
+
466
+ @staticmethod
467
+ def _eval_num(node):
468
+ return node.n
469
+
470
+ @staticmethod
471
+ def _eval_str(node):
472
+ if len(node.s) > MAX_STRING_LENGTH:
473
+ raise IterableTooLong(
474
+ "String Literal in statement is too long! ({0}, when {1} is max)".format(
475
+ len(node.s), MAX_STRING_LENGTH
476
+ )
477
+ )
478
+ return node.s
479
+
480
+ @staticmethod
481
+ def _eval_constant(node):
482
+ if hasattr(node.value, "__len__") and len(node.value) > MAX_STRING_LENGTH:
483
+ raise IterableTooLong(
484
+ "Literal in statement is too long! ({0}, when {1} is max)".format(
485
+ len(node.value), MAX_STRING_LENGTH
486
+ )
487
+ )
488
+ return node.value
489
+
490
+ def _eval_unaryop(self, node):
491
+ try:
492
+ operator = self.operators[type(node.op)]
493
+ except KeyError:
494
+ raise OperatorNotDefined(node.op, self.expr)
495
+ return operator(self._eval(node.operand))
496
+
497
+ def _eval_binop(self, node):
498
+ try:
499
+ operator = self.operators[type(node.op)]
500
+ except KeyError:
501
+ raise OperatorNotDefined(node.op, self.expr)
502
+ return operator(self._eval(node.left), self._eval(node.right))
503
+
504
+ def _eval_boolop(self, node):
505
+ to_return = False
506
+ if isinstance(node.op, ast.And):
507
+ for value in node.values:
508
+ to_return = self._eval(value)
509
+ if not to_return:
510
+ break
511
+ elif isinstance(node.op, ast.Or):
512
+ for value in node.values:
513
+ to_return = self._eval(value)
514
+ if to_return:
515
+ break
516
+ return to_return
517
+
518
+ def _eval_compare(self, node):
519
+ right = self._eval(node.left)
520
+ to_return = True
521
+ for operation, comp in zip(node.ops, node.comparators):
522
+ if not to_return:
523
+ break
524
+ left = right
525
+ right = self._eval(comp)
526
+ to_return = self.operators[type(operation)](left, right)
527
+ return to_return
528
+
529
+ def _eval_ifexp(self, node):
530
+ return self._eval(node.body) if self._eval(node.test) else self._eval(node.orelse)
531
+
532
+ def _eval_call(self, node):
533
+ if isinstance(node.func, ast.Attribute):
534
+ func = self._eval(node.func)
535
+ else:
536
+ try:
537
+ func = self.functions[node.func.id]
538
+ except KeyError:
539
+ raise FunctionNotDefined(node.func.id, self.expr)
540
+ except AttributeError:
541
+ raise FeatureNotAvailable("Lambda Functions not implemented")
542
+
543
+ if func in DISALLOW_FUNCTIONS:
544
+ raise FeatureNotAvailable("This function is forbidden")
545
+
546
+ return func(
547
+ *(self._eval(a) for a in node.args), **dict(self._eval(k) for k in node.keywords)
548
+ )
549
+
550
+ def _eval_keyword(self, node):
551
+ return node.arg, self._eval(node.value)
552
+
553
+ def _eval_name(self, node):
554
+ try:
555
+ # This happens at least for slicing
556
+ # This is a safe thing to do because it is impossible
557
+ # that there is a true expression assigning to none
558
+ # (the compiler rejects it, so you can't even
559
+ # pass that to ast.parse)
560
+ return self.names[node.id]
561
+
562
+ except (TypeError, KeyError):
563
+ pass
564
+
565
+ if callable(self.names):
566
+ try:
567
+ return self.names(node)
568
+ except NameNotDefined:
569
+ pass
570
+ elif not hasattr(self.names, "__getitem__"):
571
+ raise InvalidExpression(
572
+ 'Trying to use name (variable) "{0}"'
573
+ ' when no "names" defined for'
574
+ " evaluator".format(node.id)
575
+ )
576
+
577
+ if node.id in self.functions:
578
+ return self.functions[node.id]
579
+
580
+ raise NameNotDefined(node.id, self.expr)
581
+
582
+ def _eval_subscript(self, node):
583
+ container = self._eval(node.value)
584
+ key = self._eval(node.slice)
585
+ # Currently if there's a KeyError, that gets raised straight up.
586
+ # TODO: Should that be wrapped in an InvalidExpression?
587
+ return container[key]
588
+
589
+ def _eval_attribute(self, node):
590
+ for prefix in DISALLOW_PREFIXES:
591
+ if node.attr.startswith(prefix):
592
+ raise FeatureNotAvailable(
593
+ "Sorry, access to __attributes "
594
+ " or func_ attributes is not available. "
595
+ "({0})".format(node.attr)
596
+ )
597
+ if node.attr in DISALLOW_METHODS:
598
+ raise FeatureNotAvailable(
599
+ "Sorry, this method is not available. " "({0})".format(node.attr)
600
+ )
601
+ # eval node
602
+ node_evaluated = self._eval(node.value)
603
+
604
+ # Maybe the base object is an actual object, not just a dict
605
+ try:
606
+ return getattr(node_evaluated, node.attr)
607
+ except (AttributeError, TypeError):
608
+ pass
609
+
610
+ # TODO: is this a good idea? Try and look for [x] if .x doesn't work?
611
+ if self.ATTR_INDEX_FALLBACK:
612
+ try:
613
+ return node_evaluated[node.attr]
614
+ except (KeyError, TypeError):
615
+ pass
616
+
617
+ # If it is neither, raise an exception
618
+ raise AttributeDoesNotExist(node.attr, self.expr)
619
+
620
+ def _eval_index(self, node):
621
+ return self._eval(node.value)
622
+
623
+ def _eval_slice(self, node):
624
+ lower = upper = step = None
625
+ if node.lower is not None:
626
+ lower = self._eval(node.lower)
627
+ if node.upper is not None:
628
+ upper = self._eval(node.upper)
629
+ if node.step is not None:
630
+ step = self._eval(node.step)
631
+ return slice(lower, upper, step)
632
+
633
+ def _eval_joinedstr(self, node):
634
+ length = 0
635
+ evaluated_values = []
636
+ for n in node.values:
637
+ val = str(self._eval(n))
638
+ if len(val) + length > MAX_STRING_LENGTH:
639
+ raise IterableTooLong("Sorry, I will not evaluate something this long.")
640
+ evaluated_values.append(val)
641
+ return "".join(evaluated_values)
642
+
643
+ def _eval_formattedvalue(self, node):
644
+ if node.format_spec:
645
+ fmt = "{:" + self._eval(node.format_spec) + "}"
646
+ return fmt.format(self._eval(node.value))
647
+ return self._eval(node.value)
648
+
649
+
650
+ class EvalWithCompoundTypes(SimpleEval):
651
+ """
652
+ SimpleEval with additional Compound Types, and their respective
653
+ function editions. (list, tuple, dict, set).
654
+ """
655
+
656
+ _max_count = 0
657
+
658
+ def __init__(self, operators=None, functions=None, names=None):
659
+ super(EvalWithCompoundTypes, self).__init__(operators, functions, names)
660
+
661
+ self.functions.update(list=list, tuple=tuple, dict=dict, set=set)
662
+
663
+ self.nodes.update(
664
+ {
665
+ ast.Dict: self._eval_dict,
666
+ ast.Tuple: self._eval_tuple,
667
+ ast.List: self._eval_list,
668
+ ast.Set: self._eval_set,
669
+ ast.ListComp: self._eval_comprehension,
670
+ ast.GeneratorExp: self._eval_comprehension,
671
+ ast.DictComp: self._eval_comprehension,
672
+ }
673
+ )
674
+
675
+ def eval(self, expr, previously_parsed=None):
676
+ # reset _max_count for each eval run
677
+ self._max_count = 0
678
+ return super(EvalWithCompoundTypes, self).eval(expr, previously_parsed)
679
+
680
+ def _eval_dict(self, node):
681
+ result = {}
682
+
683
+ for key, value in zip(node.keys, node.values):
684
+ if key is None:
685
+ # "{**x}" gets parsed as a key-value pair of (None, Name(x))
686
+ result.update(self._eval(value))
687
+ else:
688
+ result[self._eval(key)] = self._eval(value)
689
+
690
+ return result
691
+
692
+ def _eval_list(self, node):
693
+ result = []
694
+
695
+ for item in node.elts:
696
+ if isinstance(item, ast.Starred):
697
+ result.extend(self._eval(item.value))
698
+ else:
699
+ result.append(self._eval(item))
700
+
701
+ return result
702
+
703
+ def _eval_tuple(self, node):
704
+ return tuple(self._eval(x) for x in node.elts)
705
+
706
+ def _eval_set(self, node):
707
+ return set(self._eval(x) for x in node.elts)
708
+
709
+ def _eval_comprehension(self, node):
710
+ if isinstance(node, ast.DictComp):
711
+ to_return = {}
712
+ else:
713
+ to_return = []
714
+
715
+ extra_names = {}
716
+
717
+ previous_name_evaller = self.nodes[ast.Name]
718
+
719
+ def eval_names_extra(node):
720
+ """
721
+ Here we hide our extra scope for within this comprehension
722
+ """
723
+ if node.id in extra_names:
724
+ return extra_names[node.id]
725
+ return previous_name_evaller(node)
726
+
727
+ self.nodes.update({ast.Name: eval_names_extra})
728
+
729
+ def recurse_targets(target, value):
730
+ """
731
+ Recursively (enter, (into, (nested, name), unpacking)) = \
732
+ and, (assign, (values, to), each
733
+ """
734
+ if isinstance(target, ast.Name):
735
+ extra_names[target.id] = value
736
+ else:
737
+ for t, v in zip(target.elts, value):
738
+ recurse_targets(t, v)
739
+
740
+ def do_generator(gi=0):
741
+ g = node.generators[gi]
742
+ for i in self._eval(g.iter):
743
+ self._max_count += 1
744
+
745
+ if self._max_count > MAX_COMPREHENSION_LENGTH:
746
+ raise IterableTooLong("Comprehension generates too many elements")
747
+ recurse_targets(g.target, i)
748
+ if all(self._eval(iff) for iff in g.ifs):
749
+ if len(node.generators) > gi + 1:
750
+ do_generator(gi + 1)
751
+ else:
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))
756
+
757
+ try:
758
+ do_generator()
759
+ finally:
760
+ self.nodes.update({ast.Name: previous_name_evaller})
761
+
762
+ return to_return
763
+
764
+
765
+ def simple_eval(expr, operators=None, functions=None, names=None):
766
+ """Simply evaluate an expresssion"""
767
+ s = SimpleEval(operators=operators, functions=functions, names=names)
768
+ return s.eval(expr)