v0.1.0
Added
-
The shared core.
Wiring(load, watch, stop — idempotent, leased per
configuration and re-armed afterfork()), the request scope
(scope/current/get/latest, with a read outside a request raising
rather than answering),liveness/readinessas the two different
questions they are,metrics_bodyover a configuration or a whole
ConfigGroup,explain/checkwith async twins and aGuard,
log_reloads/stream_events, and thepinned/as_requesttest doors. -
The FastAPI adapter.
setup(app, config_or_group_or_wiring)wraps the
application's lifespan, installs a pure-ASGI request-scope middleware, and
mounts/healthz,/readyzand/metrics— plus/_config/explainand
/_config/checkwhen, and only when, a guard is given.
config_dependency(config)is theDependsprovider, and it answers the
same object every time so a test can override it by name. -
The Litestar adapter.
DynamicConfigPluginadds a lifespan, a
Provideper configuration, the health routes and the scope middleware
throughInitPlugin.on_app_init. The provider is builtuse_cache=False
deliberately: Litestar's cache is per application, so a cached one would
pin a single snapshot for the life of the process.NamedDependencyis
re-exported, so a handler written against it neither warns on Litestar
2.23+ nor breaks on anything older. -
The Flask adapter.
DynamicConfigExtensioninapp.extensions, a
blueprint of health routes,before_request/teardown_appcontextfor the
scope, andsnapshot()as the read — with nothing written into
app.config, because a copy never reloads. WSGI has no lifespan, so the
watcher arms on the first request each process serves, which is after a
fork by construction;start="eager"andstart="manual"are there for
deployments that know better. -
The Quart adapter. The Flask extension's API over Quart's
while_serving, which is the moment WSGI cannot offer: the watcher starts
after the workers exist and before the first request, so a worker that
cannot load its configuration fails to start rather than serving 503s. -
The Django adapter. An installed app whose
ready()reads
DYNAMIC_CONFIG— a pointer, never a value, because Django caches
settings for the life of the process — a sync-and-async middleware that
opens the scope and attachesrequest.dynamic_config, a URLconf to
include, andmanage.py configcheck(with--explainand--strict).
Two rules decide what each process does:should_watch()keeps a watcher
out ofrunserver's autoreloader parent and out of management commands,
andserving_process()makes a broken document fail a worker's startup
while letting a command run — becauseconfigcheckis the tool you reach
for when configuration is broken. -
The Django REST Framework layer. The same surface as
APIViews, with
ConfigDiagnosticsPermissiondeferring to the installation's guard so a
project can put diagnostics inside its own permission scheme. Readiness
and metrics stay open; a refused diagnostics request gets DRF's 403 rather
than the plain views' 404, and with no guard neither set is mounted. -
The django-ninja adapter.
router()mounts the health, metrics and
diagnostics operations on aNinjaAPI. Nothing else was needed: django-ninja
is a Django application, soAppConfig.readyalready loads and watches and
the middleware already opens the request scope. Every operation registers
withauth=Noneexcept the two diagnostics ones, so a project that sets an
API-wideauth=does not end up with a liveness probe that reports its
authentication backend's health.A refused diagnostics request gets 401 here, against the plain views'
404 and DRF's 403 — each adapter keeps its framework's convention, and the
conformance suite accepts all three. -
The Robyn adapter (Experimental).
setup(app, config)registers the
startup and shutdown handlers and aSubRouterof health routes. The
request scope is a@scopeddecorator rather than middleware, and that is
a finding rather than a shortcut: aContextVarset in Robyn's
before-request middleware is not visible in the handler, so a scope opened
there would silently not be there. A handler that forgets the decorator
raises instead of reading unscoped. -
The django-bolt adapter (Experimental).
api(config, …)builds a
BoltAPIwith the lifespan, the scope middleware and the health routes
already on it, passing every other keyword through — a factory, because
django-bolt takes both seams as constructor arguments and reaching past
them into private attributes is not a thing to build on a 0.10 library.
lifespan(),ScopeMiddlewareandrouter()are public for an
application that must build its own. -
check()answers whether the document would load, not only whether
its keys resolve. The engine's owncheck()merges the layers and
compares the field names; it does not build the model, so a document whose
portis the string"8080"passed it and then refused to load. The
report now carriesloads,cleanmeans both, andfailurecarries the
validation error when that is what went wrong — which is what
/_config/checkandmanage.py configcheckreport. -
A pytest plugin, on its own
pytest11entry point:
dynamic_config_wiring,dynamic_config_pinned,dynamic_config_request
anddynamic_config_watchers. Nothing is autouse, and the module imports
no framework. -
A conformance suite. Twelve behavioural cases every adapter must pass,
written once and parametrised by a per-framework driver: a request never
tears across a reload, the watcher is paired with the app, building the app
twice does not collide,/healthzstays 200 while/readyzgoes 503, no
value reaches a metrics body, the diagnostics routes are absent without a
guard.All nine drivers run it — FastAPI, Litestar, Flask, Quart, Django, DRF,
django-ninja, Robyn and django-bolt — and the one case Robyn cannot answer
is skipped by name (scope_is_automatic = False) rather than quietly, so
the suite records which framework cannot do what.