From fabedcd41dc6a9d2b5877e01bbefaf72830a9409 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Mon, 4 May 2026 08:42:29 +0300 Subject: [PATCH] Document TimersRouter prefix semantics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit router.md showed `TimersRouter(prefix="my-service:")` in the first example without ever explaining what `prefix` does. This is the only mechanism for sharing one Redis between multiple services without key collision, so it warrants explicit coverage. Add a "How `prefix` works" section that: - spells out that `prefix` is concatenated into each subscriber's and publisher's full topic, and shows the resulting Redis keys (`timers_timeline:my-service:invoices` etc.) - recommends per-service prefixes for shared-Redis isolation - explains that `broker.publish` / `broker.cancel_timer` / `has_pending` / `get_pending_timers` / `cancel_all` do *not* see the router's prefix — callers must pass the prefixed topic explicitly, or use a publisher scoped to the router (which applies the prefix automatically). --- docs/usage/router.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/usage/router.md b/docs/usage/router.md index becea73..5201603 100644 --- a/docs/usage/router.md +++ b/docs/usage/router.md @@ -15,6 +15,41 @@ async def handle_invoice(invoice_id: str) -> None: print(f"Invoice due: {invoice_id}") ``` +## How `prefix` works + +The `prefix` you pass to `TimersRouter` is concatenated to every topic registered via that router — both subscribers and publishers. Given: + +```python +router = TimersRouter(prefix="my-service:") + +@router.subscriber("invoices") +async def handle_invoice(...): ... +``` + +…the subscriber listens on the full topic `my-service:invoices`, and Redis stores its timers under: + +- `timers_timeline:my-service:invoices` +- `timers_payloads:my-service:invoices` + +This is the recommended way to isolate multiple services or environments that share one Redis instance — give each its own `TimersRouter` prefix and they will never collide on keys. + +### Publishing to a prefixed router + +`broker.publish(...)` does **not** know about the router's prefix — it only applies the broker's own prefix (empty by default). To target a prefixed subscriber from `broker.publish`, pass the full topic: + +```python +await broker.publish("INV-001", topic="my-service:invoices", activate_in=...) +``` + +Or define a publisher inside the same router and use it — the router's prefix is applied automatically: + +```python +publisher = router.publisher("invoices") +await publisher.publish("INV-001", activate_in=...) # lands on my-service:invoices +``` + +The same rule applies to `broker.cancel_timer`, `broker.has_pending`, `broker.get_pending_timers`, and `broker.cancel_all`: pass the prefixed topic, or call the equivalent methods through a router-scoped publisher. + ## Including a router in the broker ```python