7.1.0
ASGI-lifespan-driven warm-up and Starlette body-typing fix (v7.1.0)
- Added —
guard.lifespan.guard_lifespanandguard.lifespan.make_lifespan(existing_lifespan=None)helpers. Wireapp = FastAPI(lifespan=guard_lifespan)to fully initialize guard-core's security pipeline, agent integrations, OTEL/Logfire providers, Redis connection, and cloud-IP cache during ASGI lifespan startup. First request hits a pre-warmed middleware with zero re-initialization. Backed by a per-SecurityConfigshared-state registry (guard._middleware_state) — Starlette's middleware-stack architecture would normally create a separateSecurityMiddlewareinstance for the request path (causing duplicatecomposite_handler.start()calls and leaked agent/OTEL worker tasks), but the registry guarantees per-config init runs exactly once and the live request-handling instance adopts the spawned instance's pipeline/agent/event-bus by reference. - Added —
SecurityMiddleware.mark_initialized(): public method used by the lifespan helpers to record that initialization has completed, so external callers no longer have to reach into the underscore-prefixed_initializedattribute. - Changed —
SecurityMiddlewarenow backs its per-instance state (security pipeline, composite agent handler, event bus, metrics collector, response factory, validator, bypass handler, behavioral processor) via a module-levelguard._middleware_stateregistry keyed byid(config). MultipleSecurityMiddlewareinstances sharing the sameSecurityConfig(e.g. a lifespan-spawned instance + Starlette's live request-handling instance, or a sub-app mounted middleware) now share state by reference instead of each rebuilding their own copy. Eliminates theOTEL TracerProvider already setwarning that previously fired on first request whenenable_otel=Trueorenable_logfire=Trueandguard_lifespanwas wired in. - Documented —
SecurityMiddleware.initialize()is now formally part of the public API for advanced lifespan integration patterns. Without lifespan wiring, fastapi-guard still works correctly: initialization happens lazily on the first request via the existing fallback path. - Fixed —
StarletteGuardResponse.bodynow always returnsbytesinstead of leakingbytes | memoryviewfrom the underlying StarletteResponse, silencing a pre-existing mypy invariance error and ensuring downstream code that callslen()/ slicing on the body sees consistent types. - Requires —
guard-core>=3.1.0(declared as unconstrainedguard-corein pyproject; documented here for upgrade guidance). v3.1.0 brings NOSCRIPT-aware rate limiting,lazy_init=Trueas the default, thecloud_ip_storefactory protocol, and country-code/cloud-provider normalization in decorators and dynamic rules.
What's Changed
Full Changelog: 7.0.0...7.1.0