You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/adapters/custom.md
+290Lines changed: 290 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -219,6 +219,296 @@ Everything works from here — decorators, CLI, dashboard, audit log — with SQ
219
219
220
220
---
221
221
222
+
## Distributed support
223
+
224
+
The six abstract methods give you persistence. To unlock full distributed behaviour — live dashboard updates, cross-instance global maintenance sync, and webhook deduplication — implement three additional optional methods. Each one has a default that works correctly for single-instance deployments, so you can add them incrementally.
225
+
226
+
---
227
+
228
+
### The three distributed methods
229
+
230
+
```python
231
+
from collections.abc import AsyncIterator
232
+
from shield.core.backends.base import ShieldBackend
Copy file name to clipboardExpand all lines: docs/changelog.md
+17Lines changed: 17 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -28,10 +28,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
28
28
-`_ShieldCallable` extended with an optional `signature=` override and updated `__call__` to forward the `response` kwarg to `dep_raise` when present — fully backward compatible with all existing decorators
29
29
-`examples/fastapi/dependency_injection.py` updated to include `@deprecated` as a `Depends()` example and a clear explanation of why `@force_active` cannot be used as a dependency
30
30
31
+
#### Webhook Deduplication
32
+
-`ShieldBackend` ABC gains `try_claim_webhook_dispatch(dedup_key, ttl_seconds)` — returns `True` if this instance should fire webhooks, `False` if another instance already claimed the right for this event. Default implementation always returns `True` (single-instance backends never need dedup).
33
+
-`RedisBackend` overrides `try_claim_webhook_dispatch()` using `SET NX EX` — the first instance to win the atomic write fires webhooks; all others skip. Fails open: a Redis error returns `True` so webhooks are over-delivered rather than silently dropped.
34
+
-`ShieldEngine._fire_webhooks` refactored — now schedules a single `_dispatch_webhooks` task instead of one task per URL. The task computes a deterministic SHA-256 dedup key from `event + path + serialised RouteState`, claims dispatch rights via the backend, then fans out to individual webhook URLs only if the claim succeeds.
35
+
- Dedup key is deterministic across instances: because the scheduler produces an identical `RouteState` on all instances for the same window activation, the key is the same fleet-wide and only one instance wins.
36
+
- TTL on the dedup key defaults to 60 seconds — if the winning instance crashes mid-dispatch the key expires and re-delivery is possible on the next activation cycle.
37
+
38
+
#### Distributed Global Maintenance
39
+
-`RedisBackend` now publishes a lightweight invalidation signal to `shield:global_invalidate` whenever `set_global_config()` is called — any other instance subscribed to this channel immediately drops its in-process `GlobalMaintenanceConfig` cache
40
+
-`ShieldBackend` ABC gains `subscribe_global_config()` — an async generator that yields `None` on each remote global config change; default implementation raises `NotImplementedError` (no-op for `MemoryBackend` and `FileBackend`)
41
+
-`ShieldEngine.start()` — starts a background `asyncio.Task` that listens for global config invalidation signals and calls `_invalidate_global_config_cache()` on each one; idempotent, safe to call multiple times
42
+
-`ShieldEngine.stop()` — cancels and awaits the listener task; called automatically by `__aexit__`
43
+
-`ShieldEngine.__aenter__` / `__aexit__` updated to call `start()` / `stop()` so CLI scripts using `async with ShieldEngine(...)` get distributed invalidation automatically
44
+
-`ShieldRouter.register_shield_routes()` calls `engine.start()` at application startup so FastAPI apps also start the listener without requiring the context manager
45
+
- For `MemoryBackend` / `FileBackend` the new code path is a transparent no-op — `NotImplementedError` is caught, the task exits immediately, and single-instance cache behaviour is unchanged
46
+
31
47
#### Documentation & Communication
32
48
- Early Access notice added to README and docs homepage — communicates that the library is fully functional and actively developed, and invites community feedback via GitHub Issues
33
49
- Webhooks and Custom Responses added to the Key Features table in the docs homepage
34
50
- Key Features section added to `README.md`
51
+
- New guide: **Distributed Deployments** (`docs/guides/distributed.md`) — covers backend capability matrix, the request lifecycle across instances, global maintenance cache invalidation architecture, scheduler behaviour and webhook deduplication in multi-instance setups, OpenAPI schema staleness, the fail-open guarantee, and a production checklist. Explains why `FileBackend` intentionally does not support cross-instance sync and when to use each backend.
35
52
36
53
### Changed
37
54
-`@deprecated` docstring updated — no longer described as decorator-only; documents the `Depends()` usage pattern
0 commit comments