[tryton-debian-vcs] simpleeval branch upstream updated. upstream/0.8.7-1-g0b2f046
Mathias Behrle
tryton-debian-vcs at alioth.debian.org
Thu Dec 1 22:40:48 UTC 2016
The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/simpleeval.git;a=commitdiff;h=upstream/0.8.7-1-g0b2f046
commit 0b2f046823582609e80ff2411407624b9ba51f53
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Thu Dec 1 23:20:12 2016 +0100
Adding upstream version 0.9.1.
Signed-off-by: Mathias Behrle <mathiasb at m9s.biz>
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..3aad258
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,3 @@
+[run]
+branch = True
+omit = /home/travis/virtualenv/*
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..b00a4fa
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+build
+dist
+MANIFEST
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..09fdbaf
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,19 @@
+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
new file mode 100644
index 0000000..40e7560
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,21 @@
+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
deleted file mode 100644
index d505fd3..0000000
--- a/PKG-INFO
+++ /dev/null
@@ -1,239 +0,0 @@
-Metadata-Version: 1.1
-Name: simpleeval
-Version: 0.8.7
-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.8.7
-Description: simpleeval (Simple Eval)
- ========================
-
- 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, just a single file you can dump into a project, or import
- from pypi (pip or easy_install).
-
- 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: ::
-
- from simpleeval import simple_eval
-
- simple_eval("21 + 21")
-
- returns ``42``.
-
- Expressions can be as complex and convoluted as you want: ::
-
- simple_eval("21 + 19 / 7 + (8 % 3) ** 9")
-
- returns ``535.714285714``.
-
- You can add your own functions in as well. ::
-
- 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
- ---------
- 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`` |
- +----+------------------------------------+
-
-
- 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): ::
-
- >>> 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: ::
-
- >>> simple_eval("'equal' if x == y else 'not equal'",
- names={"x": 1, "y": 2})
- 'not equal'
-
- which, of course, can be nested: ::
-
- >>> 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: ::
-
- >>> 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 ::
-
- >>> 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'. ::
-
- >>> 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: ::
-
- >>> 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): ::
-
- s = SimpleEval()
- s.eval("1 + 1")
- # 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) ::
-
- s = SimpleEval(functions={"boo": boo})
-
- or edit them after creation: ::
-
- s.names['fortytwo'] = 42
-
- this actually means you can modify names (or functions) with functions, if you really feel so inclined: ::
-
- 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.
-
- Other...
- --------
-
- The library supports both python 2 and 3 using the 2to3 converter.
-
- 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!
-
- .. image:: https://coveralls.io/repos/danthedeckie/simpleeval/badge.png :target: https://coveralls.io/r/danthedeckie/simpleeval
-
-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 266f0b6..7b78c5b 100644
--- a/README.rst
+++ b/README.rst
@@ -1,35 +1,52 @@
simpleeval (Simple Eval)
========================
-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.
+.. 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, just a single file you can dump into a project, or import
-from pypi (pip or easy_install).
+It's deliberately very simple, just a single file you can dump into a project,
+or import from pypi (pip or easy_install).
-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.
+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)
+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).
+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: ::
+To get very simple evaluating:
+
+.. code-block:: python
from simpleeval import simple_eval
@@ -37,13 +54,17 @@ To get very simple evaluating: ::
returns ``42``.
-Expressions can be as complex and convoluted as you want: ::
+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. ::
+You can add your own functions in as well.
+
+.. code-block:: python
simple_eval("square(11)", functions={"square": lambda x: x*x})
@@ -53,12 +74,13 @@ 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.
+all further examples use ``>>>`` to designate python code, as if you are using
+the python interactive prompt.
Operators
---------
-You can add operators yourself, using the ``operators`` argument, but these are the defaults:
+You can add operators yourself, using the ``operators`` argument, but these are
+the defaults:
+----+------------------------------------+
| \+ | add two things. ``x + y`` |
@@ -98,9 +120,12 @@ You can add operators yourself, using the ``operators`` argument, but these are
+----+------------------------------------+
-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): ::
+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
@@ -114,34 +139,39 @@ if you wish (using the class based evaluator explained below): ::
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.
+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)
+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.
+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: ::
+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: ::
+which, of course, can be nested:
+
+.. code-block:: python
>>> simple_eval("'a' if 1 == 2 else 'b' if 2 == 3 else 'c'")
'c'
@@ -150,12 +180,17 @@ which, of course, can be nested: ::
Functions
---------
-You can define functions which you'd like the expresssions to have access to: ::
+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 ::
+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
@@ -164,13 +199,19 @@ You can define "real" functions to pass in rather than lambdas, of course too, a
Names
-----
-
-Sometimes it's useful to have variables available, which in python terminology are called '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: ::
+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
@@ -178,28 +219,41 @@ You can also hand the handling of names over to a function, if you prefer: ::
>>> 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.
+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): ::
+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")
# 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) ::
+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
s = SimpleEval(functions={"boo": boo})
-or edit them after creation: ::
+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: ::
+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):
@@ -210,13 +264,19 @@ this actually means you can modify names (or functions) with functions, if you r
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.
+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.
Other...
--------
-The library supports both python 2 and 3 using the 2to3 converter.
+The library supports both python 2 and 3.
-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!
+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``.
-.. image:: https://coveralls.io/repos/danthedeckie/simpleeval/badge.png :target: https://coveralls.io/r/danthedeckie/simpleeval
+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!
diff --git a/setup.cfg b/setup.cfg
deleted file mode 100644
index 861a9f5..0000000
--- a/setup.cfg
+++ /dev/null
@@ -1,5 +0,0 @@
-[egg_info]
-tag_build =
-tag_date = 0
-tag_svn_revision = 0
-
diff --git a/setup.py b/setup.py
index 0e71ef7..7f0d069 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,5 @@
from setuptools import setup
-__version__ = '0.8.7'
+__version__ = '0.9.1'
setup(
name = 'simpleeval',
diff --git a/simpleeval.egg-info/PKG-INFO b/simpleeval.egg-info/PKG-INFO
deleted file mode 100644
index d505fd3..0000000
--- a/simpleeval.egg-info/PKG-INFO
+++ /dev/null
@@ -1,239 +0,0 @@
-Metadata-Version: 1.1
-Name: simpleeval
-Version: 0.8.7
-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.8.7
-Description: simpleeval (Simple Eval)
- ========================
-
- 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, just a single file you can dump into a project, or import
- from pypi (pip or easy_install).
-
- 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: ::
-
- from simpleeval import simple_eval
-
- simple_eval("21 + 21")
-
- returns ``42``.
-
- Expressions can be as complex and convoluted as you want: ::
-
- simple_eval("21 + 19 / 7 + (8 % 3) ** 9")
-
- returns ``535.714285714``.
-
- You can add your own functions in as well. ::
-
- 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
- ---------
- 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`` |
- +----+------------------------------------+
-
-
- 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): ::
-
- >>> 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: ::
-
- >>> simple_eval("'equal' if x == y else 'not equal'",
- names={"x": 1, "y": 2})
- 'not equal'
-
- which, of course, can be nested: ::
-
- >>> 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: ::
-
- >>> 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 ::
-
- >>> 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'. ::
-
- >>> 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: ::
-
- >>> 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): ::
-
- s = SimpleEval()
- s.eval("1 + 1")
- # 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) ::
-
- s = SimpleEval(functions={"boo": boo})
-
- or edit them after creation: ::
-
- s.names['fortytwo'] = 42
-
- this actually means you can modify names (or functions) with functions, if you really feel so inclined: ::
-
- 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.
-
- Other...
- --------
-
- The library supports both python 2 and 3 using the 2to3 converter.
-
- 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!
-
- .. image:: https://coveralls.io/repos/danthedeckie/simpleeval/badge.png :target: https://coveralls.io/r/danthedeckie/simpleeval
-
-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
deleted file mode 100644
index e0716d8..0000000
--- a/simpleeval.egg-info/SOURCES.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-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
deleted file mode 100644
index 8b13789..0000000
--- a/simpleeval.egg-info/dependency_links.txt
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/simpleeval.egg-info/top_level.txt b/simpleeval.egg-info/top_level.txt
deleted file mode 100644
index 49a7c04..0000000
--- a/simpleeval.egg-info/top_level.txt
+++ /dev/null
@@ -1 +0,0 @@
-simpleeval
diff --git a/simpleeval.py b/simpleeval.py
index 81e4193..010748d 100644
--- a/simpleeval.py
+++ b/simpleeval.py
@@ -1,5 +1,5 @@
'''
-SimpleEval - (C) 2013/2015 Daniel Fairhead
+SimpleEval - (C) 2013-2016 Daniel Fairhead
-------------------------------------
An short, easy to use, safe and reasonably extensible expression evaluator.
@@ -39,6 +39,7 @@ Contributors:
- dratchkov (David R) (nested dicts)
- marky1991 (Mark Young) (slicing)
- T045T (Nils Berg) (!=, py3kstr, obj.attributes)
+- perkinslr (Logan Perkins) (.__globals__ or .func_ breakouts)
-------------------------------------
Usage:
@@ -87,74 +88,88 @@ from random import random
# Module wide 'globals'
MAX_STRING_LENGTH = 100000
-MAX_POWER = 4000000 # highest exponent
+MAX_POWER = 4000000 # highest exponent
+DISALLOW_PREFIXES = ['_', 'func_']
+
PYTHON3 = sys.version_info[0] == 3
########################################
# Exceptions:
+
class InvalidExpression(Exception):
''' Generic Exception '''
pass
+
class FunctionNotDefined(InvalidExpression):
''' sorry! That function isn't defined! '''
def __init__(self, func_name, expression):
self.message = "Function '{0}' not defined," \
- " for expression '{1}'.".format( func_name, expression)
+ " for expression '{1}'.".format(func_name, expression)
self.func_name = func_name
self.expression = expression
# pylint: disable=bad-super-call
super(InvalidExpression, self).__init__(self.message)
+
class NameNotDefined(InvalidExpression):
''' a name isn't defined. '''
def __init__(self, name, expression):
self.message = "'{0}' is not defined for expression '{1}'".format(
- name, expression)
+ name, expression)
self.name = name
self.expression = expression
# pylint: disable=bad-super-call
super(InvalidExpression, self).__init__(self.message)
+
class AttributeDoesNotExist(InvalidExpression):
'''attribute does not exist'''
def __init__(self, attr, expression):
- self.message = "Attribute '{0}' does not exist in expression '{1}'".format(
- attr, expression)
+ self.message = \
+ "Attribute '{0}' does not exist in expression '{1}'".format(
+ attr, expression)
self.attr = attr
self.expression = expression
-
+
+
class FeatureNotAvailable(InvalidExpression):
''' What you're trying to do is not allowed. '''
pass
+
class NumberTooHigh(InvalidExpression):
''' Sorry! That number is too high. I don't want to spend the
next 10 years evaluating this expression! '''
pass
+
class StringTooLong(InvalidExpression):
''' That string is **way** too long, baby. '''
pass
+
########################################
# Default simple functions to include:
+
def random_int(top):
''' return a random int below <top> '''
return int(random() * top)
-def safe_power(a, b): # pylint: disable=invalid-name
+
+def safe_power(a, b): # pylint: disable=invalid-name
''' a limited exponent/to-the-power-of function, for safety reasons '''
if abs(a) > MAX_POWER or abs(b) > MAX_POWER:
raise NumberTooHigh("Sorry! I don't want to evaluate {0} ** {1}"
.format(a, b))
return a ** b
-def safe_mult(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:
@@ -164,7 +179,8 @@ def safe_mult(a, b): # pylint: disable=invalid-name
return a * b
-def safe_add(a, b): # pylint: disable=invalid-name
+
+def safe_add(a, b): # pylint: disable=invalid-name
''' string length limit again '''
if isinstance(a, str) and isinstance(b, str):
if len(a) + len(b) > MAX_STRING_LENGTH:
@@ -178,19 +194,22 @@ def safe_add(a, b): # pylint: disable=invalid-name
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.Eq: op.eq, ast.NotEq: op.ne, ast.Gt: op.gt, ast.Lt: op.lt,
+ 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}
DEFAULT_FUNCTIONS = {"rand": random, "randint": random_int,
- "int": int, "float": float, "str": str if PYTHON3 else unicode}
+ "int": int, "float": float,
+ "str": str if PYTHON3 else unicode}
DEFAULT_NAMES = {"True": True, "False": False}
########################################
# And the actual evaluator:
-class SimpleEval(object): # pylint: disable=too-few-public-methods
+
+class SimpleEval(object): # pylint: disable=too-few-public-methods
''' A very simple expression parser.
>>> s = SimpleEval()
>>> s.eval("20 + 30 - ( 10 * 5)")
@@ -223,62 +242,83 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
self.expr = expr
# and evaluate:
- return self._eval(ast.parse(expr).body[0].value)
+ 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 eval function used on each node in the parsed tree. '''
+ ''' The internal evaluator used on each node in the parsed tree. '''
# literals:
- if isinstance(node, ast.Num): # <number>
+ if isinstance(node, ast.Num): # <number>
return node.n
- elif isinstance(node, ast.Str): # <string>
+ 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))
+ len(node.s), MAX_STRING_LENGTH))
return node.s
# python 3 compatibility:
elif (hasattr(ast, 'NameConstant') and
- isinstance(node, ast.NameConstant)): # <bool>
+ isinstance(node, ast.NameConstant)): # <bool>
return node.value
# operators, functions, etc:
- elif isinstance(node, ast.UnaryOp): # - and + 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>
+ 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...
+ self._eval(node.right))
+ elif isinstance(node, ast.BoolOp): # and & or...
if isinstance(node.op, ast.And):
- return all((self._eval(v) for v in node.values))
+ for value in node.values:
+ vout = self._eval(value)
+ if not vout:
+ return False
+ return vout
elif isinstance(node.op, ast.Or):
- return any((self._eval(v) for v in node.values))
- elif isinstance(node, ast.Compare): # 1 < 2, a == b...
- return self.operators[type(node.ops[0])](self._eval(node.left),
- self._eval(node.comparators[0]))
- elif isinstance(node, ast.IfExp): # x if y else z
+ for value in node.values:
+ vout = self._eval(value)
+ if vout:
+ return vout
+ return False
+
+ 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
+
+ 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...
+ elif isinstance(node, ast.Call): # function...
try:
- return self.functions[node.func.id](*(self._eval(a)
- for a in node.args))
+ 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)
# variables/names:
- elif isinstance(node, ast.Name): # a, b, c...
+ 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)
+ # 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):
@@ -286,17 +326,25 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
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))
+ 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)
- elif isinstance(node, ast.Subscript): # b[1]
+ elif isinstance(node, ast.Subscript): # b[1]
return self._eval(node.value)[self._eval(node.slice)]
- elif isinstance(node, ast.Attribute): # a.b.c
+ 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))
+
try:
return self._eval(node.value)[node.attr]
except (KeyError, TypeError):
@@ -324,7 +372,8 @@ class SimpleEval(object): # pylint: disable=too-few-public-methods
return slice(lower, upper, step)
else:
raise FeatureNotAvailable("Sorry, {0} is not available in this "
- "evaluator".format(type(node).__name__ ))
+ "evaluator".format(type(node).__name__))
+
def simple_eval(expr, operators=None, functions=None, names=None):
''' Simply evaluate an expresssion '''
diff --git a/test_simpleeval.py b/test_simpleeval.py
index ec1689a..2fe6d30 100644
--- a/test_simpleeval.py
+++ b/test_simpleeval.py
@@ -40,10 +40,16 @@ class TestBasic(DRYTest):
def test_bools_and_or(self):
self.t('True and False', False)
self.t('True or False', True)
- self.t('1 - 1 or 21', True)
- self.t('1 - 1 and 11', False)
+ self.t('1 - 1 or 21', 21)
+ self.t('1 - 1 and 11', 0)
self.t('110 == 100 + 10 and True', True)
self.t('110 != 100 + 10 and True', False)
+ self.t('False or 42', 42)
+
+ 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)
def test_maths_with_floats(self):
self.t("11.02 - 9.1", 1.92)
@@ -63,6 +69,13 @@ class TestBasic(DRYTest):
self.t("1 >= 12", False)
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<1+1', 1<2<1+1)
+ self.t('1 == 1 == 2', 1 == 1 == 2)
+ self.t('1 == 1 < 2', 1 == 1 < 2)
+
def test_mixed_comparisons(self):
self.t("1 > 0.999999", True)
self.t("1 == True", True) # Note ==, not 'is'.
@@ -226,6 +239,48 @@ 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.t('x(100)', 200)
+
+ with self.assertRaises(simpleeval.FeatureNotAvailable):
+ self.t('x.__globals__', None)
+
+ class EscapeArtist(object):
+ def trapdoor(self):
+ return 42
+
+ def _quasi_private(self):
+ return 84
+
+ self.s.names['houdini'] = EscapeArtist()
+
+ with self.assertRaises(simpleeval.FeatureNotAvailable):
+ self.t('houdini.trapdoor.__globals__', 0)
+
+ with self.assertRaises(simpleeval.FeatureNotAvailable):
+ self.t('houdini.trapdoor.func_globals', 0)
+
+ with self.assertRaises(simpleeval.FeatureNotAvailable):
+ self.t('houdini._quasi_private()', 0)
+
+ # and test for changing '_' to '__':
+
+ dis = simpleeval.DISALLOW_PREFIXES
+ simpleeval.DISALLOW_PREFIXES = ['func_']
+
+ self.t('houdini._quasi_private()', 84)
+
+ # and return things to normal
+
+ simpleeval.DISALLOW_PREFIXES = dis
+
+
+
class TestNames(DRYTest):
''' 'names', what other languages call variables... '''
@@ -316,9 +371,11 @@ class TestNames(DRYTest):
self.assertEqual(self.s.names['a']['d'], 11)
def test_object(self):
- ''' using an object for name lookp '''
+ ''' using an object for name lookup '''
class TestObject(object):
- pass
+ def method_thing(self):
+ return 42
+
o = TestObject()
o.a = 23
o.b = 42
@@ -330,6 +387,9 @@ class TestNames(DRYTest):
self.t('o', o)
self.t('o.a', 23)
self.t('o.b + o.c.d', 9043)
+
+ self.t('o.method_thing()', 42)
+
with self.assertRaises(AttributeDoesNotExist):
self.t('o.d', None)
@@ -367,6 +427,29 @@ class TestNames(DRYTest):
self.t('a', 1)
self.t('a + b', 3)
+
+class Test_whitespace(DRYTest):
+ ''' test that incorrect whitespace (preceding/trailing) doesn't matter. '''
+ def test_no_whitespace(self):
+ self.t('200 + 200', 400)
+
+ def test_trailing(self):
+ self.t('200 + 200 ', 400)
+
+ def test_preciding_whitespace(self):
+ self.t(' 200 + 200', 400)
+
+ def test_preceding_tab_whitespace(self):
+ self.t("\t200 + 200", 400)
+
+ def test_preceding_mixed_whitespace(self):
+ self.t(" \t 200 + 200", 400)
+
+ def test_both_ends_whitespace(self):
+ self.t(" \t 200 + 200 ", 400)
+
+
+
class Test_simple_eval(unittest.TestCase):
''' test the 'simple_eval' wrapper function '''
def test_basic_run(self):
--
simpleeval
More information about the tryton-debian-vcs
mailing list