Best way to evaluate boolean expressions from strings?

G

Gustavo Narea

Hello, everybody.

I need to evaluate boolean expressions like "foo == 1" or "foo ==1 and
(bar > 2 or bar == 0)" which are defined as strings (in a database or
a plain text file, for example). How would you achieve this?

These expressions will contain placeholders for Python objects (like
"foo" and "bar" in the examples above). Also, the Python objects that
will get injected in the expression will support at least one of the
following operations: "==", "!=", ">", "<", ">=", "<=", "&", "|",
"in".

I don't need the ability to import modules, define classes, define
functions, etc. I just need to evaluate boolean expressions defined as
strings (using the Python syntax is fine, or even desirable).

Here's a complete example:

I have the "user_ip" and "valid_ips" placeholders defined in Python as
follows:
"""
user_ip = '111.111.111.111'

class IPCollection(object):
def __init__(self, *valid_ips):
self.valid_ips = valid_ips
def __contains__(self, value):
return value in self.valid_ips

valid_ips = IPCollection('222.222.222.222', '111.111.111.111')
"""

So the following boolean expressions given as strings should be
evaluated as:
* "user_ip == '127.0.0.1'" ---> False
* "user_ip == '127.0.0.1' or user_ip in valid_ips" ---> True
* "user_ip not in valid_ips" ---> False

That's it. How would you deal with this? I would love to re-use
existing stuff as much as possible, that works in Python 2.4-2.6 and
also that has a simple syntax (these expressions may not be written by
technical people; hence I'm not sure about using TALES).

Thanks in advance!
 
P

Peter Otten

Gustavo said:
I need to evaluate boolean expressions like "foo == 1" or "foo ==1 and
(bar > 2 or bar == 0)" which are defined as strings (in a database or
a plain text file, for example). How would you achieve this?

These expressions will contain placeholders for Python objects (like
"foo" and "bar" in the examples above). Also, the Python objects that
will get injected in the expression will support at least one of the
following operations: "==", "!=", ">", "<", ">=", "<=", "&", "|",
"in".

I don't need the ability to import modules, define classes, define
functions, etc. I just need to evaluate boolean expressions defined as
strings (using the Python syntax is fine, or even desirable).

Here's a complete example:

I have the "user_ip" and "valid_ips" placeholders defined in Python as
follows:
"""
user_ip = '111.111.111.111'

class IPCollection(object):
def __init__(self, *valid_ips):
self.valid_ips = valid_ips
def __contains__(self, value):
return value in self.valid_ips

valid_ips = IPCollection('222.222.222.222', '111.111.111.111')
"""

So the following boolean expressions given as strings should be
evaluated as:
* "user_ip == '127.0.0.1'" ---> False
* "user_ip == '127.0.0.1' or user_ip in valid_ips" ---> True
* "user_ip not in valid_ips" ---> False

That's it. How would you deal with this? I would love to re-use
existing stuff as much as possible, that works in Python 2.4-2.6 and
also that has a simple syntax (these expressions may not be written by
technical people; hence I'm not sure about using TALES).

exprs = [
"user_ip == '127.0.0.1'",
"user_ip == '127.0.0.1' or user_ip in valid_ips",
"user_ip not in valid_ips"]
for expr in exprs:
print expr, "-->", eval(expr)

Be warned that a malicious user can make your program execute arbitrary
python code; if you put the input form for expressions on the internet
you're toast.

Peter
 
G

Gustavo Narea

Thank you very much, Gabriela and Peter!

I'm going for Pyparsing. :)

-- Gustavo.
 
A

Arnaud Delobelle

Gustavo Narea said:
Hello, everybody.

I need to evaluate boolean expressions like "foo == 1" or "foo ==1 and
(bar > 2 or bar == 0)" which are defined as strings (in a database or
a plain text file, for example). How would you achieve this?

These expressions will contain placeholders for Python objects (like
"foo" and "bar" in the examples above). Also, the Python objects that
will get injected in the expression will support at least one of the
following operations: "==", "!=", ">", "<", ">=", "<=", "&", "|",
"in".

I don't need the ability to import modules, define classes, define
functions, etc. I just need to evaluate boolean expressions defined as
strings (using the Python syntax is fine, or even desirable).

Here's a complete example:

I have the "user_ip" and "valid_ips" placeholders defined in Python as
follows:
"""
user_ip = '111.111.111.111'

class IPCollection(object):
def __init__(self, *valid_ips):
self.valid_ips = valid_ips
def __contains__(self, value):
return value in self.valid_ips

valid_ips = IPCollection('222.222.222.222', '111.111.111.111')
"""

So the following boolean expressions given as strings should be
evaluated as:
* "user_ip == '127.0.0.1'" ---> False
* "user_ip == '127.0.0.1' or user_ip in valid_ips" ---> True
* "user_ip not in valid_ips" ---> False

That's it. How would you deal with this? I would love to re-use
existing stuff as much as possible, that works in Python 2.4-2.6 and
also that has a simple syntax (these expressions may not be written by
technical people; hence I'm not sure about using TALES).

Thanks in advance!

Here is a proof of concept using the ast module (Python >= 2.6):

----------------------------------------
import ast

class UnsafeError(Exception): pass

class SafetyChecker(ast.NodeVisitor):
def __init__(self, allowed_nodes, allowed_names):
self.allowed_nodes = allowed_nodes
self.allowed_names = allowed_names
def visit(self, node):
if isinstance(node, ast.Name):
if node.id in self.allowed_names:
return
raise UnsafeError('unsafe name: %s' % node.id)
if type(node) not in self.allowed_nodes:
if not any(tp in self.allowed_nodes for tp in type(node).__bases__):
raise UnsafeError('unsafe node: %s' % type(node).__name__)
ast.NodeVisitor.visit(self, node)

node_whitelist = [
'Expression', 'Load',
'operator', 'unaryop', 'UnaryOp', 'BoolOp', 'BinOp',
'boolop', 'cmpop', # Operators
'Num', 'Str', 'List', 'Tuple', # Literals
]

node_whitelist = [getattr(ast, name) for name in node_whitelist]

checker = SafetyChecker(node_whitelist, ['a', 'b', 'foo', 'bar'])

def safe_eval(expr, checker=checker):
t = ast.parse(expr, 'test.py', 'eval')
checker.visit(t)
return eval(expr)
----------------------------------------
Example:
safe_eval('2*a - bar') -4
safe_eval('[1, 2] + [3, 4]') [1, 2, 3, 4]
safe_eval('f(foo)')
Traceback (most recent call last):
[...]
__main__.UnsafeError: unsafe node: CallTraceback (most recent call last):
[...]
__main__.UnsafeError: unsafe name: xTraceback (most recent call last):
[...]
__main__.UnsafeError: unsafe node: Lambda'hello'


You'd have to tweak the node_whitelist using the info at

http://docs.python.org/library/ast.html#abstract-grammar
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,969
Messages
2,570,161
Members
46,705
Latest member
Stefkari24

Latest Threads

Top