[tryton-debian-vcs] simpleeval branch debian updated. debian/0.9.1-2-3-gfd50a46
Mathias Behrle
tryton-debian-vcs at alioth.debian.org
Tue Jan 24 16:17:23 UTC 2017
The following commit has been merged in the debian branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/simpleeval.git;a=commitdiff;h=debian/0.9.1-2-3-gfd50a46
commit fd50a46aa47de3406e1d9dd6a0423b1ec11f51b9
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Tue Jan 24 17:15:29 2017 +0100
Releasing debian version 0.9.3-1.
Signed-off-by: Mathias Behrle <mathiasb at m9s.biz>
diff --git a/debian/changelog b/debian/changelog
index 804c642..ccd99f1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,10 @@
+simpleeval (0.9.3-1) unstable; urgency=medium
+
+ * Merging upstream version 0.9.3.
+ * Update years in d/copyright.
+
+ -- Mathias Behrle <mathiasb at m9s.biz> Tue, 24 Jan 2017 17:15:09 +0100
+
simpleeval (0.9.1-2) unstable; urgency=medium
* Update years in d/copyright.
commit 5380871b92d9ccd745ed9db30861456258428ac5
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Tue Jan 24 17:14:59 2017 +0100
Update years in d/copyright.
diff --git a/debian/copyright b/debian/copyright
index f5cf9cf..e12259c 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -1,11 +1,11 @@
Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files: *
-Copyright: 2013-2016 Daniel Fairhead
+Copyright: 2013-2017 Daniel Fairhead
License: MIT
Files: debian/*
-Copyright: 2015 Mathias Behrle <mathiasb at m9s.biz>
+Copyright: 2015-2017 Mathias Behrle <mathiasb at m9s.biz>
License: MIT
License: MIT
commit 3bd2a16d17115a76ff3fb1199b4cf092f3850fba
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Tue Jan 24 17:14:34 2017 +0100
Merging upstream version 0.9.3.
diff --git a/.coveragerc b/.coveragerc
deleted file mode 100644
index 3aad258..0000000
--- a/.coveragerc
+++ /dev/null
@@ -1,3 +0,0 @@
-[run]
-branch = True
-omit = /home/travis/virtualenv/*
diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index b00a4fa..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,4 +0,0 @@
-*.pyc
-build
-dist
-MANIFEST
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index 09fdbaf..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,19 +0,0 @@
-language: python
-python:
- - "2.7"
- - "3.2"
- - "3.3"
- - "3.4"
- - "3.5"
-install:
- - pip install nose
- - pip install coveralls
- # coverage no longer supports python 3.2...
- # - pip install coverage
- - if [ "$TRAVIS_PYTHON_VERSION" == "3.2" ]; then travis_retry pip install coverage==3.7.1; fi
- - if [ "$TRAVIS_PYTHON_VERSION" != "3.2" ]; then travis_retry pip install coverage; fi
-script:
- - nosetests
- - coverage run -m nose
-after_success:
- - coveralls
diff --git a/LICENCE b/LICENCE
deleted file mode 100644
index 40e7560..0000000
--- a/LICENCE
+++ /dev/null
@@ -1,21 +0,0 @@
-simpleeval - Copyright (c) 2013 Daniel Fairhead
-
-(MIT Licence)
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in
-all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
-THE SOFTWARE.
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..9933cb5
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,323 @@
+Metadata-Version: 1.1
+Name: simpleeval
+Version: 0.9.3
+Summary: A simple, safe single expression evaluator library.
+Home-page: https://github.com/danthedeckie/simpleeval
+Author: Daniel Fairhead
+Author-email: danthedeckie at gmail.com
+License: UNKNOWN
+Download-URL: https://github.com/danthedeckie/simpleeval/tarball/0.9.3
+Description: simpleeval (Simple Eval)
+ ========================
+
+ .. image:: https://travis-ci.org/danthedeckie/simpleeval.svg?branch=master
+ :target: https://travis-ci.org/danthedeckie/simpleeval
+ :alt: Build Status
+
+ .. image:: https://coveralls.io/repos/github/danthedeckie/simpleeval/badge.svg?branch=master
+ :target: https://coveralls.io/r/danthedeckie/simpleeval?branch=master
+ :alt: Coverage Status
+
+ .. image:: https://badge.fury.io/py/simpleeval.svg
+ :target: https://badge.fury.io/py/simpleeval
+ :alt: PyPI Version
+
+ A quick single file library for easily adding evaluatable expressions into
+ python projects. Say you want to allow a user to set an alarm volume, which
+ could depend on the time of day, alarm level, how many previous alarms had gone
+ off, and if there is music playing at the time.
+
+ Or if you want to allow simple formulae in a web application, but don't want to
+ give full eval() access, or don't want to run in javascript on the client side.
+
+ It's deliberately very simple, pull it in from PyPI (pip or easy_install), or
+ even just a single file you can dump into a project.
+
+ Internally, it's using the amazing python ``ast`` module to parse the
+ expression, which allows very fine control of what is and isn't allowed. It
+ should be completely safe in terms of what operations can be performed by the
+ expression.
+
+ The only issue I know to be aware of is that you can create an expression which
+ takes a long time to evaluate, or which evaluating requires an awful lot of
+ memory, which leaves the potential for DOS attacks. There is basic protection
+ against this, and you can lock it down further if you desire. (see the
+ Operators_ section below)
+
+ You should be aware of this when deploying in a public setting.
+
+ The defaults are pretty locked down and basic, and it's very easy to add
+ whatever extra specific functionality you need (your own functions,
+ variable/name lookup, etc).
+
+ Basic Usage
+ -----------
+
+ To get very simple evaluating:
+
+ .. code-block:: python
+
+ from simpleeval import simple_eval
+
+ simple_eval("21 + 21")
+
+ returns ``42``.
+
+ Expressions can be as complex and convoluted as you want:
+
+ .. code-block:: python
+
+ simple_eval("21 + 19 / 7 + (8 % 3) ** 9")
+
+ returns ``535.714285714``.
+
+ You can add your own functions in as well.
+
+ .. code-block:: python
+
+ simple_eval("square(11)", functions={"square": lambda x: x*x})
+
+ returns ``121``.
+
+ For more details of working with functions, read further down.
+
+ Note:
+ ~~~~~
+ all further examples use ``>>>`` to designate python code, as if you are using
+ the python interactive prompt.
+
+ .. _Operators:
+
+ Operators
+ ---------
+ You can add operators yourself, using the ``operators`` argument, but these are
+ the defaults:
+
+ +--------+------------------------------------+
+ | ``+`` | add two things. ``x + y`` |
+ | | ``1 + 1`` -> ``2`` |
+ +--------+------------------------------------+
+ | ``-`` | subtract two things ``x - y`` |
+ | | ``100 - 1`` -> ``99`` |
+ +--------+------------------------------------+
+ | ``/`` | divide one thing by another |
+ | | ``x / y`` |
+ | | ``100/10`` -> ``10`` |
+ +--------+------------------------------------+
+ | ``*`` | multiple one thing by another |
+ | | ``x * y`` |
+ | | ``10 * 10`` -> ``100`` |
+ +--------+------------------------------------+
+ | ``**`` | 'to the power of' ``x**y`` |
+ | | ``2 ** 10`` -> ``1024`` |
+ +--------+------------------------------------+
+ | ``%`` | modulus. (remainder) ``x % y`` |
+ | | ``15 % 4`` -> ``3`` |
+ +--------+------------------------------------+
+ | ``==`` | equals ``x == y`` |
+ | | ``15 == 4`` -> ``False`` |
+ +--------+------------------------------------+
+ | ``<`` | Less than. ``x < y`` |
+ | | ``1 < 4`` -> ``True`` |
+ +--------+------------------------------------+
+ | ``>`` | Greater than. ``x > y`` |
+ | | ``1 > 4`` -> ``False`` |
+ +--------+------------------------------------+
+ | ``<=`` | Less than or Equal to. ``x <= y`` |
+ | | ``1 < 4`` -> ``True`` |
+ +--------+------------------------------------+
+ | ``>=`` | Greater or Equal to ``x >= 21`` |
+ | | ``1 >= 4`` -> ``False`` |
+ +--------+------------------------------------+
+ | ``in`` | is something contained within |
+ | | something else. |
+ | | ``"spam" in "my breakfast"`` |
+ | | -> ``False`` |
+ +--------+------------------------------------+
+
+
+ The ``^`` operator is notably missing - not because it's hard, but because it
+ is often mistaken for a exponent operator, not the bitwise operation that it is
+ in python. It's trivial to add back in again if you wish (using the class
+ based evaluator explained below):
+
+ .. code-block:: python
+
+ >>> import ast
+ >>> import operator
+
+ >>> s = SimpleEval()
+ >>> s.operators[ast.BitXor] = operator.xor
+
+ >>> s.eval("2 ^ 10")
+ 8
+
+ Limited Power
+ ~~~~~~~~~~~~~
+
+ Also note, the ``**`` operator has been locked down by default to have a
+ maximum input value of ``4000000``, which makes it somewhat harder to make
+ expressions which go on for ever. You can change this limit by changing the
+ ``simpleeval.POWER_MAX`` module level value to whatever is an appropriate value
+ for you (and the hardware that you're running on) or if you want to completely
+ remove all limitations, you can set the ``s.operators[ast.Pow] = operator.pow``
+ or make your own function.
+
+ On my computer, ``9**9**5`` evaluates almost instantly, but ``9**9**6`` takes
+ over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``POWER_MAX``
+ limit, it throws a ``NumberTooHigh`` exception for you. (Otherwise it would go
+ on for hours, or until the computer runs out of memory)
+
+ String Safety
+ ~~~~~~~~~~~~~
+
+ There are also limits on string length (100000 characters,
+ ``MAX_STRING_LENGTH``). This can be changed if you wish.
+
+ If Expressions
+ --------------
+
+ You can use python style ``if x then y else z`` type expressions:
+
+ .. code-block:: python
+
+ >>> simple_eval("'equal' if x == y else 'not equal'",
+ names={"x": 1, "y": 2})
+ 'not equal'
+
+ which, of course, can be nested:
+
+ .. code-block:: python
+
+ >>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
+ 'c'
+
+
+ Functions
+ ---------
+
+ You can define functions which you'd like the expresssions to have access to:
+
+ .. code-block:: python
+
+ >>> simple_eval("double(21)", functions={"double": lambda x:x*2})
+ 42
+
+ You can define "real" functions to pass in rather than lambdas, of course too,
+ and even re-name them so that expressions can be shorter
+
+ .. code-block:: python
+
+ >>> def double(x):
+ return x * 2
+ >>> simple_eval("d(100) + double(1)", functions={"d": double, "double":double})
+ 202
+
+ Names
+ -----
+
+ Sometimes it's useful to have variables available, which in python terminology
+ are called 'names'.
+
+ .. code-block:: python
+
+ >>> simple_eval("a + b", names={"a": 11, "b": 100})
+ 111
+
+ You can also hand the handling of names over to a function, if you prefer:
+
+
+ .. code-block:: python
+
+ >>> def name_handler(node):
+ return ord(node.id[0].lower(a))-96
+
+ >>> simple_eval('a + b', names=name_handler)
+ 3
+
+ That was a bit of a silly example, but you could use this for pulling values
+ from a database or file, say, or doing some kind of caching system.
+
+ Creating an Evaluator Class
+ ---------------------------
+
+ Rather than creating a new evaluator each time, if you are doing a lot of
+ evaluations, you can create a SimpleEval object, and pass it expressions each
+ time (which should be a bit quicker, and certainly more convenient for some use
+ cases):
+
+ .. code-block:: python
+
+ >>> s = SimpleEval()
+
+ >>> s.eval("1 + 1")
+ 2
+
+ >>> s.eval('100 * 10')
+ 1000
+
+ # and so on...
+
+ You can assign / edit the various options of the ``SimpleEval`` object if you
+ want to. Either assign them during creation (like the ``simple_eval``
+ function)
+
+ .. code-block:: python
+
+ def boo():
+ return 'Boo!'
+
+ s = SimpleEval(functions={"boo": boo})
+
+ or edit them after creation:
+
+ .. code-block:: python
+
+ s.names['fortytwo'] = 42
+
+ this actually means you can modify names (or functions) with functions, if you
+ really feel so inclined:
+
+ .. code-block:: python
+
+ s = SimpleEval()
+ def set_val(name, value):
+ s.names[name.value] = value.value
+ return value.value
+
+ s.functions = {'set': set_val}
+
+ s.eval("set('age', 111)")
+
+ Say. This would allow a certain level of 'scriptyness' if you had these
+ evaluations happening as callbacks in a program. Although you really are
+ reaching the end of what this library is intended for at this stage.
+
+ Compound Types
+ --------------
+
+ Compound types (``dict``, ``tuple``, ``list``, ``set``) in general just work if
+ you pass them in as named objects. If you want to allow creation of these, the
+ ``EvalWithCompoundTypes`` class works. Just replace any use of ``SimpleEval`` with
+ that.
+
+ Other...
+ --------
+
+ The library supports both python 2 and 3.
+
+ Object attributes that start with ``_`` or ``func_`` are disallowed by default.
+ If you really need that (BE CAREFUL!), then modify the module global
+ ``simpleeval.DISALLOW_PREFIXES``.
+
+ Please read the ``test_simpleeval.py`` file for other potential gotchas or
+ details. I'm very happy to accept pull requests, suggestions, or other issues.
+ Enjoy!
+
+Keywords: eval,simple,expression,parse,ast
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Programming Language :: Python
diff --git a/README.rst b/README.rst
index 7b78c5b..d5f3179 100644
--- a/README.rst
+++ b/README.rst
@@ -21,8 +21,8 @@ off, and if there is music playing at the time.
Or if you want to allow simple formulae in a web application, but don't want to
give full eval() access, or don't want to run in javascript on the client side.
-It's deliberately very simple, just a single file you can dump into a project,
-or import from pypi (pip or easy_install).
+It's deliberately very simple, pull it in from PyPI (pip or easy_install), or
+even just a single file you can dump into a project.
Internally, it's using the amazing python ``ast`` module to parse the
expression, which allows very fine control of what is and isn't allowed. It
@@ -33,7 +33,7 @@ The only issue I know to be aware of is that you can create an expression which
takes a long time to evaluate, or which evaluating requires an awful lot of
memory, which leaves the potential for DOS attacks. There is basic protection
against this, and you can lock it down further if you desire. (see the
-`Operators` section below)
+Operators_ section below)
You should be aware of this when deploying in a public setting.
@@ -77,47 +77,54 @@ Note:
all further examples use ``>>>`` to designate python code, as if you are using
the python interactive prompt.
+.. _Operators:
+
Operators
---------
You can add operators yourself, using the ``operators`` argument, but these are
the defaults:
- +----+------------------------------------+
- | \+ | add two things. ``x + y`` |
- | | ``1 + 1`` -> ``2`` |
- +----+------------------------------------+
- | \- | subtract two things ``x - y`` |
- | | ``100 - 1`` -> ``99`` |
- +----+------------------------------------+
- | \/ | divide one thing by another |
- | | ``x / y`` |
- | | ``100/10`` -> ``10`` |
- +----+------------------------------------+
- | \* | multiple one thing by another |
- | | ``x * y`` |
- | | ``10 * 10`` -> ``100`` |
- +----+------------------------------------+
- |\*\*| 'to the power of' ``x**y`` |
- | | ``2 ** 10`` -> ``1024`` |
- +----+------------------------------------+
- | \% | modulus. (remainder) ``x % y`` |
- | | ``15 % 4`` -> ``3`` |
- +----+------------------------------------+
- | == | equals ``x == y`` |
- | | ``15 == 4`` -> ``False`` |
- +----+------------------------------------+
- | < | Less than. ``x < y`` |
- | | ``1 < 4`` -> ``True`` |
- +----+------------------------------------+
- | > | Greater than. ``x > y`` |
- | | ``1 > 4`` -> ``False`` |
- +----+------------------------------------+
- | <= | Less than or Equal to. ``x <= y`` |
- | | ``1 < 4`` -> ``True`` |
- +----+------------------------------------+
- | >= | Greater or Equal to ``x >= 21`` |
- | | ``1 >= 4`` -> ``False`` |
- +----+------------------------------------+
++--------+------------------------------------+
+| ``+`` | add two things. ``x + y`` |
+| | ``1 + 1`` -> ``2`` |
++--------+------------------------------------+
+| ``-`` | subtract two things ``x - y`` |
+| | ``100 - 1`` -> ``99`` |
++--------+------------------------------------+
+| ``/`` | divide one thing by another |
+| | ``x / y`` |
+| | ``100/10`` -> ``10`` |
++--------+------------------------------------+
+| ``*`` | multiple one thing by another |
+| | ``x * y`` |
+| | ``10 * 10`` -> ``100`` |
++--------+------------------------------------+
+| ``**`` | 'to the power of' ``x**y`` |
+| | ``2 ** 10`` -> ``1024`` |
++--------+------------------------------------+
+| ``%`` | modulus. (remainder) ``x % y`` |
+| | ``15 % 4`` -> ``3`` |
++--------+------------------------------------+
+| ``==`` | equals ``x == y`` |
+| | ``15 == 4`` -> ``False`` |
++--------+------------------------------------+
+| ``<`` | Less than. ``x < y`` |
+| | ``1 < 4`` -> ``True`` |
++--------+------------------------------------+
+| ``>`` | Greater than. ``x > y`` |
+| | ``1 > 4`` -> ``False`` |
++--------+------------------------------------+
+| ``<=`` | Less than or Equal to. ``x <= y`` |
+| | ``1 < 4`` -> ``True`` |
++--------+------------------------------------+
+| ``>=`` | Greater or Equal to ``x >= 21`` |
+| | ``1 >= 4`` -> ``False`` |
++--------+------------------------------------+
+| ``in`` | is something contained within |
+| | something else. |
+| | ``"spam" in "my breakfast"`` |
+| | -> ``False`` |
++--------+------------------------------------+
The ``^`` operator is notably missing - not because it's hard, but because it
@@ -232,8 +239,14 @@ cases):
.. code-block:: python
- s = SimpleEval()
- s.eval("1 + 1")
+ >>> s = SimpleEval()
+
+ >>> s.eval("1 + 1")
+ 2
+
+ >>> s.eval('100 * 10')
+ 1000
+
# and so on...
You can assign / edit the various options of the ``SimpleEval`` object if you
@@ -242,6 +255,9 @@ function)
.. code-block:: python
+ def boo():
+ return 'Boo!'
+
s = SimpleEval(functions={"boo": boo})
or edit them after creation:
@@ -268,6 +284,14 @@ Say. This would allow a certain level of 'scriptyness' if you had these
evaluations happening as callbacks in a program. Although you really are
reaching the end of what this library is intended for at this stage.
+Compound Types
+--------------
+
+Compound types (``dict``, ``tuple``, ``list``, ``set``) in general just work if
+you pass them in as named objects. If you want to allow creation of these, the
+``EvalWithCompoundTypes`` class works. Just replace any use of ``SimpleEval`` with
+that.
+
Other...
--------
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
index 7f0d069..76afb03 100644
--- a/setup.py
+++ b/setup.py
@@ -1,23 +1,23 @@
from setuptools import setup
-__version__ = '0.9.1'
+__version__ = '0.9.3'
setup(
- name = 'simpleeval',
- py_modules = ['simpleeval'],
- version = __version__,
- description = 'A simple, safe single expression evaluator library.',
+ name='simpleeval',
+ py_modules=['simpleeval'],
+ version=__version__,
+ description='A simple, safe single expression evaluator library.',
long_description=open('README.rst','r').read(),
- author = 'Daniel Fairhead',
- author_email = 'danthedeckie at gmail.com',
- url = 'https://github.com/danthedeckie/simpleeval',
- download_url = 'https://github.com/danthedeckie/simpleeval/tarball/' + __version__,
- keywords = ['eval', 'simple', 'expression', 'parse', 'ast'],
- test_suite = 'test_simpleeval',
- use_2to3 = True,
- classifiers = ['Development Status :: 4 - Beta',
- 'Intended Audience :: Developers',
- 'License :: OSI Approved :: MIT License',
- 'Topic :: Software Development :: Libraries :: Python Modules',
- 'Programming Language :: Python',
- ],
+ author='Daniel Fairhead',
+ author_email='danthedeckie at gmail.com',
+ url='https://github.com/danthedeckie/simpleeval',
+ download_url='https://github.com/danthedeckie/simpleeval/tarball/' + __version__,
+ keywords=['eval', 'simple', 'expression', 'parse', 'ast'],
+ test_suite='test_simpleeval',
+ use_2to3=True,
+ classifiers=['Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ 'Programming Language :: Python',
+ ],
)
diff --git a/simpleeval.egg-info/PKG-INFO b/simpleeval.egg-info/PKG-INFO
new file mode 100644
index 0000000..9933cb5
--- /dev/null
+++ b/simpleeval.egg-info/PKG-INFO
@@ -0,0 +1,323 @@
+Metadata-Version: 1.1
+Name: simpleeval
+Version: 0.9.3
+Summary: A simple, safe single expression evaluator library.
+Home-page: https://github.com/danthedeckie/simpleeval
+Author: Daniel Fairhead
+Author-email: danthedeckie at gmail.com
+License: UNKNOWN
+Download-URL: https://github.com/danthedeckie/simpleeval/tarball/0.9.3
+Description: simpleeval (Simple Eval)
+ ========================
+
+ .. image:: https://travis-ci.org/danthedeckie/simpleeval.svg?branch=master
+ :target: https://travis-ci.org/danthedeckie/simpleeval
+ :alt: Build Status
+
+ .. image:: https://coveralls.io/repos/github/danthedeckie/simpleeval/badge.svg?branch=master
+ :target: https://coveralls.io/r/danthedeckie/simpleeval?branch=master
+ :alt: Coverage Status
+
+ .. image:: https://badge.fury.io/py/simpleeval.svg
+ :target: https://badge.fury.io/py/simpleeval
+ :alt: PyPI Version
+
+ A quick single file library for easily adding evaluatable expressions into
+ python projects. Say you want to allow a user to set an alarm volume, which
+ could depend on the time of day, alarm level, how many previous alarms had gone
+ off, and if there is music playing at the time.
+
+ Or if you want to allow simple formulae in a web application, but don't want to
+ give full eval() access, or don't want to run in javascript on the client side.
+
+ It's deliberately very simple, pull it in from PyPI (pip or easy_install), or
+ even just a single file you can dump into a project.
+
+ Internally, it's using the amazing python ``ast`` module to parse the
+ expression, which allows very fine control of what is and isn't allowed. It
+ should be completely safe in terms of what operations can be performed by the
+ expression.
+
+ The only issue I know to be aware of is that you can create an expression which
+ takes a long time to evaluate, or which evaluating requires an awful lot of
+ memory, which leaves the potential for DOS attacks. There is basic protection
+ against this, and you can lock it down further if you desire. (see the
+ Operators_ section below)
+
+ You should be aware of this when deploying in a public setting.
+
+ The defaults are pretty locked down and basic, and it's very easy to add
+ whatever extra specific functionality you need (your own functions,
+ variable/name lookup, etc).
+
+ Basic Usage
+ -----------
+
+ To get very simple evaluating:
+
+ .. code-block:: python
+
+ from simpleeval import simple_eval
+
+ simple_eval("21 + 21")
+
+ returns ``42``.
+
+ Expressions can be as complex and convoluted as you want:
+
+ .. code-block:: python
+
+ simple_eval("21 + 19 / 7 + (8 % 3) ** 9")
+
+ returns ``535.714285714``.
+
+ You can add your own functions in as well.
+
+ .. code-block:: python
+
+ simple_eval("square(11)", functions={"square": lambda x: x*x})
+
+ returns ``121``.
+
+ For more details of working with functions, read further down.
+
+ Note:
+ ~~~~~
+ all further examples use ``>>>`` to designate python code, as if you are using
+ the python interactive prompt.
+
+ .. _Operators:
+
+ Operators
+ ---------
+ You can add operators yourself, using the ``operators`` argument, but these are
+ the defaults:
+
+ +--------+------------------------------------+
+ | ``+`` | add two things. ``x + y`` |
+ | | ``1 + 1`` -> ``2`` |
+ +--------+------------------------------------+
+ | ``-`` | subtract two things ``x - y`` |
+ | | ``100 - 1`` -> ``99`` |
+ +--------+------------------------------------+
+ | ``/`` | divide one thing by another |
+ | | ``x / y`` |
+ | | ``100/10`` -> ``10`` |
+ +--------+------------------------------------+
+ | ``*`` | multiple one thing by another |
+ | | ``x * y`` |
+ | | ``10 * 10`` -> ``100`` |
+ +--------+------------------------------------+
+ | ``**`` | 'to the power of' ``x**y`` |
+ | | ``2 ** 10`` -> ``1024`` |
+ +--------+------------------------------------+
+ | ``%`` | modulus. (remainder) ``x % y`` |
+ | | ``15 % 4`` -> ``3`` |
+ +--------+------------------------------------+
+ | ``==`` | equals ``x == y`` |
+ | | ``15 == 4`` -> ``False`` |
+ +--------+------------------------------------+
+ | ``<`` | Less than. ``x < y`` |
+ | | ``1 < 4`` -> ``True`` |
+ +--------+------------------------------------+
+ | ``>`` | Greater than. ``x > y`` |
+ | | ``1 > 4`` -> ``False`` |
+ +--------+------------------------------------+
+ | ``<=`` | Less than or Equal to. ``x <= y`` |
+ | | ``1 < 4`` -> ``True`` |
+ +--------+------------------------------------+
+ | ``>=`` | Greater or Equal to ``x >= 21`` |
+ | | ``1 >= 4`` -> ``False`` |
+ +--------+------------------------------------+
+ | ``in`` | is something contained within |
+ | | something else. |
+ | | ``"spam" in "my breakfast"`` |
+ | | -> ``False`` |
+ +--------+------------------------------------+
+
+
+ The ``^`` operator is notably missing - not because it's hard, but because it
+ is often mistaken for a exponent operator, not the bitwise operation that it is
+ in python. It's trivial to add back in again if you wish (using the class
+ based evaluator explained below):
+
+ .. code-block:: python
+
+ >>> import ast
+ >>> import operator
+
+ >>> s = SimpleEval()
+ >>> s.operators[ast.BitXor] = operator.xor
+
+ >>> s.eval("2 ^ 10")
+ 8
+
+ Limited Power
+ ~~~~~~~~~~~~~
+
+ Also note, the ``**`` operator has been locked down by default to have a
+ maximum input value of ``4000000``, which makes it somewhat harder to make
+ expressions which go on for ever. You can change this limit by changing the
+ ``simpleeval.POWER_MAX`` module level value to whatever is an appropriate value
+ for you (and the hardware that you're running on) or if you want to completely
+ remove all limitations, you can set the ``s.operators[ast.Pow] = operator.pow``
+ or make your own function.
+
+ On my computer, ``9**9**5`` evaluates almost instantly, but ``9**9**6`` takes
+ over 30 seconds. Since ``9**7`` is ``4782969``, and so over the ``POWER_MAX``
+ limit, it throws a ``NumberTooHigh`` exception for you. (Otherwise it would go
+ on for hours, or until the computer runs out of memory)
+
+ String Safety
+ ~~~~~~~~~~~~~
+
+ There are also limits on string length (100000 characters,
+ ``MAX_STRING_LENGTH``). This can be changed if you wish.
+
+ If Expressions
+ --------------
+
+ You can use python style ``if x then y else z`` type expressions:
+
+ .. code-block:: python
+
+ >>> simple_eval("'equal' if x == y else 'not equal'",
+ names={"x": 1, "y": 2})
+ 'not equal'
+
+ which, of course, can be nested:
+
+ .. code-block:: python
+
+ >>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
+ 'c'
+
+
+ Functions
+ ---------
+
+ You can define functions which you'd like the expresssions to have access to:
+
+ .. code-block:: python
+
+ >>> simple_eval("double(21)", functions={"double": lambda x:x*2})
+ 42
+
+ You can define "real" functions to pass in rather than lambdas, of course too,
+ and even re-name them so that expressions can be shorter
+
+ .. code-block:: python
+
+ >>> def double(x):
+ return x * 2
+ >>> simple_eval("d(100) + double(1)", functions={"d": double, "double":double})
+ 202
+
+ Names
+ -----
+
+ Sometimes it's useful to have variables available, which in python terminology
+ are called 'names'.
+
+ .. code-block:: python
+
+ >>> simple_eval("a + b", names={"a": 11, "b": 100})
+ 111
+
+ You can also hand the handling of names over to a function, if you prefer:
+
+
+ .. code-block:: python
+
+ >>> def name_handler(node):
+ return ord(node.id[0].lower(a))-96
+
+ >>> simple_eval('a + b', names=name_handler)
+ 3
+
+ That was a bit of a silly example, but you could use this for pulling values
+ from a database or file, say, or doing some kind of caching system.
+
+ Creating an Evaluator Class
+ ---------------------------
+
+ Rather than creating a new evaluator each time, if you are doing a lot of
+ evaluations, you can create a SimpleEval object, and pass it expressions each
+ time (which should be a bit quicker, and certainly more convenient for some use
+ cases):
+
+ .. code-block:: python
+
+ >>> s = SimpleEval()
+
+ >>> s.eval("1 + 1")
+ 2
+
+ >>> s.eval('100 * 10')
+ 1000
+
+ # and so on...
+
+ You can assign / edit the various options of the ``SimpleEval`` object if you
+ want to. Either assign them during creation (like the ``simple_eval``
+ function)
+
+ .. code-block:: python
+
+ def boo():
+ return 'Boo!'
+
+ s = SimpleEval(functions={"boo": boo})
+
+ or edit them after creation:
+
+ .. code-block:: python
+
+ s.names['fortytwo'] = 42
+
+ this actually means you can modify names (or functions) with functions, if you
+ really feel so inclined:
+
+ .. code-block:: python
+
+ s = SimpleEval()
+ def set_val(name, value):
+ s.names[name.value] = value.value
+ return value.value
+
+ s.functions = {'set': set_val}
+
+ s.eval("set('age', 111)")
+
+ Say. This would allow a certain level of 'scriptyness' if you had these
+ evaluations happening as callbacks in a program. Although you really are
+ reaching the end of what this library is intended for at this stage.
+
+ Compound Types
+ --------------
+
+ Compound types (``dict``, ``tuple``, ``list``, ``set``) in general just work if
+ you pass them in as named objects. If you want to allow creation of these, the
+ ``EvalWithCompoundTypes`` class works. Just replace any use of ``SimpleEval`` with
+ that.
+
+ Other...
+ --------
+
+ The library supports both python 2 and 3.
+
+ Object attributes that start with ``_`` or ``func_`` are disallowed by default.
+ If you really need that (BE CAREFUL!), then modify the module global
+ ``simpleeval.DISALLOW_PREFIXES``.
+
+ Please read the ``test_simpleeval.py`` file for other potential gotchas or
+ details. I'm very happy to accept pull requests, suggestions, or other issues.
+ Enjoy!
+
+Keywords: eval,simple,expression,parse,ast
+Platform: UNKNOWN
+Classifier: Development Status :: 4 - Beta
+Classifier: Intended Audience :: Developers
+Classifier: License :: OSI Approved :: MIT License
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Programming Language :: Python
diff --git a/simpleeval.egg-info/SOURCES.txt b/simpleeval.egg-info/SOURCES.txt
new file mode 100644
index 0000000..e0716d8
--- /dev/null
+++ b/simpleeval.egg-info/SOURCES.txt
@@ -0,0 +1,9 @@
+MANIFEST.in
+README.rst
+setup.py
+simpleeval.py
+test_simpleeval.py
+simpleeval.egg-info/PKG-INFO
+simpleeval.egg-info/SOURCES.txt
+simpleeval.egg-info/dependency_links.txt
+simpleeval.egg-info/top_level.txt
\ No newline at end of file
diff --git a/simpleeval.egg-info/dependency_links.txt b/simpleeval.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/simpleeval.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/simpleeval.egg-info/top_level.txt b/simpleeval.egg-info/top_level.txt
new file mode 100644
index 0000000..49a7c04
--- /dev/null
+++ b/simpleeval.egg-info/top_level.txt
@@ -0,0 +1 @@
+simpleeval
diff --git a/simpleeval.py b/simpleeval.py
index 010748d..fc9dc91 100644
--- a/simpleeval.py
+++ b/simpleeval.py
@@ -1,5 +1,5 @@
'''
-SimpleEval - (C) 2013-2016 Daniel Fairhead
+SimpleEval - (C) 2013-2017 Daniel Fairhead
-------------------------------------
An short, easy to use, safe and reasonably extensible expression evaluator.
@@ -38,8 +38,10 @@ Contributors:
- corro (Robin Baumgartner) (py3k)
- dratchkov (David R) (nested dicts)
- marky1991 (Mark Young) (slicing)
-- T045T (Nils Berg) (!=, py3kstr, obj.attributes)
+- T045T (Nils Berg) (!=, py3kstr, obj.
- perkinslr (Logan Perkins) (.__globals__ or .func_ breakouts)
+- impala2 (Kirill Stepanov) (massive _eval refactor)
+- gk (ugik) (Other iterables than str can DOS too, and can be made)
-------------------------------------
Usage:
@@ -117,9 +119,9 @@ class FunctionNotDefined(InvalidExpression):
class NameNotDefined(InvalidExpression):
''' a name isn't defined. '''
def __init__(self, name, expression):
+ self.name = name
self.message = "'{0}' is not defined for expression '{1}'".format(
name, expression)
- self.name = name
self.expression = expression
# pylint: disable=bad-super-call
@@ -147,8 +149,8 @@ class NumberTooHigh(InvalidExpression):
pass
-class StringTooLong(InvalidExpression):
- ''' That string is **way** too long, baby. '''
+class IterableTooLong(InvalidExpression):
+ ''' That iterable is **way** too long, baby. '''
pass
@@ -170,22 +172,22 @@ def safe_power(a, b): # pylint: disable=invalid-name
def safe_mult(a, b): # pylint: disable=invalid-name
- ''' limit the number of times a string can be repeated... '''
- if isinstance(a, str) or isinstance(b, str):
- if isinstance(a, int) and a*len(b) > MAX_STRING_LENGTH:
- raise StringTooLong("Sorry, a string that long is not allowed")
- elif isinstance(b, int) and b*len(a) > MAX_STRING_LENGTH:
- raise StringTooLong("Sorry, a string that long is not allowed")
+ ''' limit the number of times an iterable can be repeated... '''
+
+ if hasattr(a, '__len__') and b*len(a) > MAX_STRING_LENGTH:
+ raise IterableTooLong('Sorry, I will not evalute something that long.')
+ if hasattr(b, '__len__') and a*len(b) > MAX_STRING_LENGTH:
+ raise IterableTooLong('Sorry, I will not evalute something that long.')
return a * b
def safe_add(a, b): # pylint: disable=invalid-name
- ''' string length limit again '''
- if isinstance(a, str) and isinstance(b, str):
+ ''' iterable length limit again '''
+ if hasattr(a, '__len__') and hasattr(b, '__len__'):
if len(a) + len(b) > MAX_STRING_LENGTH:
- raise StringTooLong("Sorry, adding those two strings would"
- " make a too long string.")
+ raise IterableTooLong("Sorry, adding those two together would"
+ " make something too long.")
return a + b
@@ -193,11 +195,15 @@ def safe_add(a, b): # pylint: disable=invalid-name
# Defaults for the evaluator:
DEFAULT_OPERATORS = {ast.Add: safe_add, ast.Sub: op.sub, ast.Mult: safe_mult,
- ast.Div: op.truediv, ast.Pow: safe_power, ast.Mod: op.mod,
+ ast.Div: op.truediv, ast.FloorDiv: op.floordiv,
+ ast.Pow: safe_power, ast.Mod: op.mod,
ast.Eq: op.eq, ast.NotEq: op.ne,
ast.Gt: op.gt, ast.Lt: op.lt,
- ast.GtE: op.ge, ast.LtE: op.le, ast.USub: op.neg,
- ast.UAdd: op.pos}
+ ast.GtE: op.ge, ast.LtE: op.le,
+ ast.USub: op.neg, ast.UAdd: op.pos,
+ ast.In: lambda x, y: op.contains(y, x),
+ ast.NotIn: lambda x, y: not op.contains(y, x),
+ }
DEFAULT_FUNCTIONS = {"rand": random, "randint": random_int,
"int": int, "float": float,
@@ -233,6 +239,28 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
self.functions = functions
self.names = names
+ self.nodes = {
+ ast.Num: self._eval_num,
+ ast.Str: self._eval_str,
+ ast.Name: self._eval_name,
+ ast.UnaryOp: self._eval_unaryop,
+ ast.BinOp: self._eval_binop,
+ ast.BoolOp: self._eval_boolop,
+ ast.Compare: self._eval_compare,
+ ast.IfExp: self._eval_ifexp,
+ ast.Call: self._eval_call,
+ ast.keyword: self._eval_keyword,
+ ast.Name: self._eval_name,
+ ast.Subscript: self._eval_subscript,
+ ast.Attribute: self._eval_attribute,
+ ast.Index: self._eval_index,
+ ast.Slice: self._eval_slice,
+ }
+
+ # py3k stuff:
+ if hasattr(ast, 'NameConstant'):
+ self.nodes[ast.NameConstant] = self._eval_nameconstant
+
def eval(self, expr):
''' evaluate an expresssion, using the operators, functions and
names previously set up. '''
@@ -244,135 +272,184 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
# and evaluate:
return self._eval(ast.parse(expr.strip()).body[0].value)
- # pylint: disable=too-many-return-statements, too-many-branches
def _eval(self, node):
''' The internal evaluator used on each node in the parsed tree. '''
- # literals:
-
- if isinstance(node, ast.Num): # <number>
- return node.n
- elif isinstance(node, ast.Str): # <string>
- if len(node.s) > MAX_STRING_LENGTH:
- raise StringTooLong("String Literal in statement is too long!"
- " ({0}, when {1} is max)".format(
- len(node.s), MAX_STRING_LENGTH))
- return node.s
-
- # python 3 compatibility:
-
- elif (hasattr(ast, 'NameConstant') and
- isinstance(node, ast.NameConstant)): # <bool>
- return node.value
-
- # operators, functions, etc:
-
- elif isinstance(node, ast.UnaryOp): # - and + etc.
- return self.operators[type(node.op)](self._eval(node.operand))
- elif isinstance(node, ast.BinOp): # <left> <operator> <right>
- return self.operators[type(node.op)](self._eval(node.left),
- self._eval(node.right))
- elif isinstance(node, ast.BoolOp): # and & or...
- if isinstance(node.op, ast.And):
- for value in node.values:
- vout = self._eval(value)
- if not vout:
- return False
- return vout
- elif isinstance(node.op, ast.Or):
- for value in node.values:
- vout = self._eval(value)
- if vout:
- return vout
- return False
+ try:
+ handler = self.nodes[type(node)]
+ except KeyError:
+ raise FeatureNotAvailable("Sorry, {0} is not available in this "
+ "evaluator".format(type(node).__name__))
- elif isinstance(node, ast.Compare): # 1 < 2, a == b...
- left = self._eval(node.left)
- for operation, comp in zip(node.ops, node.comparators):
- right = self._eval(comp)
- if self.operators[type(operation)](left, right):
- left = right # Hi Dr. Seuss...
- else:
- return False
- return True
+ return handler(node)
- elif isinstance(node, ast.IfExp): # x if y else z
- return self._eval(node.body) if self._eval(node.test) \
- else self._eval(node.orelse)
- elif isinstance(node, ast.Call): # function...
- try:
- if isinstance(node.func, ast.Name):
- return self.functions[node.func.id](*(self._eval(a)
- for a in node.args))
- elif isinstance(node.func, ast.Attribute):
- return self._eval(node.func)(*(self._eval(a)
- for a in node.args))
- except KeyError:
- raise FunctionNotDefined(node.func.id, self.expr)
+ def _eval_num(self, node):
+ return node.n
- # variables/names:
+ def _eval_str(self, node):
+ if len(node.s) > MAX_STRING_LENGTH:
+ raise IterableTooLong("String Literal in statement is too long!"
+ " ({0}, when {1} is max)".format(
+ len(node.s), MAX_STRING_LENGTH))
+ return node.s
- elif isinstance(node, ast.Name): # a, b, c...
- try:
- # This happens at least for slicing
- # This is a safe thing to do because it is impossible
- # that there is a true exression assigning to none
- # (the compiler rejects it, so you can't even pass that
- # to ast.parse)
- if node.id == "None":
- return None
- elif isinstance(self.names, dict):
- return self.names[node.id]
- elif callable(self.names):
- return self.names(node)
- else:
- raise InvalidExpression(
- 'Trying to use name (variable) "{0}"'
- ' when no "names" defined for'
- ' evaluator'.format(node.id))
+ def _eval_nameconstant(self, node):
+ return node.value
- except KeyError:
- raise NameNotDefined(node.id, self.expr)
+ def _eval_unaryop(self, node):
+ return self.operators[type(node.op)](self._eval(node.operand))
- elif isinstance(node, ast.Subscript): # b[1]
- return self._eval(node.value)[self._eval(node.slice)]
+ def _eval_binop(self, node):
+ return self.operators[type(node.op)](self._eval(node.left),
+ self._eval(node.right))
- elif isinstance(node, ast.Attribute): # a.b.c
- for prefix in DISALLOW_PREFIXES:
- if node.attr.startswith(prefix):
- raise FeatureNotAvailable(
- "Sorry, access to __attributes "
- " or func_ attributes is not available. "
- "({0})".format(node.attr))
+ def _eval_boolop(self, node):
+ if isinstance(node.op, ast.And):
+ for value in node.values:
+ vout = self._eval(value)
+ if not vout:
+ return False
+ return vout
+ elif isinstance(node.op, ast.Or):
+ for value in node.values:
+ vout = self._eval(value)
+ if vout:
+ return vout
+ return False
+
+ def _eval_compare(self, node):
+ left = self._eval(node.left)
+ for operation, comp in zip(node.ops, node.comparators):
+ right = self._eval(comp)
+ if self.operators[type(operation)](left, right):
+ left = right # Hi Dr. Seuss...
+ else:
+ return False
+ return True
- try:
- return self._eval(node.value)[node.attr]
- except (KeyError, TypeError):
- pass
+ def _eval_ifexp(self, node):
+ return self._eval(node.body) if self._eval(node.test) \
+ else self._eval(node.orelse)
- # Maybe the base object is an actual object, not just a dict
- try:
- return getattr(self._eval(node.value), node.attr)
- except (AttributeError, TypeError):
- pass
-
- # If it is neither, raise an exception
- raise AttributeDoesNotExist(node.attr, self.expr)
-
- elif isinstance(node, ast.Index):
- return self._eval(node.value)
- elif isinstance(node, ast.Slice):
- lower = upper = step = None
- if node.lower is not None:
- lower = self._eval(node.lower)
- if node.upper is not None:
- upper = self._eval(node.upper)
- if node.step is not None:
- step = self._eval(node.step)
- return slice(lower, upper, step)
+ def _eval_call(self, node):
+ if isinstance(node.func, ast.Attribute):
+ func = self._eval(node.func)
else:
- raise FeatureNotAvailable("Sorry, {0} is not available in this "
- "evaluator".format(type(node).__name__))
+ try:
+ func = self.functions[node.func.id]
+ except KeyError:
+ raise FunctionNotDefined(node.func.id, self.expr)
+
+ return func(
+ *(self._eval(a) for a in node.args),
+ **dict(self._eval(k) for k in node.keywords)
+ )
+
+ def _eval_keyword(self, node):
+ return node.arg, self._eval(node.value)
+
+ def _eval_name(self, node):
+ try:
+ # This happens at least for slicing
+ # This is a safe thing to do because it is impossible
+ # that there is a true exression assigning to none
+ # (the compiler rejects it, so you can't even
+ # pass that to ast.parse)
+ if node.id == "None":
+ return None
+ elif isinstance(self.names, dict):
+ return self.names[node.id]
+ elif callable(self.names):
+ return self.names(node)
+ else:
+ raise InvalidExpression('Trying to use name (variable) "{0}"'
+ ' when no "names" defined for'
+ ' evaluator'.format(node.id))
+
+ except KeyError:
+ raise NameNotDefined(node.id, self.expr)
+
+ def _eval_subscript(self, node):
+
+ container = self._eval(node.value)
+ key = self._eval(node.slice)
+ try:
+ return container[key]
+ except KeyError:
+ raise
+
+ return self._eval(node.value)[self._eval(node.slice)]
+
+ def _eval_attribute(self, node):
+ for prefix in DISALLOW_PREFIXES:
+ if node.attr.startswith(prefix):
+ raise FeatureNotAvailable(
+ "Sorry, access to __attributes "
+ " or func_ attributes is not available. "
+ "({0})".format(node.attr))
+
+ try:
+ return self._eval(node.value)[node.attr]
+ except (KeyError, TypeError):
+ pass
+
+ # Maybe the base object is an actual object, not just a dict
+ try:
+ return getattr(self._eval(node.value), node.attr)
+ except (AttributeError, TypeError):
+ pass
+
+ # If it is neither, raise an exception
+ raise AttributeDoesNotExist(node.attr, self.expr)
+
+ def _eval_index(self, node):
+ return self._eval(node.value)
+
+ def _eval_slice(self, node):
+ lower = upper = step = None
+ if node.lower is not None:
+ lower = self._eval(node.lower)
+ if node.upper is not None:
+ upper = self._eval(node.upper)
+ if node.step is not None:
+ step = self._eval(node.step)
+ return slice(lower, upper, step)
+
+
+class EvalWithCompoundTypes(SimpleEval):
+ '''
+ SimpleEval with additional Compound Types, and their respective
+ function editions. (list, tuple, dict, set).
+ '''
+
+ def __init__(self, *args, **kwargs):
+ super(EvalWithCompoundTypes, self).__init__(*args, **kwargs)
+
+ self.functions.update(
+ list=list,
+ tuple=tuple,
+ dict=dict,
+ set=set)
+
+ self.nodes.update({
+ ast.Dict: self._eval_dict,
+ ast.Tuple: self._eval_tuple,
+ ast.List: self._eval_list,
+ ast.Set: self._eval_set
+ })
+
+ def _eval_dict(self, node):
+ return {self._eval(k): self._eval(v)
+ for (k, v) in zip(node.keys, node.values)}
+
+ def _eval_tuple(self, node):
+ return tuple(self._eval(x) for x in node.elts)
+
+ def _eval_list(self, node):
+ return list(self._eval(x) for x in node.elts)
+
+ def _eval_set(self, node):
+ return set(self._eval(x) for x in node.elts)
def simple_eval(expr, operators=None, functions=None, names=None):
diff --git a/test_simpleeval.py b/test_simpleeval.py
index 2fe6d30..ca07b6b 100644
--- a/test_simpleeval.py
+++ b/test_simpleeval.py
@@ -7,9 +7,15 @@
'''
# pylint: disable=too-many-public-methods, missing-docstring
-import unittest, operator, ast
+import unittest
+import operator
+import ast
import simpleeval
-from simpleeval import SimpleEval, NameNotDefined, InvalidExpression, AttributeDoesNotExist, simple_eval
+from simpleeval import (
+ SimpleEval, EvalWithCompoundTypes, NameNotDefined,
+ InvalidExpression, AttributeDoesNotExist, simple_eval
+)
+
class DRYTest(unittest.TestCase):
''' Stuff we need to do every test, let's do here instead..
@@ -19,10 +25,11 @@ class DRYTest(unittest.TestCase):
''' initialize a SimpleEval '''
self.s = SimpleEval()
- def t(self, expr, shouldbe): #pylint: disable=invalid-name
+ def t(self, expr, shouldbe): # pylint: disable=invalid-name
''' test an evaluation of an expression against an expected answer '''
return self.assertEqual(self.s.eval(expr), shouldbe)
+
class TestBasic(DRYTest):
''' Simple expressions. '''
@@ -48,8 +55,8 @@ class TestBasic(DRYTest):
self.s.names = {'out': True, 'position': 3}
self.t('(out and position <=6 and -10)'
- ' or (out and position > 6 and -5)'
- ' or (not out and 15)', -10)
+ ' or (out and position > 6 and -5)'
+ ' or (not out and 15)', -10)
def test_maths_with_floats(self):
self.t("11.02 - 9.1", 1.92)
@@ -70,9 +77,9 @@ class TestBasic(DRYTest):
self.t("1.09 <= 1967392", True)
self.t('1 < 2 < 3 < 4', 1 < 2 < 3 < 4)
- self.t('1 < 2 > 3 < 4', 1 < 2 > 3 < 4)
+ self.t('1 < 2 > 3 < 4', 1 < 2 > 3 < 4)
- self.t('1<2<1+1', 1<2<1+1)
+ self.t('1<2<1+1', 1 < 2 < 1 + 1)
self.t('1 == 1 == 2', 1 == 1 == 2)
self.t('1 == 1 < 2', 1 == 1 < 2)
@@ -101,7 +108,8 @@ class TestBasic(DRYTest):
self.t('"Test Stuff!" + str(11)', "Test Stuff!11")
def test_slicing(self):
- self.s.operators[ast.Slice] = operator.getslice if hasattr(operator, "getslice") else operator.getitem
+ self.s.operators[ast.Slice] = (operator.getslice \
+ if hasattr(operator, "getslice") else operator.getitem)
self.t("'hello'[1]", "e")
self.t("'hello'[:]", "hello")
self.t("'hello'[:3]", "hel")
@@ -117,6 +125,15 @@ class TestBasic(DRYTest):
self.t("'hello'[1:3:1]", "el")
self.t("'hello'[1:3:2]", "e")
+ with self.assertRaises(IndexError):
+ self.t("'hello'[90]", 0)
+
+ self.t('"spam" not in "my breakfast"', True)
+ self.t('"silly" in "ministry of silly walks"', True)
+ self.t('"I" not in "team"', True)
+ self.t('"U" in "RUBBISH"', True)
+
+
class TestFunctions(DRYTest):
''' Functions for expressions to play with '''
@@ -166,10 +183,61 @@ class TestFunctions(DRYTest):
# I don't know how to further test these functions. Ideas?
+ def test_methods(self):
+ self.t('"WORD".lower()', 'word')
+ self.t('"{}:{}".format(1, 2)', '1:2')
+
+ def test_function_args_none(self):
+ def foo():
+ return 42
+
+ self.s.functions['foo'] = foo
+ self.t('foo()', 42)
+
+ def test_function_args_required(self):
+ def foo(toret):
+ return toret
+
+ self.s.functions['foo'] = foo
+ with self.assertRaises(TypeError):
+ self.t('foo()', 42)
+
+ self.t('foo(12)', 12)
+ self.t('foo(toret=100)', 100)
+
+ def test_function_args_defaults(self):
+ def foo(toret=9999):
+ return toret
+
+ self.s.functions['foo'] = foo
+ self.t('foo()', 9999)
+
+ self.t('foo(12)', 12)
+ self.t('foo(toret=100)', 100)
+
+ def test_function_args_bothtypes(self):
+ def foo(mult, toret=100):
+ return toret * mult
+
+ self.s.functions['foo'] = foo
+ with self.assertRaises(TypeError):
+ self.t('foo()', 9999)
+
+ self.t('foo(2)', 200)
+
+ with self.assertRaises(TypeError):
+ self.t('foo(toret=100)', 100)
+
+ self.t('foo(4, toret=4)', 16)
+ self.t('foo(mult=2, toret=4)', 8)
+ self.t('foo(2, 10)', 20)
+
+
class TestOperators(DRYTest):
''' Test adding in new operators, removing them, make sure it works. '''
pass
+
class TestTryingToBreakOut(DRYTest):
''' Test various weird methods to break the security sandbox... '''
@@ -199,36 +267,55 @@ class TestTryingToBreakOut(DRYTest):
simpleeval.MAX_POWER = old_max
+ def test_encode_bignums(self):
+ # thanks gk
+ if hasattr(1, 'from_bytes'): # python3 only
+ with self.assertRaises(simpleeval.IterableTooLong):
+ self.t('(1).from_bytes(("123123123123123123123123").encode()*999999, "big")', 0)
+
def test_string_length(self):
- with self.assertRaises(simpleeval.StringTooLong):
+ with self.assertRaises(simpleeval.IterableTooLong):
self.t("50000*'text'", 0)
- with self.assertRaises(simpleeval.StringTooLong):
+ with self.assertRaises(simpleeval.IterableTooLong):
self.t("'text'*50000", 0)
- with self.assertRaises(simpleeval.StringTooLong):
+ with self.assertRaises(simpleeval.IterableTooLong):
self.t("('text'*50000)*1000", 0)
- with self.assertRaises(simpleeval.StringTooLong):
+ with self.assertRaises(simpleeval.IterableTooLong):
self.t("(50000*'text')*1000", 0)
- self.t("'stuff'*20000", 20000*'stuff')
+ self.t("'stuff'*20000", 20000 * 'stuff')
- self.t("20000*'stuff'", 20000*'stuff')
+ self.t("20000*'stuff'", 20000 * 'stuff')
- with self.assertRaises(simpleeval.StringTooLong):
+ with self.assertRaises(simpleeval.IterableTooLong):
self.t("('stuff'*20000) + ('stuff'*20000) ", 0)
- with self.assertRaises(simpleeval.StringTooLong):
- self.t("'stuff'*100000", 100000*'stuff')
+ with self.assertRaises(simpleeval.IterableTooLong):
+ self.t("'stuff'*100000", 100000 * 'stuff')
- with self.assertRaises(simpleeval.StringTooLong):
- self.t("'" + (10000*"stuff") +"'*100", 0)
+ with self.assertRaises(simpleeval.IterableTooLong):
+ self.t("'" + (10000 * "stuff") +"'*100", 0)
- with self.assertRaises(simpleeval.StringTooLong):
+ with self.assertRaises(simpleeval.IterableTooLong):
self.t("'" + (50000 * "stuff") + "'", 0)
+ def test_bytes_array_test(self):
+ self.t("'20000000000000000000'.encode() * 5000",
+ '20000000000000000000'.encode() * 5000)
+
+ with self.assertRaises(simpleeval.IterableTooLong):
+ self.t("'123121323123131231223'.encode() * 5000", 20)
+
+ def test_list_length_test(self):
+ self.t("'spam spam spam'.split() * 5000", ['spam', 'spam', 'spam'] * 5000)
+
+ with self.assertRaises(simpleeval.IterableTooLong):
+ self.t("('spam spam spam' * 5000).split() * 5000", None)
+
def test_python_stuff(self):
''' other various pythony things. '''
# it only evaluates the first statement:
@@ -239,12 +326,11 @@ class TestTryingToBreakOut(DRYTest):
with self.assertRaises(simpleeval.FeatureNotAvailable):
self.t("[x for x in (1, 2, 3)]", (1, 2, 3))
-
def test_function_globals_breakout(self):
''' by accessing function.__globals__ or func_... '''
# thanks perkinslr.
- self.s.functions['x'] = lambda y:y+y
+ self.s.functions['x'] = lambda y: y + y
self.t('x(100)', 200)
with self.assertRaises(simpleeval.FeatureNotAvailable):
@@ -279,6 +365,71 @@ class TestTryingToBreakOut(DRYTest):
simpleeval.DISALLOW_PREFIXES = dis
+ def test_builtins_private_access(self):
+ # explicit attempt of the exploit from perkinslr
+ with self.assertRaises(simpleeval.FeatureNotAvailable):
+ self.t("True.__class__.__class__.__base__.__subclasses__()[-1].__init__.func_globals['sys'].exit(1)", 42)
+
+
+class TestCompoundTypes(DRYTest):
+ ''' Test the compound-types edition of the library '''
+
+ def setUp(self):
+ self.s = EvalWithCompoundTypes()
+
+ def test_dict(self):
+ self.t('{}', {})
+ self.t('{"foo": "bar"}', {'foo': 'bar'})
+ self.t('{"foo": "bar"}["foo"]', 'bar')
+ self.t('dict()', {})
+ self.t('dict(a=1)', {'a': 1})
+
+ def test_dict_contains(self):
+ self.t('{"a":22}["a"]', 22)
+ with self.assertRaises(KeyError):
+ self.t('{"a":22}["b"]', 22)
+
+ self.t('{"a": 24}.get("b", 11)', 11)
+ self.t('"a" in {"a": 24}', True)
+
+ def test_tuple(self):
+ self.t('()', ())
+ self.t('(1,)', (1,))
+ self.t('(1, 2, 3, 4, 5, 6)', (1, 2, 3, 4, 5, 6))
+ self.t('(1, 2) + (3, 4)', (1, 2, 3, 4))
+ self.t('(1, 2, 3)[1]', 2)
+ self.t('tuple()', ())
+ self.t('tuple("foo")', ('f', 'o', 'o'))
+
+ def test_tuple_contains(self):
+ self.t('("a","b")[1]', 'b')
+ with self.assertRaises(IndexError):
+ self.t('("a","b")[5]', 'b')
+ self.t('"a" in ("b","c","a")', True)
+
+ def test_list(self):
+ self.t('[]', [])
+ self.t('[1]', [1])
+ self.t('[1, 2, 3, 4, 5]', [1, 2, 3, 4, 5])
+ self.t('[1, 2, 3][1]', 2)
+ self.t('list()', [])
+ self.t('list("foo")', ['f', 'o', 'o'])
+
+ def test_list_contains(self):
+ self.t('["a","b"][1]', 'b')
+ with self.assertRaises(IndexError):
+ self.t('("a","b")[5]', 'b')
+
+ self.t('"b" in ["a","b"]', True)
+
+ def test_set(self):
+ self.t('{1}', {1})
+ self.t('{1, 2, 1, 2, 1, 2, 1}', {1, 2})
+ self.t('set()', set())
+ self.t('set("foo")', {'f', 'o'})
+
+ self.t('2 in {1,2,3,4}', True)
+ self.t('22 not in {1,2,3,4}', True)
class TestNames(DRYTest):
@@ -299,12 +450,11 @@ class TestNames(DRYTest):
with self.assertRaises(InvalidExpression):
self.t('s', 21)
- self.s.names = {'a' : {'b': {'c': 42}}}
+ self.s.names = {'a': {'b': {'c': 42}}}
with self.assertRaises(AttributeDoesNotExist):
self.t('a.b.d**2', 42)
-
def test_dict(self):
''' using a normal dict for names lookup '''
@@ -357,7 +507,7 @@ class TestNames(DRYTest):
# nested dict
- self.s.names = {'a' : {'b': {'c': 42}}}
+ self.s.names = {'a': {'b': {'c': 42}}}
self.t("a.b.c*2", 84)
@@ -382,7 +532,7 @@ class TestNames(DRYTest):
o.c = TestObject()
o.c.d = 9001
- self.s.names = {'o' : o}
+ self.s.names = {'o': o}
self.t('o', o)
self.t('o.a', 23)
@@ -396,7 +546,7 @@ class TestNames(DRYTest):
def test_func(self):
''' using a function for 'names lookup' '''
- def resolver(node): # pylint: disable=unused-argument
+ def resolver(node): # pylint: disable=unused-argument
''' all names now equal 1024! '''
return 1024
@@ -449,7 +599,6 @@ class Test_whitespace(DRYTest):
self.t(" \t 200 + 200 ", 400)
-
class Test_simple_eval(unittest.TestCase):
''' test the 'simple_eval' wrapper function '''
def test_basic_run(self):
@@ -459,5 +608,6 @@ class Test_simple_eval(unittest.TestCase):
self.assertEqual(simple_eval('rand() < 1.0 and rand() > -0.01'), True)
self.assertEqual(simple_eval('randint(200) < 200 and rand() > 0'), True)
+
if __name__ == '__main__':
unittest.main()
--
simpleeval
More information about the tryton-debian-vcs
mailing list