Skip to main content

Python client for ThrottleKit — distributed rate limiting via gRPC or direct Redis.

Project description

throttlekit (Python)

Python client for ThrottleKit — distributed rate limiting against the one Node core, reached through either of two pluggable backends and proven against the same golden vectors:

Backend Path Decision computed in Use it when
ServiceBackend gRPC → throttlekit-server the service (= the core) you want the full surface (check/check_many/peek/forecast) and to never touch the raw wire
RedisBackend vendored Lua → the same Redis a Node fleet uses Lua-in-Redis (the core's own script) you already run Redis and want one hop, no extra service — check only

Status: experimental (alpha). The contract (throttlekit.proto, the golden vectors, and the extracted Lua) is vendored and checksum-pinned from the frozen throttlekit 1.0 core; this client tracks it. The raw Lua wire is not a frozen contract yet (it ships frozen: false), so the RedisBackend is explicitly experimental and may change with the core's scripts.

The one invariant

The whole ThrottleKit design rests on it: exactly one thing computes a Decision — the Node core, directly or as Lua-in-Redis. Neither backend re-implements an algorithm, so there is no second rate limiter to keep in sync and no float-determinism risk. The RedisBackend marshals ARGV, runs the core's vendored script, and decodes the reply; the decision is produced server-side, in Lua.

Install

Installed as throttlekit-py, imported as throttlekit (PyPI's throttlekit is an unrelated project):

pip install throttlekit-py            # the gRPC ServiceBackend
pip install "throttlekit-py[redis]"   # + a redis client for the direct RedisBackend

Use — the service door

from throttlekit import ServiceBackend

with ServiceBackend("localhost:50051") as rl:
    d = rl.check("api", api_key)
    if not d.allowed:
        ...  # 429 — retry after d.retry_after_ms

check / check_many / peek / forecast return frozen Decision / Forecast dataclasses; debit(policy, key, tokens) meters a windowed token budget (the cost axis — debit the actual tokens a stream produces, e.g. for an LLM gateway). A denial is a normal Decision (allowed is False), never an exception; gRPC faults map to PolicyNotFoundError / OperationNotSupportedError / ServiceUnavailableError.

Use — the direct Redis door

Configure a strategy and point it at the Redis your fleet shares. check is the whole surface (it is the contract-vectored, Lua-computed decision); peek / forecast deliberately stay on the service door, where the core — not a re-derived client port — computes them.

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)             # now defaults to the Redis server clock (skew-free across a fleet)
if not d.allowed:
    ...                            # 429 — retry after d.retry_after_ms

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), exactly as the Node RedisStore does.

How this stays in lock-step with the core

scripts/sync_contract.py vendors, with checksums, from the core repo:

  • contract/ — the dev/test artifacts: throttlekit.proto (→ gRPC stubs) and golden-vectors.json.
  • src/throttlekit/_scripts/ — the runtime Lua the RedisBackend executes (shipped in the wheel), with the core's manifest.json (which carries each script's sha256).

tests/test_contract.py is the drift-gate (the vendored bytes must match their checksums and the pinned contractVersion). But the real proof is behavioral:

  • tests/test_redis_backend.py replays every rate-limit golden vector — the full, time-parametrized timeline — through the Python client → vendored Lua → real Redis, and asserts every reply field equals the Node oracle bit-for-bit. (Because the direct path puts an explicit now in ARGV, it can do the rigorous time-parametrized replay the cross-process service door can't.)
  • tests/test_service_backend.py starts a real throttlekit-server and asserts the clock-independent behavior over gRPC.

Develop

pip install -e .[dev]
python scripts/sync_contract.py          # vendor proto + vectors + Lua from ../GreenfeildProject (the core)
python scripts/gen_proto.py              # generate the gRPC stubs from the vendored proto
pytest                                   # unit + contract; the Redis/service tests skip if their backend is absent
ruff check . && mypy                     # lint + types

The RedisBackend conformance needs a reachable Redis: it uses THROTTLEKIT_REDIS_URL or the project default redis://localhost:6380, and skips cleanly when neither is up.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

throttlekit_py-0.2.1.tar.gz (29.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

throttlekit_py-0.2.1-py3-none-any.whl (24.5 kB view details)

Uploaded Python 3

File details

Details for the file throttlekit_py-0.2.1.tar.gz.

File metadata

  • Download URL: throttlekit_py-0.2.1.tar.gz
  • Upload date:
  • Size: 29.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for throttlekit_py-0.2.1.tar.gz
Algorithm Hash digest
SHA256 2873b830ffe5c04dfdf4d88cdb561d8fceef31edfea39546784c8cd288df881e
MD5 88b80125a44e28e54d54bc973dbcaca1
BLAKE2b-256 db2979d355b5a95b35ead4a2e1c1a616000192c3f6d53f39d5aa3b6d698b5a9e

See more details on using hashes here.

File details

Details for the file throttlekit_py-0.2.1-py3-none-any.whl.

File metadata

  • Download URL: throttlekit_py-0.2.1-py3-none-any.whl
  • Upload date:
  • Size: 24.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for throttlekit_py-0.2.1-py3-none-any.whl
Algorithm Hash digest
SHA256 939243ddca98581001c5d9ff48c018b3d501eb68e46c81129c35a86c9ed0ab01
MD5 983823e276b2543ed1fbb193dfd432f4
BLAKE2b-256 60b4a407775c632d0a1a1496a1d3b27a49883ae0cd369096f97bea51a09fda31

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page