Releases: modern-python/lite-bootstrap
1.2.1
lite-bootstrap 1.2.1 — lift the FastAPI 0.137 cap
1.2.1 is a patch release. No public-API or behavior changes. It removes the temporary fastapi<0.137 ceiling introduced in 1.1.1, now that the upstream prometheus-fastapi-instrumentator fix has shipped.
Dependency constraints
fastapi<0.137cap removed from thefastapiextra (the single declaration everyfastapi-*extra composes from). FastAPI 0.137 and 0.138 now resolve.prometheus-fastapi-instrumentatorfloor raised on thefastapi-metricsextra:>=6.1→>=8.0.1. The crash that motivated the cap required FastAPI ≥0.137 and instrumentator ≤8.0.0, so lifting the FastAPI ceiling is paired with a floor that guarantees the fixed instrumentator wherever metrics are installed.
The original cap was added in 1.1.1 because prometheus-fastapi-instrumentator read route.path unconditionally and crashed on FastAPI 0.137's internal _IncludedRouter route type. That is fixed upstream in instrumentator v8.0.1 (issue #370, closed 2026-06-22). lite-bootstrap's own offline-docs guard (the isinstance(route, Route) filter shipped in 1.1.1) remains in place.
Verified end-to-end against FastAPI 0.138.0 + prometheus-fastapi-instrumentator 8.0.2: full suite green at 100% coverage, including the offline-docs and metrics paths that previously crashed.
Backwards compatibility
Fully backward compatible with 1.2.0. No public API or behavior changed; this is purely a loosening of resolution constraints. Installs that were held at FastAPI ≤0.136 by the cap will now resolve forward to current FastAPI.
References
- Upstream fix: prometheus-fastapi-instrumentator v8.0.1, issue #370
- Original cap: 1.1.1 (PR #122)
1.2.0
lite-bootstrap 1.2.0 — deeper seams: structured-log payload, unified teardown guard, OTel config tidy-up
1.2.0 is a minor release. Backward compatible with 1.1.1, with one observable behavior change (a new double-attach warning on Litestar and FastStream). It lands the shippable results of an architecture-deepening sweep: the structlog→Sentry contract gets a single owner, the teardown-on-shutdown guard is unified across all frameworks, and an OpenTelemetry config field moves to where it belongs.
Features
-
StructuredLogPayloadowns the structlog→Sentry contract (PR #129). The rendered-log-line shape that the Sentry instrument used to sniff and re-parse inline now lives in one value object (lite_bootstrap.instruments.logging_factory.StructuredLogPayload), with its meta-key vocabulary exposed as the publicSTRUCTLOG_META_KEYS. This closes a silent-drift failure mode: previously, renaming or adding a structlog meta-key could quietly leak it into Sentry'scontexts.structlogor drop the enrichment, with no test to catch it. Sentry output is unchanged for existing setups; the enrichment is now pinned by a round-trip test. -
opentelemetry_excluded_urlsnow lives onOpenTelemetryConfig(PR #132). The field was OpenTelemetry's own setting but had been declared separately on each of the FastAPI, Litestar, and FastStream configs. It now lives onOpenTelemetryConfig, so it is available wherever OpenTelemetry is configured and is read with typed access internally. The automatic exclusion of the metrics path and (unless health-check spans are enabled) the health-check path from traces is unchanged and now covered by a regression test.
Behavior changes
- The double-attach teardown guard now applies to Litestar and FastStream (PR #130). Teardown-on-shutdown wiring is unified behind a single
BaseBootstrapper._attach_teardown_onceseam. The safeguard that warns and skips when a second bootstrapper is constructed against the same application — previously present only on FastAPI and FastMCP — now covers Litestar and FastStream as well. If your code constructs two bootstrappers against one LitestarAppConfigor FastStream app, you will now see aUserWarningand the second teardown is skipped, where this was previously silent. This is non-breaking: the warning is informational, the second attach is skipped (not errored), and the first bootstrapper's teardown still runs on shutdown. Construct one bootstrapper per application.
Backwards compatibility
Fully backward compatible with 1.1.1. No public API was removed.
IGNORED_STRUCTLOG_ATTRIBUTESis retained insentry_instrumentas a silent alias ofSTRUCTLOG_META_KEYS, so existing imports keep working.opentelemetry_excluded_urlsis still set exactly as before (e.g.FastAPIConfig(opentelemetry_excluded_urls=[...])) — it is now inherited rather than locally declared.FreeConfigadditionally accepts the field now (inert there, since Free has no HTTP surface).- The only observable difference is the new Litestar/FastStream double-attach warning described above.
Internal-only changes that do not affect the public API: FastMCP's double-attach detection moved from scanning the provider list to the shared attribute marker, and FastAPI's internal lifespan marker was renamed to the unified _lite_bootstrap_teardown_attached.
References
1.1.1
lite-bootstrap 1.1.1 — FastAPI 0.137 compatibility
1.1.1 is a patch release. No public-API or behavior changes. It restores compatibility with FastAPI 0.137 and pins a transitive incompatibility, nothing more.
Bug fixes
- Offline docs no longer crash on FastAPI 0.137's
_IncludedRouter(PR #122). FastAPI 0.137.0 added the internal_IncludedRouterroute type (aBaseRoutesubclass with no.path) toapp.router.routes.enable_offline_docsfiltered routes via an uncheckedtyping.cast(Route, route).path, which raisedAttributeError: '_IncludedRouter' object has no attribute 'path'whenever a router was included (e.g. by the health-checks instrument). The filter now matches only realRouteinstances (isinstance(route, Route) and route.path in …), leaving_IncludedRouterand other route types untouched. Correct on both old and new FastAPI.
Dependency constraints
fastapi<0.137cap on thefastapiextra (temporary).prometheus-fastapi-instrumentator(≤ 8.0.0) has the same unguardedroute.pathaccess and is not yet fixed upstream, so any lite-bootstrap install that pulls FastAPI 0.137 would break metrics. The cap lives on the basefastapiextra — the single declaration every otherfastapi-*extra composes from — so the whole FastAPI surface resolves to a version tested end-to-end with no skew between extras. Lift the cap once a fixed instrumentator ships.- Upstream issue: trallnag/prometheus-fastapi-instrumentator#370
Backwards compatibility
Fully backward compatible with 1.1.0. No public API changed; the FastAPI fix is purely defensive. The only observable difference is the new fastapi<0.137 resolution ceiling, which holds installs at a known-good FastAPI until the upstream metrics fix lands.
References
- PR: #122
- Upstream issue: trallnag/prometheus-fastapi-instrumentator#370
1.1.0
lite-bootstrap 1.1.0 — Lifecycle hardening, config validation, CI gate
1.1.0 is a minor release. No intentional public-API breakage. The two behavior changes that could affect existing code are fixes to genuine bugs and are called out in Behavior changes below.
This release closes a 26-finding bug-audit cycle (audits + retro live under planning/specs/2026-06-05-bug-audit-v2*.md). The changes split across four shipped PRs:
- #108 — Lifecycle & teardown correctness (10 findings)
- #109 — Config UX & security validation (6 findings)
- #110 — Hygiene + CI gate (4 findings)
- #111 — Generalized
TeardownErroraggregation + cascade tests + README lifecycle docs (3 deferred follow-ups)
Test suite grew from 153 → 194 (+27%) at 100% line coverage throughout. pip-audit now runs on every PR and weekly via cron; a new filterwarnings config catches accidental InstrumentSkippedWarning emissions.
New features
- Injectable
prometheus_collector_registryonFastStreamConfig. Pass an existingprometheus_client.CollectorRegistryto expose counters registered elsewhere through FastStream's/metricsendpoint. Defaults to a fresh per-instance registry — fully backward compatible. opentelemetry_excluded_urlsfield onFastStreamConfig. Was agetattrfallback before; now a discoverable, IDE-completable config field matchingFastAPIConfigandLitestarConfig.SentryInstrument.teardown(). Callssentry_sdk.flush(timeout=2)thensentry_sdk.init()(no args) to reset the SDK to a no-op state. Previously the SDK stayed globally configured after bootstrapper teardown, leaking state across process-local tests.FastStreamLoggingInstrument.teardown(). Restoresbroker.config.logger.params_storageto its pre-bootstrap value. The bootstrap mutated broker state; teardown didn't reverse it.
Bug fixes
Lifecycle & teardown (PR #108)
- OpenTelemetry teardown now flushes spans and shuts down the tracer provider (LOG-1, LOG-2).
bootstrap()stored theTracerProvideronly as a local;teardown()couldn't reach it to callshutdown(). Buffered spans inBatchSpanProcessorwere dropped on graceful shutdown. Teardown also restores the two OTel-namespace stdlib loggers (opentelemetry.instrumentation.instrumentor,opentelemetry.trace) to their pre-bootstrapdisabledstate. LoggingInstrument.teardown()runs all cleanup steps even on partial failure (LOG-3). A raise from anyhandler.close()previously left remaining handlers attached, skipped the root-level reset, and never calledclose_handlers()on the memory factory. Now wrapped intry/finallywith per-handler error capture; all collected errors raise together viaTeardownError(errors).LitestarOpenTelemetryInstrumentationMiddlewarecache evicts dead refs (LOG-6). The olddict[int, ASGIApp]keyed byid()never evicted, holding wrapperOpenTelemetryMiddlewareinstances alive after Litestar droppednext_app. Replaced withweakref.WeakKeyDictionary; non-weakrefable apps fall through to the un-cached path.- Double-bootstrap on the same application is now detected and warned (LOG-7, LOG-8). Constructing two
FastMcpBootstrappers around the sameFastMCP(or twoFastAPIBootstrappers around the sameFastAPI) previously stacked teardown hooks. The second construction now emits aUserWarning, skips the re-attachment, and tells you the second bootstrapper'steardown()won't fire on ASGI shutdown. - Generalized
TeardownErroraggregation across all instruments (PR #111).OpenTelemetryInstrument,FastStreamLoggingInstrument, andSentryInstrumentnow run their full cleanup sequence even if an early step raises — aggregating errors into a singleTeardownErroror lettingsuper().teardown()run viatry/finally. Previously a misbehaving instrumentor / broker / Sentry flush silently skipped subsequent cleanup. - Pyroscope's precondition check survives
python -O(LOG-5/SEC-4). Replaced twoassertstatements (in_narrow_appandPyroscopeInstrument.bootstrap) with explicitraise TypeError(...)andraise RuntimeError(...).python -Ostrips asserts; the invariants now hold under all optimization levels. Closes the twobanditB101 findings.
Config & security (PR #109)
FastAPIConfigno longer stomps user-supplied app'stitle/debug/version(UX-1). Previously the three assignments ran unconditionally; a user passingFastAPI(title="My API", version="3.0.0")would silently have those clobbered by lite-bootstrap defaults. Now the assignments only run in theUnsetTypebranch (when lite-bootstrap constructed the app). See Behavior changes below.enable_offline_docsvalidatesrequest.scope["root_path"]against the existing path allowlist (SEC-1). Invalid root paths (e.g., HTML-injection payloads via a malicious upstream proxy'sX-Forwarded-Prefix) now fall back to empty and emit a warning instead of being reflected into Swagger/Redoc HTML script tags. Threat model: not an issue in default ASGI deployments; only matters ifProxyHeadersMiddlewaretrusts upstream prefix headers.- OpenTelemetry endpoint with
insecure=Trueemits a warning for non-local hosts (SEC-2). NewOpenTelemetryConfig.__post_init__parses the endpoint (handles bothhost:portandscheme://host:portforms, including IPv6 brackets andunix://) and warns when traces would ship unencrypted to a non-localhost/127.0.0.1/::1/unix://target. CorsConfigrejects unsafe wildcard + credentials combos at construction (SEC-3).cors_allowed_credentials=Truecombined withcors_allowed_origins=["*"](or a permissive regex like".*"/".+") is the canonical CORS misconfiguration — browsers reject the response. Now raisesConfigurationErrorimmediately instead of silently building a non-functional CORS layer. See Behavior changes below.
Hygiene & process (PR #110)
- Missing-dependency events now log via stdlib
loggingin addition towarnings.warn(UX-4). Users running underpython -W ignoreorPYTHONWARNINGS=ignorepreviously saw nothing when a configured instrument's optional dep was missing. The newlogger.warningline onlite_bootstrap.bootstrappers.baseis unaffected by warning filters.
Behavior changes
Two changes could affect existing code in ways the previous release wouldn't have. Both fix real bugs; surfaced here so you can audit.
-
User-supplied
FastAPIinstance retains its owntitle,debug,version. If you were relying on lite-bootstrap to overwrite these fromservice_name/service_debug/service_versionafter handing it a pre-builtFastAPI(), you'll now see your original values. Migration: set these on yourFastAPI()directly, or useFastAPIConfig(application_kwargs={...})to have lite-bootstrap construct the app. -
CorsConfig(cors_allowed_origins=["*"], cors_allowed_credentials=True)now raisesConfigurationError. This combo was never functional — browsers reject responses withAccess-Control-Allow-Credentials: trueandAccess-Control-Allow-Origin: *. If your code constructed this combo and worked anyway (because the credentials header was silently dropped by FastAPI's CORSMiddleware), construction now fails with a clear message. Migration: enumerate allowed origins explicitly, or setcors_allowed_credentials=False.
New constraints documented
Three lifecycle constraints surfaced by the audit are now documented in README.md and CLAUDE.md:
- One bootstrapper per application instance. Second construction emits a warning and skips re-attachment (see LOG-7/LOG-8 above).
- One
OpenTelemetryInstrumentper process. The OTel SDK enforcesset_tracer_provideras set-once via_TRACER_PROVIDER_SET_ONCE.do_once(...)(verified againstopentelemetry/trace/__init__.py:548-556);teardown()cannot reset the global pointer. __post_init__cascade invariant. Every config-class__post_init__must callsuper().__post_init__().BaseConfigships a no-op as the chain terminator.FastAPIConfiguses the explicitsuper(FastAPIConfig, self).__post_init__()form because@dataclass(slots=True)breaks baresuper().
CI changes
security-audit.ymlworkflow added.pip-auditruns on every PR and weekly via cron against the lockfile (uv export --all-extras --no-hashes). The default-branch run is purely informational; PRs that introduce CVEs will block until resolved.InstrumentSkippedWarningescalated to error in tests. Any unexpected emission outside apytest.warns(...)block now fails the test (registered viapytest_configure()intests/conftest.py; can't live inpyproject.tomlbecause that import order breaks pytest-cov tracing).
Backwards compatibility
Aside from the two Behavior changes called out above, every public API behaves identically. New fields default to their old behavior (prometheus_collector_registry=None → fresh registry as before; opentelemetry_excluded_urls=[] → empty set as before). New warnings/validators trigger only on configurations that were already broken or risky.
The 26-fix list with full file:line references and rationale is in:
planning/specs/2026-06-05-bug-audit-v2.md— the auditplanning/specs/2026-06-05-bug-audit-v2-sequencing.md— the 3-PR breakdownplanning/specs/2026-06-05-bug-audit-v2-retro.md— what the cycle taught us
References
1.0.0
What's Changed
- fix: honor root_path in offline-docs redoc handler by @lesnik512 in #89
- fix: shut down TracerProvider in OpenTelemetryInstrument.teardown by @lesnik512 in #90
- fix: make teardown idempotent and exception-safe by @lesnik512 in #91
- fix: strip skip_sentry from Sentry context; drop dead is_X_installed conjuncts by @lesnik512 in #92
- docs: document and pin BaseConfig.from_dict / from_object semantics by @lesnik512 in #93
- refactor: extract OpenTelemetryServiceFieldsConfig mixin by @lesnik512 in #94
- refactor: make BaseInstrument generic; delete pure-annotation subclasses by @lesnik512 in #95
- fix: tighten Sentry idiom and typing micro-issues by @lesnik512 in #96
- refactor: OTel instrument touch-ups (REF-1, LOW-3, LOW-5) by @lesnik512 in #97
- test: add standalone instrument tests and is_valid_path negative tests by @lesnik512 in #98
- refactor: split logging module + cleanup + lifecycle test by @lesnik512 in #99
- refactor: drop unused abc.ABC from BaseInstrument; document config holders by @lesnik512 in #100
- refactor: drop frozen=True from instruments; sentinel for FastAPIConfig.application by @lesnik512 in #101
- feat: configurable broker ping timeout for FastStream health check by @lesnik512 in #102
- refactor: rename OpentelemetryConfig → OpenTelemetryConfig; FreeBootstrapperConfig → FreeConfig by @lesnik512 in #103
- chore: pin uv_build upper bound; assert pyroscope_endpoint precondition by @lesnik512 in #104
- feat: add FastMcpBootstrapper by @lesnik512 in #105
- fix: make BaseBootstrapper.bootstrap() idempotent by @lesnik512 in #106
- refactor: replace InstrumentNotReadyWarning with is_configured classmethod + summary log by @lesnik512 in #107
Full Changelog: 0.28.1...1.0.0
0.28.1
What's Changed
- fix: prevent FastStream bootstrap NameError when optional extras are missing by @lesnik512 in #88
Full Changelog: 0.28.0...0.28.1
0.28.0
What's Changed
- fix: cache OpenTelemetryMiddleware in litestar instrumentation by @lesnik512 in #80
- Fix/low severity cleanups by @lesnik512 in #81
- fix: preserve teardown error detail via TeardownError exception by @lesnik512 in #82
- perf: hoist OTel import in tracer_injection to module level by @lesnik512 in #83
- refactor: declare opentelemetry_service_name/namespace on PyroscopeCo… by @lesnik512 in #84
- feat: add logging_enabled flag, decouple from service_debug by @lesnik512 in #85
- feat: unify instrument skip feedback through warning subclasses by @lesnik512 in #86
Full Changelog: 0.27.1...0.28.0
0.27.1
What's Changed
- feat: make TimeStamper configurable in LoggingConfig by @lesnik512 in #79
Full Changelog: 0.27.0...0.27.1
0.27.0
What's Changed
- feat: integrate litestar StructlogPlugin for request.logger support by @lesnik512 in #78
Full Changelog: 0.26.1...0.27.0
0.26.1
What's Changed
- fix: set faststream log level on logger before injecting into broker by @lesnik512 in #77
Full Changelog: 0.26.0...0.26.1