Code Monkey home page Code Monkey logo

simpleeval's Introduction

simpleeval (Simple Eval)

Build Status

Code Coverage

PyPI Version

image

image

A 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 trying to stay simple to use and not have millions of features, 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 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
>> "Right shift" the number. 100 >> 2 -> 25
<< "Left shift" the number. 100 << 2 -> 400
in is something contained within something else. "spam" in "my breakfast" -> False
^ "bitwise exclusive OR" (xor) 62 ^ 20 -> 42
| "bitwise OR" 8 | 34 -> 42
& "bitwise AND" 100 & 63 -> 36
~ "bitwise invert" ~ -43 -> 42

The ^ operator is often mistaken for a exponent operator, not the bitwise operation that it is in python, so if you want 3 ^ 2 to equal 9, you can replace the operator like this:

>>> import ast
>>> from simpleeval import safe_power

>>> s = SimpleEval()
>>> s.operators[ast.BitXor] = safe_power

>>> s.eval("3 ^ 2")
9

for example.

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.MAX_POWER 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 MAX_POWER limit, it throws a NumberTooHigh exception for you. (Otherwise it would go on for hours, or until the computer runs out of memory)

Strings (and other Iterables) Safety

There are also limits on string length (100000 characters, MAX_STRING_LENGTH). This can be changed if you wish.

Related to this, if you try to create a silly long string/bytes/list, by doing 'i want to break free'.split() * 9999999999 for instance, it will block you.

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

If you don't provide your own functions dict, then the the following defaults are provided in the DEFAULT_FUNCTIONS dict:

randint(x) Return a random int below x
rand() Return a random float between 0 and 1
int(x) Convert x to an int.
float(x) Convert x to a float.
str(x) Convert x to a str (unicode in py2)

If you want to provide a list of functions, but want to keep these as well, then you can do a normal python .copy() & .update:

>>> my_functions = simpleeval.DEFAULT_FUNCTIONS.copy()
>>> my_functions.update(
        square=(lambda x:x*x),
        double=(lambda x:x+x),
    )
>>> simple_eval('square(randint(100))', functions=my_functions)

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.

The two default names that are provided are True and False. So if you want to provide your own names, but want True and False to keep working, either provide them yourself, or .copy() and .update the DEFAULT_NAMES. (See functions example above).

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")
2

>>> s.eval('100 * 10')
1000

# and so on...

One useful feature of using the SimpleEval object is that you can parse an expression once, and then evaluate it mulitple times using different names:

# Set up & Cache the parse tree:
expression = "foo + bar"
parsed = s.parse(expression)

# evaluate the expression multiple times:
for names in [{"foo": 1, "bar": 10}, {"foo": 100, "bar": 42}]:
    s.names = names
    print(s.eval(expression, previously_parsed=parsed))

for instance. This may help with performance.

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)

def boo():
    return 'Boo!'

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.

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.

The EvalWithCompoundTypes class also contains support for simple comprehensions. eg: [x + 1 for x in [1,2,3]]. There's a safety MAX_COMPREHENSION_LENGTH to control how many items it'll allow before bailing too. This also takes into account nested comprehensions.

