Python SDK
The Python SDK is a headless server library: it decides entitlements and
placements in-process, with no network call and no persistence beyond
memory. The server holds the user context and an Playbook snapshot and
passes both to the SDK; the SDK decides locally.
It is intentionally a small surface — the same decision core as the TypeScript
SDK’s headless LocalRuntime, exposed as a clean public class. It is not a
port of the browser SDK (no React, no hooks, no decision cache — see
Non-goals).
Install
Section titled “Install”The revturbine package (requires Python ≥ 3.10) is published to PyPI:
pip install revturbinePin to a version whose schema matches the Playbook you deploy — see
Refreshing the Playbook:
pip install "revturbine==<version>"Quick start
Section titled “Quick start”Construct one SDK per (user_context, exported_config), then check
entitlements and decide placements:
import jsonfrom revturbine import RevTurbineCustomerSdk
with open("playbook.json") as f: exported_config = json.load(f)
sdk = RevTurbineCustomerSdk( user_context={ "tenant_id": "tenant_abc", "user_id": "user_123", # optional — the user's current plan + usage: "plan_handle": "pro", "usage": {"api_calls": {"used": 900.0, "limit": 1000.0}}, }, exported_config=exported_config,)
# Entitlement gateresult = sdk.check_entitlement("advanced_analytics")if not result["allowed"]: raise PermissionError(result["reason"])
# Placement decision (which payload, if any, to show)decision = sdk.get_placement_decision( {"placement_id": "pl_dashboard_upsell", "user_id": "user_123"})if decision["visible"]: render(decision["output"])
# Batch form — same decision path, order-preservingdecisions = sdk.get_placement_decisions( [ {"placement_id": "pl_a", "user_id": "user_123"}, {"placement_id": "pl_b", "user_id": "user_123"}, ])User context
Section titled “User context”| Key | Required | Meaning |
|---|---|---|
tenant_id | ✅ | Tenant identifier. |
user_id | ✅ | Current user identifier. |
plan_handle | — | The user’s current plan handle (feeds the plan + entitlements providers). |
plan_name | — | Display name; defaults to plan_handle. |
usage | — | Per-entitlement overrides: {handle: {"used": float, "limit": float}}. |
tenant_id and user_id are the only required fields; an empty/missing one
raises ValueError at construction.
Public surface
Section titled “Public surface”| Method | Returns |
|---|---|
check_entitlement(handle, context=None) | {status, allowed, reason, ...} |
get_placement_decision(input) | {placement_id, visible, output, content, ...} |
get_placement_decisions(inputs) | ordered list of the above |
That is the entire public API. There is no storage/persistence parameter — the instance is stateless and in-memory by construction; construct a fresh one per user context.
Runtime model
Section titled “Runtime model”Unlike the TypeScript SDK (which has revturbine_server / custom_endpoints
/ local_only runtime modes), the Python SDK is local-only by design.
There is no runtime-mode switch and no network path:
Python RevTurbineCustomerSdk | |
|---|---|
| Network | None — decides from the supplied Playbook |
| Persistence | None — InMemoryStorage only; no file/db, no decision cache |
| State | None across calls; no interaction/suppression hydration |
| Inputs | user_context + Playbook, both supplied by the server |
This makes it safe on a request hot path: an entitlement gate is a pure in-process computation, with no decision-API round-trip and no shared mutable state between requests.
Refreshing the Playbook
Section titled “Refreshing the Playbook”The Playbook is a snapshot exported from the RevTurbine control plane.
The SDK never fetches or caches it — your service owns its lifecycle:
- Fetch/export the latest
PlaybookJSON (on boot, on a timer, or on a webhook from the control plane). - Construct a new
RevTurbineCustomerSdkwith the new snapshot. Because the instance is stateless, “refresh” is just “rebuild” — atomically swap the reference your handlers read.
# e.g. behind a lock / atomic reference, refreshed every N minutes_sdk = RevTurbineCustomerSdk(user_context=ctx, exported_config=load_config())Pin the installed SDK tag to a version whose schema matches the
Playbook you deploy; a snapshot newer than the SDK’s schema can decide
differently than your TypeScript frontend.
Non-goals
Section titled “Non-goals”The Python SDK is the headless server decision core only. The following are intentionally not ported (plan 33 REQ-14) — they are browser/full-SDK concerns with no server equivalent:
- React / JSX components and hooks (no Python equivalent).
- Browser storage,
localStoragetheme persistence,window.RevTurbine. - The HTTP-backed / dual-mode dispatch (
runtime_mode) of the original plan — superseded; the Python library is local-only. identify,dismiss/snooze/convert,track_treatment_interaction,get_trial_status,capture,bootstrap_placement_decisions,get_user_context, decision-cache / interaction-state hydration.- Segment / targeting / personalization-token derivation from raw traits (the evaluator matches pre-resolved data; deriving segments is browser/segments machinery).
Parity guarantee
Section titled “Parity guarantee”Every public method delegates, with zero added decision logic, to the same decision substrate the TypeScript SDK uses. Our parity suite runs all three ports — TypeScript, Python, and Rust — through identical fixtures and asserts byte-identical normalized output; a divergence is treated as a Python-port bug, never a fixture to loosen. This is what lets you gate in Python and render in TypeScript and trust they decided the same thing.