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-sdkPre-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.3Computing 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=TrueContract 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"