Since the primary intention of this library is short expressions - an extra 'sweetener' is enabled by default. You can access a dict (or similar's) keys using the .attr syntax:

>>>  simple_eval("foo.bar", names={"foo": {"bar": 42}})
42

for instance. You can turn this off either by setting the module global ATTR_INDEX_FALLBACK to False, or on the SimpleEval instance itself. e.g. evaller.ATTR_INDEX_FALLBACK=False.

Extending

The SimpleEval class is pretty easy to extend. For instance, to create a version that disallows method invocation on objects:

import ast
import simpleeval

class EvalNoMethods(simpleeval.SimpleEval):
    def _eval_call(self, node):
        if isinstance(node.func, ast.Attribute):
            raise simpleeval.FeatureNotAvailable("No methods please, we're British")
        return super(EvalNoMethods, self)._eval_call(node)

and then use EvalNoMethods instead of the SimpleEval class.

Other...

The library supports python 3 - but should be mostly compatible (and tested before 0.9.11) with python 2.7 as well.

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.

A few builtin functions are listed in simpleeval.DISALLOW_FUNCTIONS. type, open, etc. If you need to give access to this kind of functionality to your expressions, then be very careful. You'd be better wrapping the functions in your own safe wrappers.

The initial idea came from J.F. Sebastian on Stack Overflow ( http://stackoverflow.com/a/9558001/1973500 ) with modifications and many improvements, see the head of the main file for contributors list.

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!

Developing

Run tests:

$ make test

Or to set the tests running on every file change:

$ make autotest

(requires entr)

I'm trying to keep the codebase relatively clean with Black, isort, pylint & mypy. See:

$ make format

and:

$ make lint

BEWARE

I've done the best I can with this library - but there's no warranty, no guarantee, nada. A lot of very clever people think the whole idea of trying to sandbox CPython is impossible. Read the code yourself, and use it at your own risk.

simpleeval's People

Contributors

birne94 avatar bozokopic avatar charlax avatar corro avatar danthedeckie avatar daveisfera avatar daxamin avatar edwardbetts avatar graingert avatar koenigsley avatar kolanich avatar kurtmckee avatar marky1991 avatar orthographic-pedant avatar readevalprint avatar rossburton avatar sanjaymsh avatar shughes-uk avatar smurfix avatar t045t avatar xaled avatar zhudotexe avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

simpleeval's Issues

Evaluator crash on complex assignment target in list comprehension

simpleeval 0.9.10

So I just remembered that any valid assignment target can be used in a list comprehension! i.e. something like [... for foo[0] in ...] is valid syntax.

Expected behavior

>>> [x for x in ([None],) for x[0] in (15,)]
[[15]]

Actual behavior

>>> EvalWithCompoundTypes('[x for x in ([None],) for x[0] in (15,)]')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "LIBPATH/simpleeval.py", line 539, in eval
    return super(EvalWithCompoundTypes, self).eval(expr)
  File "LIBPATH/simpleeval.py", line 332, in eval
    return self._eval(ast.parse(expr.strip()).body[0].value)
  File "LIBPATH/simpleeval.py", line 343, in _eval
    return handler(node)
  File "LIBPATH/simpleeval.py", line 597, in _eval_comprehension
    do_generator()
  File "LIBPATH/simpleeval.py", line 592, in do_generator
    do_generator(gi+1)
  File "LIBPATH/simpleeval.py", line 589, in do_generator
    recurse_targets(g.target, i)
  File "LIBPATH/simpleeval.py", line 579, in recurse_targets
    for t, v in zip(target.elts, value):
AttributeError: 'Subscript' object has no attribute 'elts'

Functions not considered during name lookup

Here's a simple reproducer of the problem:

#!/usr/bin/env python3

from simpleeval import EvalWithCompoundTypes

s = EvalWithCompoundTypes(functions={"map": map, "str": str})
print(s.eval("list(map(str, [-1, 0, 1]))"))

The fix is to have SimpleEval._eval_name() also look in self.functions when it can't find node.is in self.names. I can submit a pull request once my pending one is closed.

Lambda application causes crash instead of `FeatureNotAvailable`

I ran into this crash when investigating safeeval's resiliency against lambda-based attacks.

Platform:

Python 3.6.5 (default, Apr  3 2018, 21:02:01)
[GCC 4.2.1 Compatible Android Clang 5.0.300080 ] on linux

I'm using the Android app Termux, so the environment is a bit unusual, but this doesn't look like a platform-specific crash. Maybe this is caused by some change in ast on Python 3.6?

Code:

>>> import simpleeval
>>> s = simpleeval.SimpleEval()
>>> s.eval('(lambda x: x+1)(3)')

Expected result:

Traceback (most recent call last):
  ...
  simpleeval.FeatureNotAvailable: Sorry, Lambda is not available in this evaluator

Actual result (crash):

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<python path>/site-packages/simpleeval.py", line 283, in eval
    return self._eval(ast.parse(expr.strip()).body[0].value)
  File "<python path>/site-packages/simpleeval.py", line 294, in _eval
    return handler(node)
  File "<python path>/site-packages/simpleeval.py", line 353, in _eval_call
    func = self.functions[node.func.id]
AttributeError: 'Lambda' object has no attribute 'id'

s.eval('lambda x: x') correctly raises FeatureNotAvailable, only a lambda application causes this problem.

Do not allow assignment operator

The following statement successfully executes.

simpleeval.simple_eval("a = 2")

And returns 2.
I suggest that assignment operator is entirely disallowed as input to simple eval.

Because "a == 2" can be easily mistyped as "a = 2" and existence of variable "a" is not even checked in evaluation of this input.

Leading space results in IndentationError

I'm using simpleeval for evaluating template expressions, e.g.

{{ foo.bar }}

Because the engine (https://github.com/iivvoo/ate if anyone's interested ;) aims to be user (programmer) friendly it ignores leading/trailing whitespaces and passes them verbatim to simpleeval which sometimes results in an IndentationError

In [1]: from simpleeval import simple_eval
In [2]: simple_eval(" True")
  File "<unknown>", line 1
    True
    ^
IndentationError: unexpected indent

This is quite simple to work around but it makes me wonder if there's any need at all for simpleeval to be so strict about whitespace - is any form of indentation actually supported?

Support for Python 3.8

Tests fail with the following error when running on Python 3.8:
simpleeval.FeatureNotAvailable: Sorry, Constant is not available in this evaluator

Support implied multiply

Can support by added for the implied multiplication, for example:
"2(3+4)"

If not, how can a random equation be edited at runtime to insert the '*' ?

test_simpleeval.py not packaged

The file test_simpleeval.py is not in the package on PyPI.
It will be good to have it to test if the installation is correct.

Modifying defaults

Hi, first of all I want to thank you for the package. It comes very handy to my project. I have forked it, so I'm open to contribute.

I have some questions, what happens if I do:

import simpleeval
import math

simpleeval.DEFAULT_FUNCTIONS["sqrt"] = math.sqrt
print simpleeval.simple_eval("sqrt(25)")

>>> 5

It works, but, is that all right? Is it the supposed way I should do it? is there any risk?

Crashes when evaluating an empty string

When giving simpleeval an empty string it raises this error:
Traceback (most recent call last):

Traceback (most recent call last):
  File "simpleeval.py", line 613, in simple_eval
    return s.eval(expr)
  File "simpleeval.py", line 334, in eval
    return self._eval(parsed.body[0].value)
IndexError: list index out of range

I propose to either throw an appropriate error or maybe return something. Wether that'd be an empty string or None is too philosophical for me but maybe there's an PIP on this? Anyways, something like this should suffice:

parsed = ast.parse(expr.strip())
if len(parsed.body) > 0:
    # and evaluate if not empty:
    return self._eval(parsed.body[0].value)
else:
    return None

Expression does not give expected result

Consider the following:

out = True
position = 3
expr = '(out and position <=6 and -10) or (out and position > 6 and -5) or (not out and 15)'

simple_eval(expr, names={'out': True, 'position': 3} )

gives the result

True

This is wrong. Using

eval(expr)

we get the correct result:

-10

Is this a bug? Or intentional?

Raise error when multi line expression is passed for evaluation

Using simple_eval method expression like True\nFalse is evaluated to True, False\nTrue to False.

It looks like only the first statement is taken from AST for evaluation.

return self._eval(ast.parse(expr.strip()).body[0].value)

I suggest that an exception must be raised in case where there are multiple statements to be sure that passed expression is evaluated correctly.

Operator with arguments??

I'd like to have an operator like "?(number)". How would I go about doing this, and if I can't, is it planned and/or are there workarounds?

You might not understand why, but it's so that I can do things like FIBONACCI(2) would get the first 2 fibonacci numbers. it's like functions but as expressions.

Problem when building using simpleeval

I have a project that I wanted to build to create a release. I've tried with py2exe and PyInstaller.
However, always after the building, it throws an error that says:

   File "simpleeval.py", line 116, in <module>
NameError: name 'help' is not defined
[39588] Failed to execute script app

I was able to solve this by adding:

def help():
    pass

right above the definition of DISALLOW_FUNCTIONS, that was at the line 116.

I only did it because I've found another person with the same problem, that described this solution in this site.

It would be possible to add this in a release? Because the program works perfectly just with this little modification. But it is annoying to add this always when I want to build the project.

I don't know if there's another person that faced the same problem and has a better way to solve this. If so, I would like to know.

Including non-builtin class in names allows end user to snoop

If the user (source of untrusted code) can get access to a non-builtin function, such as a method defined on a non-builtin class, they can access the functions func_globals or __globals__ property and see everything defined in that function's module. This includes other imported modules and often leads to access to sys, and by extension, sys.modules. This could allow an attacker to find sensitive data (usernames, passwords, PII) by crawling through the list of modules. Even if the sensitive information is not defined at top level, inspection of function objects will reveal any constants or default arguments used anywhere in the running system.

At a minimum, attribute lookup for attributes beginning with _ should be denied (plus the special case in python2 to deny func_*). Ideally, attribute lookup should have a configurable max-depth, or a whitelist of allowed types and classes.

Failing test with python3.2

With python3.4 no errors (s. #5), but when building for Debian wheezy, which only has python3.2 the following error occurs:

I: pybuild base:170: python3.2 setup.py test
running test
running build_py
running egg_info
creating /tmp/buildd/simpleeval-0.8.2/.pybuild/pythonX.Y_3.2/build/simpleeval.egg-info
writing /tmp/buildd/simpleeval-0.8.2/.pybuild/pythonX.Y_3.2/build/simpleeval.egg-info/PKG-INFO
writing top-level names to /tmp/buildd/simpleeval-0.8.2/.pybuild/pythonX.Y_3.2/build/simpleeval.egg-info/top_level.txt
writing dependency_links to /tmp/buildd/simpleeval-0.8.2/.pybuild/pythonX.Y_3.2/build/simpleeval.egg-info/dependency_links.txt
writing manifest file '/tmp/buildd/simpleeval-0.8.2/.pybuild/pythonX.Y_3.2/build/simpleeval.egg-info/SOURCES.txt'
reading manifest file '/tmp/buildd/simpleeval-0.8.2/.pybuild/pythonX.Y_3.2/build/simpleeval.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file '/tmp/buildd/simpleeval-0.8.2/.pybuild/pythonX.Y_3.2/build/simpleeval.egg-info/SOURCES.txt'
running build_ext
Traceback (most recent call last):
  File "setup.py", line 22, in <module>
    'Programming Language :: Python :: 3',
  File "/usr/lib/python3.2/distutils/core.py", line 148, in setup
    dist.run_commands()
  File "/usr/lib/python3.2/distutils/dist.py", line 917, in run_commands
    self.run_command(cmd)
  File "/usr/lib/python3.2/distutils/dist.py", line 936, in run_command
    cmd_obj.run()
  File "/usr/lib/python3/dist-packages/setuptools/command/test.py", line 137, in run
    self.with_project_on_sys_path(self.run_tests)
  File "/usr/lib/python3/dist-packages/setuptools/command/test.py", line 117, in with_project_on_sys_path
    func()
  File "/usr/lib/python3/dist-packages/setuptools/command/test.py", line 146, in run_tests
    testLoader = loader_class()
  File "/usr/lib/python3.2/unittest/main.py", line 123, in __init__
    self.parseArgs(argv)
  File "/usr/lib/python3.2/unittest/main.py", line 191, in parseArgs
    self.createTests()
  File "/usr/lib/python3.2/unittest/main.py", line 198, in createTests
    self.module)
  File "/usr/lib/python3.2/unittest/loader.py", line 137, in loadTestsFromNames
    suites = [self.loadTestsFromName(name, module) for name in names]
  File "/usr/lib/python3.2/unittest/loader.py", line 137, in <listcomp>
    suites = [self.loadTestsFromName(name, module) for name in names]
  File "/usr/lib/python3.2/unittest/loader.py", line 96, in loadTestsFromName
    module = __import__('.'.join(parts_copy))
  File "/tmp/buildd/simpleeval-0.8.2/test_simpleeval.py", line 87
    self.t('"Test Stuff!" + str(11)', u"Test Stuff!11")
                                                     ^
SyntaxError: invalid syntax

Exceptions during list comprehensions leak local variables

Example code:

import ast

from simpleeval import EvalWithCompoundTypes

e = EvalWithCompoundTypes()

try:
    print(e.eval('[x if x == "2" else y for x in "123"]'))
except Exception as exc:
    print(exc)
print(e.nodes[ast.Name])
print(e.eval('x'))

Result:

'y' is not defined for expression '[x if x == "2" else y for x in "123"]'
<function EvalWithCompoundTypes._eval_comprehension.<locals>.eval_names_extra at 0x1098f91e0>
1

Expected Result:

The second print statement should print something like <bound method SimpleEval._eval_name of <simpleeval.EvalWithCompoundTypes object at 0x10bf2f860>> while the second evaluation should raise a NameNotDefined exception.

Solution:

The _eval_comprehension method changes the evaller for the ast.Name node, but does not reset it in case of an exception. This can be easily solved by wrapping the call to do_generator into a try...finally construct.

How to use math constants

Hi, is it possible to evaluate "Pi" or "e" in an expression ?
If not, would you have any idea on how to implement this ?

How to change MAX_POWER limit without edit simpleeval source code

Hello,
congratulations on this fantastic library.

It has allowed me to develop a generic function with the following signature:

def calc_model (formula, ** kargs)

The native function eval (formula, kargs) was giving me an error when the formula contained functions like math.log10 (x) even after importing it.

Embedding the import just before the formula was not the solution either:

if ('math' in formula):
         formula = 'import math; '+ formula

Finally, I had the solution with simpleeval defining a specific lambda function:

log10_lambda = {"log10": lambda x: math.log10(x)}
result = simple_eval (formula, names = kargs, functions = log10_lambda)

MY QUESTION IS

How can I increase or even completely disable the MAX_POWER limit value without editing the library source code?

I have some formulas that need it

Thanks in advance.

Weird behaviour when trying to evaluate a class method chaining

Hello,
First I want to thank you for this project,
I was trying to evaluate some class method calls using the method chaining syntax, but i got a weird result:
my code:

from __future__ import print_function
import simpleeval

class A(object):
    def __init__(self):
        self.a = "0"

    def add(self, b):
        self.a += "-add" + str(b)
        return self

    def sub(self, b):
        self.a += "-sub" + str(b)
        return self

    def __str__(self):
        return str(self.a)

x = A()
print(simpleeval.simple_eval("x.add(1).sub(2).sub(3)", names={"x": x}))
x = A()
print(x.add(1).sub(2).sub(3)) # expected output

the output:

0-add1-add1-sub2-add1-add1-sub2-sub3
0-add1-sub2-sub3

The first line is the output from simpleeval.

Support "decimal" module

Would be possible to support the decimal module, such that e.g. simple_eval('0.1+0.2') gives 0.3 instead of 0.30000000000000004?

Expression Evaluation Error

Perhaps I am misunderstanding how the expression evaluator should work, but consider the following:

formula = "0 <= 10 <= 5"
simple_eval(formula)
True

While 0 <= 10 should evaluate to True, 10 <= 5 should not. Perhaps the evaluation is being short-circuited? Is this intentional?

I also tested this with Python's built in eval function:

formula = "0 <= 10 <= 5"
eval(formula)
False

FunctionNotDefined object has no attribute 'func_name'

According to the source code, the FunctionNotDefined exception has an attribute func_name, and has had it since the initial commit. However, when using pip to install the package, the same section looks like this:

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)
        self.__name__ = func_name
        self.expression = expression

so the attribute is called __name__.

The weird thing is, that the tar on PyPI DOES seem to contain the correct code (no diff with the github version.

What's going on here?

To reproduce, I did:

docker run --rm -it python:3.7.0-stretch bash
pip install simpleeval
grep -A6 'class FunctionNotDefined' /usr/local/lib/python3.7/site-packages/simpleeval.py

Decimal support example not working

Hi, in this issue you propose a solution for using simpleeval with Decimal type. That appears to no longer be working? I'm not quite sure why.

import decimal
import simpleeval


class DecimalEval(simpleeval.SimpleEval):
    @staticmethod
    def _eval_num(node):
        return decimal.Decimal(node.n)

decimal.getcontext().prec = 1

print(DecimalEval().eval('0.1+0.2'))
print(type(DecimalEval().eval('0.1+0.2')))

This outputs

0.30000000000000004
<class 'float'>

Can you give some insight please?

Can't install...

How do I install with pip? (I've got a bad feeling I'm missing something really obvious)

Side effects on names

Hi,

I've noticed that the names dictionary provided to simple_eval was actually modified here:

3aa36f3

It is a difficult side-effect to detect, so I would like to know if it is the expected behavior (meaning one should always copy actual data passed to names) or a bug to fix.

simpleeval + sqlalchemy query

Hi,

I'm trying to get the safety of simpleeval to work with sqlalchemy's core query:
Something like:

tbl = Table('mytable', meta)
names = tbl.c._data
myexpr = 'mycol > 5'
se = simpleeval.EvalWithCompoundTypes(names=names)
safe_filter = se.eval(myexpr)  # <-- I get an error
results = session.query(tbl).filter(safe_filter)

I'm getting this error on eval():

TypeError: Boolean value of this clause is not defined

For the record, this does work:

tbl = Table('mytable', meta)
names = tbl.c._data
myexpr = 'mycol > 5'
unsafe_filter = eval(myexpr, names)
results = session.query(tbl).filter(unsafe_filter)

It looks like eval() is returning: sqlalchemy.sql.elements.BinaryExpression, which breaks simpleeval...

Any hope of getting this to work? Am I doing something wrong?

Thanks!

Failing test TestTryingToBreakOut

TestTryingToBreakOut is currently failing with

I: pybuild base:170: python2.7 setup.py test 
running test
running egg_info
creating simpleeval.egg-info
writing simpleeval.egg-info/PKG-INFO
writing top-level names to simpleeval.egg-info/top_level.txt
writing dependency_links to simpleeval.egg-info/dependency_links.txt
writing manifest file 'simpleeval.egg-info/SOURCES.txt'
reading manifest file 'simpleeval.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'simpleeval.egg-info/SOURCES.txt'
running build_ext
test_bools_and_or (test_simpleeval.TestBasic) ... ok
test_comparisons (test_simpleeval.TestBasic) ... ok
test_default_conversions (test_simpleeval.TestBasic)
conversion between types ... ok
test_if_else (test_simpleeval.TestBasic)
x if y else z ... ok
test_maths_with_floats (test_simpleeval.TestBasic) ... ok
test_maths_with_ints (test_simpleeval.TestBasic)
simple maths expressions ... ok
test_mixed_comparisons (test_simpleeval.TestBasic) ... ok
test_load_file (test_simpleeval.TestFunctions)
add in a function which loads data from an external file. ... ok
test_randoms (test_simpleeval.TestFunctions)
test the rand() and randint() functions ... ok
test_dict (test_simpleeval.TestNames)
using a normal dict for names lookup ... ok
test_from_doc (test_simpleeval.TestNames)
the 'name first letter as value' example from the docs ... ok
test_func (test_simpleeval.TestNames)
using a function for 'names lookup' ... ok
test_none (test_simpleeval.TestNames)
what to do when names isn't defined, or is 'none' ... ok
test_import (test_simpleeval.TestTryingToBreakOut)
usual suspect. import ... ok
test_long_running (test_simpleeval.TestTryingToBreakOut)
exponent operations can take a long time. ... ok
test_python_stuff (test_simpleeval.TestTryingToBreakOut)
other various pythony things. ... ok
test_string_length (test_simpleeval.TestTryingToBreakOut) ... ERROR
test_basic_run (test_simpleeval.Test_simple_eval) ... ok
test_default_functions (test_simpleeval.Test_simple_eval) ... ok

======================================================================
ERROR: test_string_length (test_simpleeval.TestTryingToBreakOut)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tmp/buildd/simpleeval-0.8.2/test_simpleeval.py", line 199, in test_string_length
    self.t("'" + (50000 * "stuff") + "'", 0)
  File "/tmp/buildd/simpleeval-0.8.2/test_simpleeval.py", line 24, in t
    return self.assertEqual(self.s.eval(expr), shouldbe)
  File "/tmp/buildd/simpleeval-0.8.2/simpleeval.py", line 209, in eval
    return self._eval(ast.parse(expr).body[0].value)
  File "/tmp/buildd/simpleeval-0.8.2/simpleeval.py", line 223, in _eval
    node.id, len(node.s), MAX_STRING_LENGTH))
AttributeError: 'Str' object has no attribute 'id'

----------------------------------------------------------------------
Ran 19 tests in 0.009s

FAILED (errors=1)

Default Operators are not appended when new operators added

Documentation on your simpleeval package (on github) has an explanation on how add ^ operator, but lacks an example of how operators paramater work

is through a dict with lambda, just like functions paramater work?
already tested the following and it was the only way I could make it work

s = SimpleEval()
s.operators[ast.BitOr] = op.or_

the problem with this is that Or can not be added as or
Also custom functions that are not available in ast package are trickier (could not make it work)
for example lets take operator juggler:
1 juggler 7 returns 5, since juggler takes 1 from 7, and since the result is higher that 1, takes another 1 from the result (6) ending in 5

if the example does not make much sense is because I'm forcing it to be a custom operator

This triggers the fact that when any operator is added DEFAULT OPERATORS are ignored:
if anything is added through parameter operator= a conditional on the constructor (__init__) makes it imposible to append new operators to the default operators

Is this a feature? an optimization feature? maybe I am not using the package correctly

I thought the documentation could use something like:

adding operator Or would require the following:

simple_eval("True Or False", operators={"Or": lambda x, y: x | y})

returns True

simple_eval("1 juggler 7", operators={"juggler": lambda x, y: 
                                       y - 2 *x if (y - x) > x else y - x })

returns 5

this becomes extremely handy when using complex operators for time series when comparisons are time wise (maybe sent in tuple form)

simple_eval("(t0, t1) crosses_above (s0, s1)", 
            operators={"crosses_above": lambda t, s: t[1] > s[1] if t[0] < s[0] else False})

returns True or False depending on the case
when: t[0, 1] = 2, 4 and s[0, 1] = 2.5, 3.5; crosses_above returns True
when: t[0, 1] = 2, 3 and s[0, 1] = 2.5, 3.5; crosses_above returns False

When this issue is clear to me I could open a fork and send the PR with the respective changes into the documentation

Also I am impressed at your work, wish you a wonderful day sir

Installing works, but gives ParseError warning

I just ran:

pip install simpleeval

It was successfully installed (and works locally), but there was a warning from line 55:

Can't parse docstring in build/lib/simpleeval.py line 55: ParseError: bad input: type=0, value='', context=('', (56, 0))

Help is not included when using pyinstaller

I'm having an issue when creating a python binary using Pyinstaller. It does not include the site module , which is where help comes from. This results in a NameError when importing simpleeval. As including site seems like it would cause Pyinstaller significant issues it might be wise to check for it's existence before adding it to your excluded keywords list in simpleeval.

The relevant code is DISALLOW_FUNCTIONS = {type, isinstance, eval, getattr, setattr, help, repr, compile, open}

Here's a pyinstaller issue I was able to find that is related.

pyinstaller/pyinstaller#4448

If you feel this is a pyinstaller issue I can recreate this issue over there.

Release plan

Do we have plan to make a new release?
we hit the issue: #69 and it has been fix by #72
but not perform new release for it, so i wonder to know the release plan see if it can catch our time-line thanks

LICENCE in pypi tarball

Hi,
could you please ship licence file along with tarbal as usual in opensource projects?
Thanks for considering

Issue when running on python3.8

When you try to run the testsuite of simpleeval on python 3.8 you get following error on most tests:

[   12s] ___________________ TestShortCircuiting.test_shortcircuit_if ___________________
[   12s] 
[   12s] self = <simpleeval.SimpleEval object at 0xf66c0208>
[   12s] node = <_ast.Constant object at 0xf66c02f8>
[   12s] 
[   12s]     def _eval(self, node):
[   12s]         """ The internal evaluator used on each node in the parsed tree. """
[   12s]     
[   12s]         try:
[   12s] >           handler = self.nodes[type(node)]
[   12s] E           KeyError: <class '_ast.Constant'>
[   12s] 
[   12s] simpleeval.py:315: KeyError
[   12s] 
[   12s] During handling of the above exception, another exception occurred:
[   12s] 
[   12s] self = <test_simpleeval.TestShortCircuiting testMethod=test_shortcircuit_if>
[   12s] 
[   12s]     def test_shortcircuit_if(self):
[   12s]         x = []
[   12s]         def foo(y):
[   12s]             x.append(y)
[   12s]             return y
[   12s]         self.s.functions = {'foo': foo}
[   12s] >       self.t('foo(1) if foo(2) else foo(3)', 1)
[   12s] 
[   12s] test_simpleeval.py:1001: 
[   12s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   12s] test_simpleeval.py:31: in t
[   12s]     return self.assertEqual(self.s.eval(expr), shouldbe)
[   12s] simpleeval.py:309: in eval
[   12s]     return self._eval(ast.parse(expr.strip()).body[0].value)
[   12s] simpleeval.py:320: in _eval
[   12s]     return handler(node)
[   12s] simpleeval.py:372: in _eval_ifexp
[   12s]     return self._eval(node.body) if self._eval(node.test) \
[   12s] simpleeval.py:320: in _eval
[   12s]     return handler(node)
[   12s] simpleeval.py:386: in _eval_call
[   12s]     return func(
[   12s] simpleeval.py:387: in <genexpr>
[   12s]     *(self._eval(a) for a in node.args),
[   12s] _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
[   12s] 
[   12s] self = <simpleeval.SimpleEval object at 0xf66c0208>
[   12s] node = <_ast.Constant object at 0xf66c02f8>
[   12s] 
[   12s]     def _eval(self, node):
[   12s]         """ The internal evaluator used on each node in the parsed tree. """
[   12s]     
[   12s]         try:
[   12s]             handler = self.nodes[type(node)]
[   12s]         except KeyError:
[   12s] >           raise FeatureNotAvailable("Sorry, {0} is not available in this "
[   12s]                                       "evaluator".format(type(node).__name__))
[   12s] E           simpleeval.FeatureNotAvailable: Sorry, Constant is not available in this evaluator
[   12s] 
[   12s] simpleeval.py:317: FeatureNotAvailable
[   12s] ======================== 64 failed, 16 passed in 4.37s =========================

README doesn't render correctly on PyPI

Hi! This is what the PyPI page looks like to me (Chrome/Android):

storage emulated 0 firespeed org resizeimage 612-452-1538742958902

IME with markdown, adding a long_description_content_type='<your_format>' to your setup(...) should make the page render correctly. (I'm not sure if it's supposed to be 'rst' or 'reStructuredText' though.) You might have to update your setuptools, some older versions don't recognize that parameter.

Possible to define my own operators

Hi,
I'm trying to use simpleeval to evaluate expressions as XML attributes.
Since XML syntax uses "<" and ">" I cannot do this:

  <Tag expr="3 > 1"/>

So I am trying to use something like this:

  <Tag expr="3 GT 1"/>

Can this be done?

safe_lambda

Hi! maybe this can help:

def safe_lambda(x, y):
  return lambda z: simple_eval(y, names={x:z})

list(filter(safe_lambda('x', 'x > 2'), [1, 2, 3, 4, 5, 6]))

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.