2

The system I work with allows users to specify a Python Boolean expression as a string (in a configuration file). The system takes the string, converts it to a Python AST object and then evaluates the expression based on input data.

There are occasional circumstances when the Boolean expression may result in an error condition (e.g. input data does not have the right number of entries). The requirement is that if a user wraps the Boolean expression in fail_on_error(...), the exception will be caught and converted to False (e.g.):

assert test('fail_on_error(True)') == True
assert test('fail_on_error(False)') == False
assert test('fail_on_error(list()[0])') == False # index out of range

This could be done using regular expressions, parsing the expression string, but I'd prefer to do this the "right way" with Python's ast module. Unfortunately I'm running into problems.

The Code

class ReplaceWithTry(ast.NodeTransformer):
    def visit_Call(self, node):
        if node.func.id != 'fail_on_error':
            return node

        # Get (first) argument to 'fail_on_error' and return its evaluated expression
        return_expr = ast.Return(value=node.args[0])
        except_handler = ast.ExceptHandler(type=None,
                                           name=None,
                                           body=ast.Return(value=ast.NameConstant(value=False))])
        return ast.Try(body=[return_expr],
                       handlers=[except_handler],
                       orelse=[],
                       finalbody=[])

def test(expr_str):
    syntax_tree = ast.parse(expr_str, '<string>', 'eval')
    new_tree = ast.fix_missing_locations(ReplaceWithTry().visit(syntax_tree))
    ast_obj = compile(new_tree, '<string>', 'eval')
    return eval(ast_obj)

The Error

I'm getting the following error:

knoepfel$ python replace_with_try.py 
Traceback (most recent call last):
  File "replace_with_try.py", line 24, in <module>
    assert test('fail_on_error(True)')
  File "replace_with_try.py", line 21, in test
    ast_obj = compile(new_tree, '<string>', 'eval')
TypeError: expected some sort of expr, but got <ast.Try object at 0x103da33a0>

Obviously I'm missing something. Any ideas?

KyleKnoepfel
  • 1,426
  • 8
  • 24

1 Answers1

0

This is quite an old question, but I suppose this will work

class ReplaceWithTry(ast.NodeTransformer):
    def visit_Call(self, node):
        if node.func.id != 'fail_on_error':
            return node

        new_node = ast.parse("""try: ...""") # your statement in python code
        return new_node

and

def test(expr_str):
    syntax_tree = ast.parse(expr_str)
    new_tree = ast.fix_missing_locations(ReplaceWithTry().visit(syntax_tree))
    ast_obj = compile(ast.unparse(new_tree), '<ast>', 'eval')
    return eval(ast_obj)
Quasi
  • 576
  • 4
  • 13