v7.4.0
Initialization-status probe and a closed lifespan-discoverability trap (v7.4.0)
- Added —
SecurityMiddleware.get_initialization_status()reports cloud-provider and geo-IP warmup readiness. It delegates to guard-core'sHandlerInitializer.get_initialization_status()and JSON-encodes the result, sodatetimevalues serialize as ISO 8601 strings.guard.status.add_status_route(app, path="/_guard/status")is an opt-in helper that wires it as aGETroute — nothing is registered unless you call it. Cheap enough to back a Kubernetes/ALB warmup probe. Requiresguard-core>=3.8.0. - Added —
guard.lifespan.guard_startup(app): a public, awaitable warm-up entry point for host frameworks — NiceGUI, Chainlit, Gradio, and similar — that own the ASGIlifespanslot internally and only expose their own startup-hook registration API (e.g. NiceGUI'sapp.on_startup). It performs exactly whatguard_lifespandoes on entry (locate the middleware, adopt or build warm state, initialize, mark initialized, register state) by reusing the same private helper rather than duplicating it, and shares its idempotency guarantee: the shared-state registry is keyed byid(config), so a second call finds the state already registered and adopts it instead of re-initializing or re-fetching anything. - Fixed —
SecurityMiddlewarenow warns when it initializes lazily on the first request whileSecurityConfig.lazy_init=Falseis set. That combination previously failed silently:lazy_initonly governs guard-core's own Redis-gated background warmup, not whetherSecurityMiddleware.initialize()itself runs at ASGI startup versus on the first request — that timing is controlled entirely by whetherguard_lifespan/make_lifespan/guard_startupis wired in. A user who setlazy_init=Falseand wired none of them reasonably expected boot-time initialization and instead hit a confusing "uninitialized" state on their first request. The new warning names the mismatch explicitly; initialization behavior is unchanged. - Changed (example) —
examples/simple_app/main.pynow wiresmake_lifespan, composed with the example's own startup/shutdown logging (previously registered via@app.on_event, which had gone silently inert now that Starlette no longer readson_startup/on_shutdownonce an explicitlifespanis set), and demonstratesadd_status_route. The flagship example now shows the fully-correct eager-init-plus-readiness-probe setup instead of leaving it to be inferred from docs alone. - Docs — The boot-time-initialization precondition — previously one sentence at the bottom of
first-steps.mdand never mentioned next tolazy_inititself, which wasn't documented insecurity-config.mdat all — is now a prominent warning admonition, cross-linked fromlazy_init's (new) entry in the configuration reference. Both docs now present all three initialization-wiring tiers in order:guard_lifespan(you own the app),make_lifespan(you have a lifespan to compose with),guard_startup(the host framework owns the lifespan slot). - Compatibility — Additive only. No change to middleware dispatch ordering or to what is blocked vs. allowed by any existing check — locked in by a new regression test covering a representative blocked and allowed request.
get_initialization_status/add_status_routerequireguard-core>=3.8.0(unreleased at time of writing; tracked at rennf93/guard-core#50).guard-coreremains an unconstrained dependency inpyproject.tomlper this project's convention, so installing against an older guard-core only raisesAttributeErrorwhen those two new entry points are actually called — not at import time, and not for any of this release's other changes.
What's Changed
- fix(tests): repair behavioural tests against guard-core 3.7.0 by @rennf93 in #106
- docs(security): document the app-server forwarded-header prerequisite by @rennf93 in #107
- feat(middleware): opt-in initialization-status probe + close the lifespan-discoverability trap by @rennf93 in #108
Full Changelog: 7.3.1...7.4.0