Skip to content

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 ExportedConfig 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).

The revturbine package (requires Python ≥ 3.10) is published to PyPI:

Terminal window
pip install revturbine

Pin to a version whose schema matches the ExportedConfig you deploy — see Refreshing the ExportedConfig:

Terminal window
pip install "revturbine==<version>"

Construct one SDK per (user_context, exported_config), then check entitlements and decide placements:

import json
from revturbine import RevTurbineCustomerSdk
with open("exported_config.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 gate
result = 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-preserving
decisions = sdk.get_placement_decisions(
[
{"placement_id": "pl_a", "user_id": "user_123"},
{"placement_id": "pl_b", "user_id": "user_123"},
]
)

| 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.

| 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.

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 ExportedConfig | | Persistence | None — InMemoryStorage only; no file/db, no decision cache | | State | None across calls; no interaction/suppression hydration | | Inputs | user_context + ExportedConfig, 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.

The ExportedConfig is a snapshot exported from the RevTurbine control plane. The SDK never fetches or caches it — your service owns its lifecycle:

  1. Fetch/export the latest ExportedConfig JSON (on boot, on a timer, or on a webhook from the control plane).
  2. Construct a new RevTurbineCustomerSdk with 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 ExportedConfig you deploy; a snapshot newer than the SDK’s schema can decide differently than your TypeScript frontend.

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, localStorage theme 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).

Every public method delegates, with zero added decision logic, to the same decision substrate the TypeScript SDK uses. The shared corpus at revturbine-sdk-internal/tests/parity/ runs both languages 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.