pure-eval
pure-eval is a Python library (current version 0.2.3) designed to safely evaluate Abstract Syntax Tree (AST) nodes without allowing arbitrary code execution or unwanted side effects. It provides a controlled way to inspect and compute values from Python expressions, making it suitable for static analysis and secure evaluation contexts. The library is actively maintained, with updates released periodically.
Warnings
- gotcha pure-eval is explicitly designed to raise a `CannotEval` exception when it encounters expressions that could have side effects, such as calling functions, accessing properties with getters that modify state, or modifying variables. It is not a drop-in replacement for Python's built-in `eval()` function for arbitrary code.
- gotcha By default, `pure-eval`'s `Evaluator` might inherit built-in functions and names from the execution environment. For maximum security and a truly 'pure' evaluation context, explicitly pass an empty dictionary `{}` or a carefully curated whitelist of safe built-ins to the `__builtins__` key in the `global_context` dictionary when initializing `Evaluator`.
Install
-
pip install pure-eval
Imports
- Evaluator
from pure_eval import Evaluator
- CannotEval
from pure_eval import CannotEval
Quickstart
import ast
from pure_eval import Evaluator, CannotEval
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
self.area_computed = False
@property
def area(self):
self.area_computed = True # This is a side effect
return self.width * self.height
# Create an AST node representing a pure expression
rect_node = ast.parse("rect.width * rect.height").body[0].value
rect_instance = Rectangle(10, 5)
# Initialize Evaluator with the context. Disable builtins for security.
evaluator = Evaluator({
"rect": rect_instance,
"__builtins__": {}
})
try:
result = evaluator.evaluate(rect_node)
print(f"Evaluated pure result: {result}")
except CannotEval as e:
print(f"Could not evaluate pure expression: {e}")
# Create an AST node representing an expression with a potential side effect
side_effect_node = ast.parse("rect.area").body[0].value
try:
evaluator.evaluate(side_effect_node)
except CannotEval as e:
print(f"Refused to evaluate expression with potential side effect: {e}")
# Verify no side effect occurred from the attempted evaluation
print(f"Area computed flag: {rect_instance.area_computed}")