pure_eval
Evaluates Python AST expression nodes safely, without triggering side effects like property getters or function calls.
Repository Health
Technical Analysis
pure_eval is a small, focused Python library that inspects an AST expression node and determines whether it can be evaluated without running arbitrary code, then evaluates it if so. Given a mapping of variable names to values (or a live stack frame), it walks expressions like attribute access, subscripting, and literal containers, only executing operations it can prove are free of side effects such as property getters, __getattr__ hooks, or function calls with unknown behavior.
It underlies interactive Python tooling that needs to show live values without accidentally triggering user code: IPython, ptpython, and the stack_data traceback formatter all use pure_eval to safely display variable values while a user is debugging or exploring code in a REPL.
What You Get
- An Evaluator class that safely evaluates individual AST expression nodes against a namespace, with per-node result caching
- Evaluator.from_frame() to build an evaluator directly from a Python stack frame’s locals, globals, and builtins
- find_expressions() and interesting_expressions_grouped() to walk an entire tree and surface every safely-evaluable, non-obvious expression
- A CannotEval exception as a single, consistent signal for ‘this can’t be proven safe to evaluate’
- A standalone getattr_static() utility for reading attributes without invoking descriptors, getattr, or getattribute
Common Use Cases
- Showing live variable values in a debugger or REPL without risking a call into a side-effecting property
- Annotating tracebacks with the values of sub-expressions in the failing line of code
- Rendering inline ’= value’ hints for expressions in an editor or notebook without executing real program logic
- Any scenario needing a stronger safety guarantee than eval(), which can trigger arbitrary code via descriptors and dunder methods
Under The Hood
Architecture
A single Evaluator class in core.py drives evaluation through a dispatch method (_handle) that pattern-matches on AST node type (Name, Attribute, Subscript, List/Tuple/Set/Dict, UnaryOp, BinOp, BoolOp, Compare, Call) and delegates each case to a private handler; results are memoized in a per-instance cache keyed by the AST node object itself, and a failed CannotEval is cached too so repeated lookups of an unsafe node don’t redo the work. Attribute access is routed through a hand-rolled getattr_static() (in my_getattr_static.py) that walks the class MRO and instance __dict__ directly via object.__getattribute__, bypassing __getattr__, __getattribute__, and most descriptor __get__ calls except for a small whitelist of provably-safe descriptor types (slots, wrapper methods). Type and value safety is centralized in utils.py’s of_standard_types/is_standard_types, which recursively verify a value belongs to a small whitelist of builtin types before any operator or builtin call is allowed to touch it, giving the whole library a single choke point that governs what’s considered safe.
Tech Stack
pure_eval is a pure-Python package with no runtime dependencies (install_requires is empty in setup.cfg), packaged with setuptools and versioned automatically from git tags via setuptools_scm. It targets a wide range of Python versions per its CI matrix, and its test suite runs on pytest with coverage collected through coverage and reported via Coveralls. Continuous integration runs on GitHub Actions across the full Python version matrix on Ubuntu, and the only imports used are from the standard library (ast, operator, types, typing, collections).
Code Quality
Tests are organized into tests/test_core.py, tests/test_getattr_static.py, and tests/test_utils.py, and take a rigorous approach: a check_eval helper cross-validates pure_eval’s output against the real eval() output on a live frame, unit-testing correctness against the interpreter itself rather than hardcoded fixtures, with slower exhaustive paths gated behind a PURE_EVAL_SLOW_TESTS environment flag. Type hints are used consistently throughout core.py and utils.py, and the package ships a py.typed marker for downstream type checkers. Error handling is deliberate: a single CannotEval exception is the universal signal that an expression can’t be proven safe, raised and cached consistently rather than silently swallowed, with broad except Exception blocks used intentionally to convert arbitrary evaluation failures into that one exception type. No dedicated linter or formatter configuration was found in the repo, though CI enforces test coverage reporting.
What Makes It Unique Rather than trying to sandbox arbitrary Python execution, a notoriously hard problem, pure_eval inverts the approach: it maintains a conservative whitelist of AST node types and value types that are provably side-effect-free, and refuses the moment it can’t prove safety rather than trying to detect and block unsafe operations after the fact. This ‘assume unsafe unless proven otherwise’ design is what makes it trustworthy enough for IPython, ptpython, and stack_data to embed directly into interactive and traceback code paths that run against arbitrary user objects. It doesn’t compete on feature breadth; its differentiator is a deliberately small, auditable surface area rather than broad expression-language coverage.
Used by 2 apps in this directory
argilla
AI Development · Data Engineering
Collaborate on high-quality AI training data with a self-hosted annotation platform built for LLMs, NLP, and multimodal models.
Grist
Databases · No Code Platforms
A modern relational spreadsheet that combines Python-powered formulas, drag-and-drop dashboards, and granular access controls in a self-hostable, SQLite-backed data platform.