ProvenPin Install

simpleeval safe user formulas with custom names, functions and limits: working versions (tested 2026-10-16)

Evaluate untrusted user formulas with simpleeval using custom names and functions, per-type attribute allowlists, and the built-in power, shift and string size limits.

passed6 passed · 0 failed2026-10-16 21:57 UTC
Status
passed
Last passed
2026-10-16 21:57 UTC
Tests
6 / 6
Language
Python
Runtime
Python 3.13.16
pinned versions2
simpleeval@1.0.8pytest@9.1.1
run it
uv venv -q .venv && uv pip install -q --python .venv/bin/python -r requirements.txt
.venv/bin/python -m pytest -q
test environment
Python 3.13.16
linux 6.17.0-1022-azure
x64
runner: github-actions 38972656245

The code

Exactly the files that passed. Your agent can pull them with get_recipe("simpleeval-safe-expression") (install), or see them on GitHub.

requirements.txt
simpleeval==1.0.8
pytest==9.1.1
formulas.py
from simpleeval import DEFAULT_FUNCTIONS, EvalWithCompoundTypes

SAFE_ATTRS = {
    str: {"lower", "upper", "strip", "title"},
    int: {"bit_length"},
    float: {"is_integer"},
    list: {"count", "index"},
}


def evaluate(formula, values=None, functions=None, allowed_attrs=SAFE_ATTRS):
    fns = dict(DEFAULT_FUNCTIONS)
    fns["clamp"] = lambda x, lo, hi: max(lo, min(hi, x))
    fns["total"] = lambda items: sum(items)
    if functions:
        fns.update(functions)

    evaluator = EvalWithCompoundTypes(
        names=dict(values or {}),
        functions=fns,
        allowed_attrs=allowed_attrs,
    )
    return evaluator.eval(formula)
tests/test_formulas.py
import pytest
from simpleeval import FeatureNotAvailable, FunctionNotDefined, IterableTooLong, NumberTooHigh, simple_eval

from formulas import evaluate


def test_custom_names_functions_and_builtins_kept():
    assert evaluate("clamp(qty * price, 0, 100)", {"qty": 5, "price": 30}) == 100
    assert evaluate("int('3') + total([1, 2])", {}) == 6


def test_function_registration_gotchas():
    with pytest.raises(FunctionNotDefined):
        evaluate("double(2)", {"double": lambda x: x * 2})
    with pytest.raises(FeatureNotAvailable):
        evaluate("1", {}, functions={"danger": eval})


def test_allowed_attrs_is_per_type():
    assert evaluate("code.upper()", {"code": "ab"}) == "AB"
    assert evaluate("n.bit_length()", {"n": 7}) == 3
    with pytest.raises(FeatureNotAvailable):
        evaluate("n.real", {"n": 3})


def test_dunder_attrs_blocked_even_without_allowlist():
    with pytest.raises(FeatureNotAvailable):
        evaluate("x.__class__", {"x": "hi"})
    with pytest.raises(FeatureNotAvailable):
        evaluate("x.__class__", {"x": "hi"}, allowed_attrs=None)


def test_arithmetic_and_size_limits():
    with pytest.raises(NumberTooHigh):
        evaluate("9 ** 9 ** 9", {})
    with pytest.raises(IterableTooLong):
        evaluate("'x' * 10 ** 7", {})


def test_compound_types_need_the_compound_evaluator():
    assert evaluate("[n * 2 for n in nums]", {"nums": [1, 2, 3]}) == [2, 4, 6]
    assert evaluate("{n: n * n for n in nums}", {"nums": [1, 2]}) == {1: 1, 2: 4}
    with pytest.raises(FeatureNotAvailable):
        simple_eval("[1, 2]")
pytest.ini
[pytest]
pythonpath = .

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.