simpleeval
A single-file Python library for safely evaluating user-supplied expressions without exposing full eval() access.
Repository Health
Technical Analysis
simpleeval (Simple Eval) is a lightweight Python library for evaluating expressions typed by end users — alarm-volume formulas, spreadsheet-style calculations, or web app formula fields — without handing them the full power, and danger, of Python’s built-in eval(). It parses expressions with Python’s own ast module and walks the resulting tree using an explicit whitelist of operators, functions, and names, so nothing runs that hasn’t been deliberately permitted.
The library ships as a single file that can be pip-installed or simply dropped into a project, and its defaults are deliberately conservative: string length, exponent size, and shift operations are all capped to make denial-of-service expressions impractical. Callers can extend it with custom functions, dynamic name lookups backed by a database or cache, and compound-type support via the EvalWithCompoundTypes subclass, while staying inside a sandboxed evaluation model.
What You Get
- A SimpleEval class for repeated evaluation of many expressions with shared, reusable state
- A simple_eval() convenience function for one-off expression evaluation
- A whitelisted operator table covering arithmetic, comparison, bitwise, and shift operations, each swappable or removable
- Built-in DoS protections: MAX_STRING_LENGTH, MAX_POWER, MAX_SHIFT, and MAX_COMPREHENSION_LENGTH guard against runaway expressions
- EvalWithCompoundTypes for expressions that need to build lists, dicts, tuples, and sets
Common Use Cases
- User-configurable formulas - letting end users define alarm thresholds, pricing rules, or scoring logic without exposing raw eval()
- Web form calculators - evaluating spreadsheet-like formulas submitted through a web UI, server-side, without needing a JS-only sandbox
- Templating and rules engines - powering conditional logic in low-code tools where end users write short expressions
- Config-driven business rules - reading expressions from a database or config file and evaluating them against a custom name resolver
Under The Hood
Architecture The library is a single module (simpleeval.py) organized around one core class, SimpleEval, which owns three swappable dicts — operators, functions, and names — and a recursive _eval() dispatcher that pattern-matches on ast node types (ast.BinOp, ast.Compare, ast.IfExp, ast.Call, and so on) and routes each to a dedicated handler. Names can be a plain dict or a callable, enabling dynamic resolution (database lookups, computed values) without subclassing. EvalWithCompoundTypes extends SimpleEval to add List/Dict/Tuple/Set node handling, kept separate so the base class’s attack surface stays minimal by default. A small set of module-level exception classes (NameNotDefined, FunctionNotDefined, NumberTooHigh, IterableTooLong) give callers precise, catchable failure modes rather than raw Python exceptions leaking through.
Tech Stack
Pure Python with zero runtime dependencies (dependencies = [] in pyproject.toml), built entirely on the standard library’s ast and operator modules. Packaging uses hatchling with a single-file wheel target. The dev toolchain runs on uv, with ruff for linting/formatting and mypy for type checking; the project targets Python 3.9 through 3.14 plus PyPy 3.9/3.10, reflected in a partial but present set of type annotations (e.g. dict[type | None, set]) on newer code paths.
Code Quality
Test coverage is extensive and unusually security-focused: alongside standard operator/function/name tests, a dedicated test_security.py exercises MRO breakout attempts, __builtins__ access, function-globals breakouts, module-access breakouts, and string-format exploits — the kind of adversarial cases a sandboxing library needs to defend explicitly. CI (GitHub Actions) runs the full matrix across Ubuntu and Windows, across all supported CPython and PyPy versions, plus a separate lint job running ruff and a dist build/check. Naming and structure are consistent throughout, and exceptions are typed and specific rather than generic.
What Makes It Unique Rather than trying to blacklist dangerous constructs from a full eval(), simpleeval inverts the model: it only implements handlers for a small, explicit whitelist of AST node types, so anything not explicitly supported simply isn’t reachable, closing off entire classes of sandbox-escape bugs by construction rather than by patching known exploits. Combined with built-in resource-exhaustion limits (max string length, max power, max shift) tuned from real-world abuse cases, it targets a narrow, well-understood threat model — untrusted expression strings, not untrusted Python code — and does that one job with a very small, auditable surface area.