-
Notifications
You must be signed in to change notification settings - Fork 0
Getting Started
🌐 throttlekit.in · 🧪 runnable examples — one self-contained script per axis (sync + async service door, direct Redis, cost via debit, the admit lifecycle, a FastAPI app).
pip install throttlekit-py # the gRPC ServiceBackend
pip install "throttlekit-py[redis]" # + a redis client for the direct RedisBackendInstalled as throttlekit-py, imported as throttlekit. Requires Python ≥ 3.10. The wheel pulls grpcio + protobuf (the ServiceBackend stubs); the [redis] extra adds a redis client for the direct door.
Point it at a running throttlekit-server. The core (inside the service) computes every decision.
ServiceBackend is store-agnostic — the server it points at can be backed by in-process memory, Redis, Postgres, or DynamoDB (chosen with --store on the server); you send the same requests and the decision is the core's regardless. (Deno KV and Cloudflare are edge-runtime stores, reachable only inside those runtimes — not through the service door.) The core's Polyglot & Python guide has the full "all stores from Python" recipe (a Docker-compose stack per backend).
from throttlekit import ServiceBackend
with ServiceBackend("localhost:50051") as rl:
d = rl.check("api", api_key) # consume one unit against the "api" policy
if not d.allowed:
retry_after_ms = d.retry_after_ms
... # 429For TLS/mTLS pass channel credentials; the default is an insecure channel (loopback/dev only):
import grpc
creds = grpc.ssl_channel_credentials(root_certificates=open("ca.pem", "rb").read())
rl = ServiceBackend("limiter.internal:50051", credentials=creds)Tip — see your decisions live. Start the server with
--tuifor ThrottleKit Lens, a built-in terminal dashboard (throttlekit-server --config x.yaml --tui). It watches the server's decisions, so the traffic your Python client drives shows up there — the full ops board across eight tabs, plus live binding-axis attribution (which of rate / concurrency / cost bound each denial) forunifiedpolicies. Prefer to read it from Python? TheMonitorBackendreads the same state over the server's Monitor door — see Fleet & Monitor clients. (Headless or in production? Emit OpenTelemetry → Grafana instead — see the Monitoring guide.)
Configure a strategy and point it at the Redis your fleet shares. The decision is produced server-side, in Lua (the core's own vendored script) — check is the whole surface.
import redis
from throttlekit import RedisBackend, Gcra
client = redis.Redis.from_url("redis://localhost:6379")
api = RedisBackend(client, Gcra(limit=100, period_ms=60_000, burst=20), prefix="prod")
d = api.check(api_key) # defaults to the Redis server clock (skew-free across a fleet)Strategies: Gcra, TokenBucket, FixedWindow, SlidingWindow, SlidingWindowLog. The prefix joins as f"{prefix}:{key}" — the same key scheme the core uses, so a Python and a Node client on one limit address the same Redis key. The backend is client-agnostic: pass any object with evalsha / eval (redis-py satisfies it structurally).
Both backends return a frozen Decision dataclass — the five fields of the core's decision, on the wire:
| Field | Meaning |
|---|---|
allowed |
whether the request is permitted |
limit |
the effective ceiling (burst capacity or window quota) |
remaining |
whole units left before the next rejection (never negative) |
reset_at |
epoch-ms at which the limiter is fully replenished |
retry_after_ms |
ms to wait before retrying (0 when allowed) |
ServiceBackend also exposes Forecast (from forecast) and Admission (from admit — see The axes).
A denial is not an error — it's a Decision with allowed is False. Exceptions are reserved for operational faults, all subclasses of ThrottleKitError:
| Exception | When |
|---|---|
PolicyNotFoundError |
the named policy isn't configured in the service (gRPC NOT_FOUND) |
OperationNotSupportedError |
wrong op for the policy kind — e.g. admit on a rate limiter, check on a meter (gRPC UNIMPLEMENTED) |
ServiceUnavailableError |
the service is unreachable (gRPC UNAVAILABLE) |
from throttlekit.errors import PolicyNotFoundError
try:
rl.check("typo-policy", key)
except PolicyNotFoundError:
...Next: The axes — reaching cost, two-tier, and concurrency from Python.