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.
@@ -3,6 +3,7 @@ build
3
3
  dist
4
4
  MANIFEST
5
5
  .idea
6
+ .vscode
6
7
  venv
7
8
  .eggs/
8
9
  .coverage
@@ -1,4 +1,4 @@
1
- simpleeval - Copyright (c) 2013-2017 Daniel Fairhead
1
+ simpleeval - Copyright (c) 2013-2024 Daniel Fairhead
2
2
 
3
3
  (MIT Licence)
4
4
 
@@ -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
- black --check --diff simpleeval.py test_simpleeval.py
26
- isort --check-only --diff simpleeval.py test_simpleeval.py
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
- black simpleeval.py test_simpleeval.py
32
- isort simpleeval.py test_simpleeval.py
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.9.13
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 :: 4 - Beta
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/code%20style-black-000000.svg
33
- :target: https://github.com/psf/black
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/badge/linting-pylint-yellowgreen
36
- :target: https://github.com/PyCQA/pylint
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:: python
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.POWER_MAX`` module level value to whatever is an appropriate value
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 ``POWER_MAX``
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:: python
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:: python
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:: python
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:: python
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`` (``unicode`` in py2) |
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:: python
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:: python
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:: python
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
- The two default names that are provided are ``True`` and ``False``. So if you want to provide your own names, but want ``True`` and ``False`` to keep working, either provide them yourself, or ``.copy()`` and ``.update`` the ``DEFAULT_NAMES``. (See functions example above).
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:: python
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:: python
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 python 3 - but should be mostly compatible (and tested before 0.9.11)
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/code%20style-black-000000.svg
33
- :target: https://github.com/psf/black
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
- .. image:: https://img.shields.io/badge/linting-pylint-yellowgreen
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:: python
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.POWER_MAX`` module level value to whatever is an appropriate value
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 ``POWER_MAX``
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:: python
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:: python
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:: python
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:: python
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`` (``unicode`` in py2) |
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:: python
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:: python
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:: python
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
- The two default names that are provided are ``True`` and ``False``. So if you want to provide your own names, but want ``True`` and ``False`` to keep working, either provide them yourself, or ``.copy()`` and ``.update`` the ``DEFAULT_NAMES``. (See functions example above).
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:: python
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:: python
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 python 3 - but should be mostly compatible (and tested before 0.9.11)
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
@@ -6,6 +6,9 @@ build-backend = "setuptools.build_meta"
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
@@ -1,15 +1,16 @@
1
1
  [metadata]
2
2
  name = simpleeval
3
- version = 0.9.13
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 :: 4 - Beta
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/code%20style-black-000000.svg
17
- :target: https://github.com/psf/black
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
- .. image:: https://img.shields.io/badge/linting-pylint-yellowgreen
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:: python
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.POWER_MAX`` module level value to whatever is an appropriate value
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 ``POWER_MAX``
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:: python
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:: python
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:: python
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:: python
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`` (``unicode`` in py2) |
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:: python
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:: python
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:: python
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
- The two default names that are provided are ``True`` and ``False``. So if you want to provide your own names, but want ``True`` and ``False`` to keep working, either provide them yourself, or ``.copy()`` and ``.update`` the ``DEFAULT_NAMES``. (See functions example above).
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:: python
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:: python
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 python 3 - but should be mostly compatible (and tested before 0.9.11)
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-2023 Daniel Fairhead
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 = ["format", "format_map", "mro"]
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
- # pylint: disable=undefined-variable
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
- # py3k stuff:
379
- if hasattr(ast, "NameConstant"):
380
- self.nodes[ast.NameConstant] = self._eval_constant
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
- # py3.6, f-strings
383
- if hasattr(ast, "JoinedStr"):
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
- # py3.8 uses ast.Constant instead of ast.Num, ast.Str, ast.NameConstant
390
- if hasattr(ast, "Constant"):
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
- " ({0}, when {1} is max)".format(len(node.s), MAX_STRING_LENGTH)
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
- " ({0}, when {1} is max)".format(len(node.value), MAX_STRING_LENGTH)
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 exression assigning to none
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
- if hasattr(self.names, "__getitem__"):
554
- return self.names[node.id]
555
- if callable(self.names):
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
- except KeyError:
564
- if node.id in self.functions:
565
- return self.functions[node.id]
577
+ if node.id in self.functions:
578
+ return self.functions[node.id]
566
579
 
567
- raise NameNotDefined(node.id, self.expr)
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 PYTHON35 and key is None:
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 PYTHON3 and isinstance(item, ast.Starred):
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
- to_return = []
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.append(self._eval(node.elt))
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
- Unit tests for simpleeval.
5
- --------------------------
4
+ Unit tests for simpleeval.
5
+ --------------------------
6
6
 
7
- Most of this stuff is pretty basic.
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
- if hasattr(1, "from_bytes"): # python3 only
433
- with self.assertRaises(simpleeval.IterableTooLong):
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