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"TenorClient — Remote HTTP API
TenorClient calls the Tenor platform for evaluation and execution against managed state.
Constructor
python
from tenor import TenorClient
client = TenorClient(
org="acme",
api_key="tk_live_9f4a2b3c4d5e6f7a8b9c0d1e2f3a4b5c",
# Optional overrides:
base_url="https://api.tenor.run", # default
timeout=10.0, # seconds, default 10.0
retries=3, # automatic retry on 429/5xx, default 3
)Evaluate
python
result = client.evaluate(
contract="escrow",
facts={"payment_received": True, "payment_amount": "5000.00"},
)
print(result.verdicts)
# {"payment_ok": Verdict(value=True, ...), "eligible_for_release": Verdict(value=True, ...)}
print(result.action_space)
# {"escrow_agent": [...], "buyer": [...], "seller": []}Execute a Flow
python
execution = client.execute_flow(
contract="escrow",
flow="release_flow",
persona="escrow_agent",
facts={
"release_approved": True,
"release_reason": "Goods received and inspected",
},
entity_bindings={
"Order": "order_001",
"Payment": "pay_001",
},
idempotency_key="idem_20260215_001",
)
print(execution.status)
# "completed"
for step in execution.steps_executed:
print(f"{step.step}: {step.result}")
for t in step.entity_transitions:
print(f" {t.entity}:{t.instance} {t.from_state} -> {t.to_state}")
# verify_approval: success
# Order:order_001 pending -> approved
# release_payment: success
# Payment:pay_001 held -> released
print(execution.provenance_chain_id)
# "chain_20260215_001"Simulate
python
simulation = client.simulate(
contract="escrow",
flow="release_flow",
persona="escrow_agent",
facts={"release_approved": True},
entity_bindings={"Order": "order_001", "Payment": "pay_001"},
)
print(simulation.status)
# "would_succeed"
for step in simulation.projected_steps:
print(f"{step.step}: would_succeed={step.would_succeed}")
# verify_approval: would_succeed=True
# release_payment: would_succeed=TrueError Handling
python
from tenor import (
TenorClient,
TenorApiError,
TenorForbiddenError,
TenorPreconditionError,
TenorConflictError,
TenorRateLimitError,
)
try:
client.execute_flow(
contract="escrow",
flow="release_flow",
persona="buyer", # Not authorized for release_funds
facts={"release_approved": True},
entity_bindings={"Order": "order_001"},
)
except TenorForbiddenError as e:
print(e.persona) # "buyer"
print(e.operation) # "release_funds"
print(e.allowed_personas) # ["escrow_agent"]
except TenorPreconditionError as e:
print(e.operation) # "verify_release"
print(e.precondition) # "verdict_present(release_approval_ok)"
print(e.error_message) # "Release conditions not met."
except TenorConflictError as e:
print(e.entity) # "Order"
print(e.instance) # "order_001"
print(e.expected_state) # "pending"
print(e.actual_state) # "disputed"Full Working Example: FastAPI Service
A complete example using the Python SDK with FastAPI to build an escrow management API:
python
from fastapi import FastAPI, Header, HTTPException
from pydantic import BaseModel
from tenor import TenorClient, TenorForbiddenError, TenorPreconditionError
import os
app = FastAPI(title="Escrow Service")
tenor = TenorClient(
org="acme",
api_key=os.environ["TENOR_API_KEY"],
)
class ReleaseRequest(BaseModel):
reason: str
class DisputeRequest(BaseModel):
reason: str
@app.get("/orders/{order_id}/actions")
async def get_actions(order_id: str, x_persona: str = Header()):
"""Return available actions for the given persona and order."""
try:
actions = tenor.actions(
contract="escrow",
persona=x_persona,
)
return {
"persona": x_persona,
"order_id": order_id,
"actions": [
{
"operation": a.operation,
"available": a.precondition_met,
}
for a in actions
],
}
except TenorForbiddenError:
raise HTTPException(status_code=403, detail=f"Unknown persona: {x_persona}")
@app.post("/orders/{order_id}/release")
async def release_order(order_id: str, body: ReleaseRequest, x_persona: str = Header()):
"""Execute the release flow for an order."""
try:
result = tenor.execute_flow(
contract="escrow",
flow="release_flow",
persona=x_persona,
facts={
"release_approved": True,
"release_reason": body.reason,
},
entity_bindings={"Order": order_id},
)
return {
"status": result.status,
"provenance_chain_id": result.provenance_chain_id,
"steps": [
{"step": s.step, "result": s.result}
for s in result.steps_executed
],
}
except TenorForbiddenError as e:
raise HTTPException(
status_code=403,
detail=f"Persona '{e.persona}' cannot perform '{e.operation}'",
)
except TenorPreconditionError as e:
raise HTTPException(status_code=422, detail=e.error_message)
@app.post("/orders/{order_id}/dispute")
async def dispute_order(order_id: str, body: DisputeRequest, x_persona: str = Header()):
"""Execute the dispute flow for an order."""
try:
result = tenor.execute_flow(
contract="escrow",
flow="dispute_flow",
persona=x_persona,
facts={"dispute_reason": body.reason},
entity_bindings={"Order": order_id},
)
return {
"status": result.status,
"provenance_chain_id": result.provenance_chain_id,
}
except TenorForbiddenError as e:
raise HTTPException(
status_code=403,
detail=f"Persona '{e.persona}' cannot perform '{e.operation}'",
)
except TenorPreconditionError as e:
raise HTTPException(status_code=422, detail=e.error_message)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"