Lightweight client for registering apps, batching lifecycle events, and handling HMAC-signed remote commands.
Project description
event-bridge-client (Python)
A lightweight client for connecting a Python application to a control backend:
register the app, batch and push lifecycle events, and handle HMAC-signed remote
commands. Wire-compatible with the Node event-bridge-client — same signing
scheme, same endpoints, same payloads.
Install
pip install event-bridge-client # core (send events, verify commands)
pip install "event-bridge-client[fastapi]" # + FastAPI inbound adapter
Requires Python 3.9+.
Quickstart (FastAPI)
from fastapi import FastAPI, Request
from event_bridge_client import create_client
client = create_client(
base_url="https://bridge.example.com",
api_key="...",
callback_url="https://api.example.com/bridge/commands",
callback_secret="...",
capabilities=["user.ban", "user.unban"],
env="PROD",
)
@client.on_command("user.ban")
async def _(data, ctx):
await ban_account(data["externalUserId"], data["reason"])
return {"ok": True}
app = FastAPI()
@app.post("/bridge/commands")
async def commands(request: Request):
return await client.middleware.fastapi(request)
@app.on_event("startup")
async def _startup():
await client.register()
@app.on_event("shutdown")
async def _shutdown():
await client.aclose()
# Anywhere in your app:
client.events.emit("user.created", {"externalUserId": "usr_123", "email": "a@b.c"})
Command handlers may be sync or async. Each receives (data, ctx) where data
is the raw command payload (a dict) and ctx carries command_id, issued_at,
and issued_by. Return {"ok": True, "result": ...} or {"ok": False, "error": "..."}.
Managed resources
Declare an entity the backend can list / search / view / action — entirely from
the descriptor, with no backend-side code change. Records are never shipped to
the backend; it proxies list / get / action queries back over the same
signed channel.
client.define_resource(
{
"key": "widgetUser",
"label": "Widget User",
"labelPlural": "Widget Users",
"titleField": "email",
"fields": [
{"key": "id", "label": "ID", "type": "string", "listVisible": False},
{"key": "email", "label": "Email", "type": "email", "filterable": True},
{"key": "plan", "label": "Plan", "type": "enum", "enumValues": ["free", "pro"]},
],
"actions": [
{
"capability": "widgetUser.ban",
"label": "Ban",
"confirm": True,
"destructive": True,
"fields": [{"name": "reason", "label": "Reason", "kind": "textarea", "required": True}],
}
],
},
# `query` is a ResourceListQuery: query.page, query.page_size, query.q, ...
list=lambda query: {"records": db.search(query.q, query.page, query.page_size), "total": db.count()},
get=lambda record_id: db.find(record_id), # optional
action=lambda inp: ban(inp["recordId"], inp["params"]["reason"]), # optional
)
Descriptor keys accept either snake_case or camelCase; they're sent to the
backend as camelCase. Field type is one of string, number, boolean,
date, datetime, enum, currency, badge, email, url, json.
Options
create_client(...) keyword arguments:
| Option | Default | Notes |
|---|---|---|
base_url |
required | Control backend base URL |
api_key |
required | API key minted by the backend admin |
callback_url |
required | HTTPS URL the backend POSTs commands to |
callback_secret |
required | HMAC shared secret minted alongside the API key |
capabilities |
() |
Strings matching command types, e.g. user.ban |
env |
"PROD" |
PROD / STAGING / DEV |
enabled |
True |
If False, all methods are no-ops (staged rollout) |
batch_interval_ms |
1500 |
Event batcher flush interval |
batch_max_size |
100 |
Force-flush when this many events are queued |
max_buffer_size |
10000 |
Hard cap on buffered events; oldest dropped past it |
max_retries |
6 |
Exponential-backoff retries for event batch POSTs |
nonce_store |
in-memory | Replay store; supply a shared one for multi-instance |
Replay protection across instances
The default replay cache is in-process — it only protects a single instance.
If you run more than one instance behind a load balancer, supply a shared
nonce_store (e.g. Redis) so a captured command can't be replayed against
another instance inside the 300-second signature window:
class RedisNonceStore:
def __init__(self, redis): self.r = redis
async def has(self, nonce: str) -> bool:
return await self.r.exists(f"bridge:nonce:{nonce}") > 0
async def add(self, nonce: str, ttl_ms: int) -> None:
await self.r.set(f"bridge:nonce:{nonce}", "1", px=ttl_ms, nx=True)
client = create_client(..., nonce_store=RedisNonceStore(redis))
Send-only usage (no FastAPI)
If the app only emits events and never receives commands, you don't need
FastAPI — pip install event-bridge-client and use register() /
events.emit() / aclose(). The middleware is only needed to receive commands.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file event_bridge_client-1.0.0.tar.gz.
File metadata
- Download URL: event_bridge_client-1.0.0.tar.gz
- Upload date:
- Size: 13.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f0870aec6f7f412143955e6c7e3c3ace0a86f6ca833cd037921b5dbe6ee70d7
|
|
| MD5 |
38d930608fb9da938f02ba30b9008cf6
|
|
| BLAKE2b-256 |
c742710f1ed478424b32f4df21fe000d15f3820f61f783426bc7f0b4d01974ff
|
File details
Details for the file event_bridge_client-1.0.0-py3-none-any.whl.
File metadata
- Download URL: event_bridge_client-1.0.0-py3-none-any.whl
- Upload date:
- Size: 17.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.10
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0d740ca991a84557efc2a1fa16374b54b74c1cbc32b0c2d62c4d52904e83d974
|
|
| MD5 |
5aa2fc80c214794ba8db139c9698bd75
|
|
| BLAKE2b-256 |
81595ee447aa4a276d89ffa6e1fdcbbf0d2176b535a8eea1be6fbac647fdf392
|