Releases: dynamic-config-rs/dynamic-config-python-web
Release list
v0.2.1
Changed
dynamic-config-py>=0.3.1is the floor: the base wheel's
failure events arrive natively there, andstream_eventsrides
them without a poll. Thefailure_pollparameter is accepted and
warns, exactly as the base wheel treats it.
Changed
stream_eventsno longer polls. A refused reload wakes the
stream natively (dynamic-config-py 0.3.1's failure hook), so
ReloadFailedevents arrive when the refusal happens and nothing
runs on a timer. Requiresdynamic-config-py>=0.3.1.
Deprecated
stream_events(failure_poll=...)is accepted, ignored, and
warns: the interval refusals were polled at, now that they wake the
stream themselves. Remove the argument.
v0.2.0
Changed
-
The route table is written once. Six adapters — FastAPI, Litestar,
Flask, Quart, Robyn and django-bolt — had each re-declared/healthz,
/readyz,/metricsand the two guarded diagnostics routes with the
same bodies; they now loop over one shared table and translate only
what is genuinely theirs: the path syntax, the response type, and the
refusal convention (the WSGI-shaped adapters keep 404, DRF keeps 403,
Django Ninja keeps 401). The raw-ASGI scope middleware FastAPI and
Litestar carried twice is one module now. No public signature moved;
the conformance suite is the proof, thirteen cases against every
adapter, unchanged before and after.The Django family stays off the table on purpose: its views late-bind
the installation per request and are individually routable public API.
They share the same_health/_metrics/_diagnosticsbodies one
level down.
Fixed
-
A scope over several configurations can no longer tear across a
reload. Each configuration has its own atomic cell and the engine
keeps no epoch across them, so opening a scope was N independent reads —
and a reload landing between two of them put two generations in one
request.enter()now reads every install counter before and after,
and starts over when anything moved, with the same retry budget as the
Rust web core'sSections::take. One configuration pays nothing.The conformance suite gained the case that would have caught it: a
wiring over two configurations, read, moved underneath the request, and
read again — thirteen cases now, asked of all nine adapters.
Added
- A standalone DRF example.
examples/09_django_drf.py— the health
surface as APIViews and the diagnostics behind
ConfigDiagnosticsPermission, beside the django-ninja example instead
of folded into the Django one.
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.