Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Stability & Production Use

Beta, and the surface is finished for 0.x.

dynamic-config-py and dynamic-config-py-remote are Beta, like every crate and package in this repository. Between here and 1.0, only security fixes and hotfixes land: no new sources, no new schema kinds, no new methods on the settled types. What still ships is a defect that produces a wrong answer, a security advisory, and documentation — each as a patch.

That is a change of intent rather than of policy, and it is worth saying plainly because the two look identical from outside: a project that publishes weekly because it is growing and one that publishes rarely because it is finished are both quiet. This is the second.

What that means for your program

Pin the minor version and take patches automatically.

dynamic-config-py ~= 0.1.2

A patch will not break you. Pre-1.0 a break bumps the minor, is called out in the changelog, and comes with what to change on your side — and there is no plan to spend one before 1.0.

The two wheels version together. dynamic-config-py[remote] resolves to a pair; a gap between them is a combination nobody has tested, which is why CI asserts their versions agree and one command moves both.

The engine's version is a separate number. dynamic_config.__version__ is this package's; __engine_version__ is the Rust crate it was built against. They move on two schedules, because a Rust-only release has nothing in it for a Python user.

Python versions

LineStatusTested in CINotes
3.9supported — the floor✅ every commitThe requires-python floor. X | None is a syntax error here, which is why a test parses every file at this level
3.10supported✅ every commit
3.11supported✅ every commitasyncio.timeout exists from here; the binding does not depend on it
3.12supported✅ every commit
3.13supported✅ every commit
3.14supported✅ every commit
3.14tsupported✅ every commitFree-threaded, its own cp314t wheel — the concurrency suite ten times over
3.8 and oldernot supportedEnd of life; the wheel's requires-python refuses to install

One abi3 wheel per platform covers 3.9 upwards, which is why the table is short and why a new CPython does not need a new wheel. The matrix is what makes the claim true rather than plausible: the Python half is ordinary code, and a version can break it — asyncio.timeout arrived in 3.11, dataclass slots in 3.10, and the typing syntax the stubs use has moved twice.

Raising the floor is a breaking change, treated exactly as an API break: it bumps the minor and is called out in the changelog. It will not happen before 1.0.

Platformx86-64aarch64
Linux (manylinux 2_28)
macOS
Windows

What is tested, and where you can see it

The claim behind Beta is evidence rather than time:

The suite344 tests, on CPython 3.9 through 3.14 — every version the wheel claims, not just the newest
Free-threaded CPythona cp314t wheel, with the concurrency suite run ten times over on a real no-GIL build
The base installa job with the wheel and nothing else, proving pip install dynamic-config-py pulls in no schema library
Every exampleall twenty-two run in CI; an example that only imports is not an example
The engine underneaththe Rust crate's own suite, its property tests, loom and shuttle models for the reload path, and instruction-count gates
The storeseach against a real server in a container, and three of them unplugged mid-watch by a proxy

What running this in production actually asks of you

Decide what a failed reload should do. The default is the right one for most services — the previous configuration keeps serving and the failure is recorded — but recorded means somebody has to look. Wire status() into a health endpoint, or config.on_reload into whatever you alert on. The telemetry chapter has the two numbers that matter: consecutive_failures, and how old the serving document is.

Give the last-known-good cache a path that survives a restart. A redacted cache means a broken source at startup is a warning rather than an outage — and it refuses to write at all unless the configuration has said what is secret, which is the point.

Watch the watcher. A file watcher is not a promise that a file will be watched: a container bind mount and some network filesystems deliver no events, and poll_interval is the answer there rather than a mystery.

Nothing here needs a sidecar, an agent or a server. The engine is in your process, the reads are lock-free, and the only thing that leaves is what a remote store you configured goes to fetch.