Skip to content

The Tenor Python SDK provides native-speed contract evaluation via PyO3 bindings (not WASM, not subprocess). The Rust evaluator core is compiled directly into a Python extension module, giving you the same evaluation performance as the CLI with a Pythonic API.

Installation

bash
pip install tenor-sdk

Pre-built wheels are available for:

  • Linux x86_64 (glibc 2.17+)
  • Linux aarch64 (glibc 2.17+)
  • macOS x86_64 (10.15+)
  • macOS aarch64 / Apple Silicon (11.0+)
  • Windows x86_64

No Rust toolchain is required for installation. The PyO3 native extension is bundled in the wheel.

Requires Python 3.9 or later.

TenorEvaluator — Local Native Evaluation

TenorEvaluator loads a compiled contract and evaluates it in-process via the Rust core. No network calls, no API key, no platform dependency.

Loading a Contract

python
from tenor import TenorEvaluator

# Load from file path
evaluator = TenorEvaluator.load("./contracts/escrow.tenor.wasm")

# Load from bytes
with open("./contracts/escrow.tenor.wasm", "rb") as f:
    evaluator = TenorEvaluator.from_bytes(f.read())

Evaluating a Contract

python
result = evaluator.evaluate(
    facts={
        "payment_received": True,
        "payment_amount": "5000.00",
        "customer_tier": "premium",
    },
    entities={
        "Order": {"order_001": "pending"},
        "Payment": {"pay_001": "cleared"},
    },
)

print(result.verdicts)
# {
#     "payment_ok": Verdict(value=True, type="Bool", stratum=0),
#     "eligible_for_release": Verdict(value=True, type="Bool", stratum=1),
# }

print(result.evaluation_time_ms)
# 0.3

Computing the Action Space

python
actions = evaluator.action_space(
    facts={
        "payment_received": True,
        "payment_amount": "5000.00",
        "customer_tier": "premium",
    },
    entities={
        "Order": {"order_001": "pending"},
        "Payment": {"pay_001": "cleared"},
    },
)

print(actions)
# {
#     "escrow_agent": [
#         Action(
#             operation="release_funds",
#             precondition_met=True,
#             entities_affected=[
#                 EntityTransition(entity="Order", instance="order_001", from_state="pending", to_state="released")
#             ],
#         )
#     ],
#     "buyer": [
#         Action(
#             operation="dispute_transaction",
#             precondition_met=True,
#             entities_affected=[
#                 EntityTransition(entity="Order", instance="order_001", from_state="pending", to_state="disputed")
#             ],
#         )
#     ],
#     "seller": [],
# }

Action Space for a Single Persona

python
buyer_actions = evaluator.action_space(
    facts={"payment_received": True, "payment_amount": "5000.00"},
    entities={"Order": {"order_001": "pending"}},
    persona="buyer",
)

for action in buyer_actions:
    print(f"{action.operation}: precondition_met={action.precondition_met}")
# dispute_transaction: precondition_met=True

Contract Metadata

python
meta = evaluator.metadata()

print(meta.entities)
# [
#     Entity(name="Order", states=["pending", "approved", "disputed", "released"], initial="pending"),
#     Entity(name="Payment", states=["held", "released", "refunded"], initial="held"),
# ]

print(meta.personas)
# ["escrow_agent", "buyer", "seller"]

print(meta.facts)
# [
#     FactDecl(name="payment_received", type="Bool"),
#     FactDecl(name="payment_amount", type="Money"),
#     FactDecl(name="customer_tier", type="Text"),
# ]

print(meta.contract_hash)
# "sha256:9f4a2b..."

Error Handling

python
from tenor import (
    TenorEvaluator,
    TenorTypeError,
    TenorUndeclaredFactError,
)

try:
    evaluator.evaluate(
        facts={"payment_received": "yes"},  # Wrong type: expected Bool
        entities={},
    )
except TenorTypeError as e:
    print(e.fact)           # "payment_received"
    print(e.expected_type)  # "Bool"
    print(e.received_type)  # "Text"
    print(e)                # "Fact 'payment_received' expected type Bool, got Text"

try:
    evaluator.evaluate(
        facts={"nonexistent_fact": True},  # Undeclared fact
        entities={},
    )
except TenorUndeclaredFactError as e:
    print(e.fact)  # "nonexistent_fact"
    print(e)       # "Fact 'nonexistent_fact' is not declared in this contract"

Full Working Example: Contract Testing with pytest

python
import pytest
from tenor import TenorEvaluator


@pytest.fixture
def evaluator():
    return TenorEvaluator.load("./contracts/escrow.tenor.wasm")


def test_payment_verdict_fires_when_payment_received(evaluator):
    result = evaluator.evaluate(
        facts={"payment_received": True, "payment_amount": "5000.00"},
        entities={"Order": {"order_001": "pending"}},
    )
    assert result.verdicts["payment_ok"].value is True
    assert result.verdicts["payment_ok"].stratum == 0


def test_payment_verdict_absent_when_no_payment(evaluator):
    result = evaluator.evaluate(
        facts={"payment_received": False, "payment_amount": "0.00"},
        entities={"Order": {"order_001": "pending"}},
    )
    assert "payment_ok" not in result.verdicts


def test_release_requires_payment_and_approval(evaluator):
    result = evaluator.evaluate(
        facts={
            "payment_received": True,
            "payment_amount": "5000.00",
            "release_approved": True,
        },
        entities={"Order": {"order_001": "pending"}},
    )
    assert result.verdicts["eligible_for_release"].value is True
    assert result.verdicts["eligible_for_release"].stratum == 1


def test_release_blocked_without_payment(evaluator):
    result = evaluator.evaluate(
        facts={
            "payment_received": False,
            "release_approved": True,
        },
        entities={"Order": {"order_001": "pending"}},
    )
    assert "eligible_for_release" not in result.verdicts


def test_action_space_escrow_agent_can_release(evaluator):
    actions = evaluator.action_space(
        facts={
            "payment_received": True,
            "payment_amount": "5000.00",
            "release_approved": True,
        },
        entities={"Order": {"order_001": "pending"}},
        persona="escrow_agent",
    )
    ops = [a.operation for a in actions if a.precondition_met]
    assert "release_funds" in ops


def test_action_space_buyer_cannot_release(evaluator):
    actions = evaluator.action_space(
        facts={
            "payment_received": True,
            "payment_amount": "5000.00",
            "release_approved": True,
        },
        entities={"Order": {"order_001": "pending"}},
        persona="buyer",
    )
    ops = [a.operation for a in actions]
    assert "release_funds" not in ops


def test_terminal_state_has_no_actions(evaluator):
    actions = evaluator.action_space(
        facts={"payment_received": True, "payment_amount": "5000.00"},
        entities={"Order": {"order_001": "released"}},
    )
    for persona, persona_actions in actions.items():
        order_actions = [
            a for a in persona_actions
            if any(e.entity == "Order" for e in a.entities_affected)
        ]
        assert len(order_actions) == 0, f"{persona} has actions on terminal Order"