pure_eval

Evaluates Python AST expression nodes safely, without triggering side effects like property getters or function calls.

Library
PyPI
v0.2.3
50stars
MIT License

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
25/100Needs Attention
Development Activity4
Maintenance0
Community24
Maturity60
Momentum12

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
70/100Good
Architecture85
Code Quality82
Innovation78
Learning Curve35

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.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search