Metadata-Version: 2.5
Name: dynamic-config-py-web
Version: 0.2.0
Summary: Native web-framework integrations for dynamic-config-py: FastAPI, Litestar, Flask, Quart, Robyn, Django and django-bolt
Project-URL: Homepage, https://dynamic-config-rs.github.io/web/
Project-URL: Documentation, https://dynamic-config-rs.github.io/web/reference.html
Project-URL: Repository, https://github.com/dynamic-config-rs/dynamic-config-python-web
Project-URL: Changelog, https://github.com/dynamic-config-rs/dynamic-config-python-web/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/dynamic-config-rs/dynamic-config-python-web/issues
Author-email: Cevat Batuhan Tolon <cevatbatuhan.tolon@gmail.com>
License: MIT
License-File: LICENSE
Keywords: asgi,config,configuration,django,django-ninja,django-rest-framework,fastapi,flask,healthcheck,hot-reload,litestar,live-reload,prometheus,quart,robyn,twelve-factor,wsgi
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: AsyncIO
Classifier: Framework :: Django
Classifier: Framework :: FastAPI
Classifier: Framework :: Flask
Classifier: Framework :: Pytest
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Middleware
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: System :: Systems Administration
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: dynamic-config-py>=0.2
Provides-Extra: all
Requires-Dist: django-ninja>=1.0; extra == 'all'
Requires-Dist: django>=4.2; extra == 'all'
Requires-Dist: djangorestframework>=3.15; extra == 'all'
Requires-Dist: fastapi>=0.100; extra == 'all'
Requires-Dist: flask>=2.2; extra == 'all'
Requires-Dist: litestar<4,>=2.7; extra == 'all'
Requires-Dist: quart>=0.19; extra == 'all'
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: httpx>=0.24; extra == 'dev'
Requires-Dist: mypy>=1.11; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
Requires-Dist: pytest>=7; extra == 'dev'
Requires-Dist: ruff>=0.6; extra == 'dev'
Provides-Extra: django
Requires-Dist: django>=4.2; extra == 'django'
Provides-Extra: django-bolt
Requires-Dist: django-bolt<1,>=0.10; (python_version >= '3.12') and extra == 'django-bolt'
Requires-Dist: django>=4.2; extra == 'django-bolt'
Provides-Extra: drf
Requires-Dist: django>=4.2; extra == 'drf'
Requires-Dist: djangorestframework>=3.15; extra == 'drf'
Provides-Extra: fastapi
Requires-Dist: fastapi>=0.100; extra == 'fastapi'
Provides-Extra: flask
Requires-Dist: flask>=2.2; extra == 'flask'
Provides-Extra: litestar
Requires-Dist: litestar<4,>=2.7; extra == 'litestar'
Provides-Extra: ninja
Requires-Dist: django-ninja>=1.0; extra == 'ninja'
Requires-Dist: django>=4.2; extra == 'ninja'
Provides-Extra: quart
Requires-Dist: quart>=0.19; extra == 'quart'
Provides-Extra: robyn
Requires-Dist: robyn>=0.88; (python_version >= '3.10') and extra == 'robyn'
Provides-Extra: test
Requires-Dist: httpx>=0.24; extra == 'test'
Requires-Dist: pytest-asyncio>=0.23; extra == 'test'
Requires-Dist: pytest>=7; extra == 'test'
Description-Content-Type: text/markdown

<div align="center">

# dynamic-config-py-web

**Native web-framework integrations for [`dynamic-config-py`](https://github.com/dynamic-config-rs/dynamic-config-python): the wiring, the request scope, the health surface and the test doors — already written.**

[**The Book**](https://dynamic-config-rs.github.io/web/) · [The engine](https://github.com/dynamic-config-rs/dynamic-config) · [The Python binding](https://github.com/dynamic-config-rs/dynamic-config-python)

</div>

---

```sh
pip install "dynamic-config-py[fastapi]"      # or [litestar] [flask] [quart]
pip install "dynamic-config-py[django]"       # …[drf] and [ninja] for its API layers
pip install "dynamic-config-py[robyn]"        # Experimental
pip install "dynamic-config-py[django-bolt]"  # Experimental, Python 3.12+
```

```python
from fastapi import Depends, FastAPI
from dynamic_config import DynamicConfig
from dynamic_config_web.fastapi import config_dependency, setup

config = DynamicConfig(Database, key="db").file("config.toml").env("APP_")
app = FastAPI()

setup(app, config)
database = config_dependency(config)


@app.get("/")
def index(db: Database = Depends(database)):
    return {"host": db.host, "pool": db.pool_size}
```

That is the whole integration. `setup` loads before the first request,
watches while serving and stops on the way out; `/healthz`, `/readyz` and
`/metrics` are mounted; and every handler reads the configuration the
request began with.

## What it does that a dependency function does not

**One reading per request, enforced.** The engine's own book states the
rule — *read `current()` once per request and use that value for the whole
request* — because a reload landing mid-request would otherwise show one
request two configurations. Here it is a `contextvars` scope opened by the
adapter: a second read is the same object, and a read outside a request
raises instead of quietly answering with whatever is installed at that
instant.

**One watcher, however many times the app is built.** A second `watch()`
on one configuration is `AlreadyExists`. `uvicorn --reload` rebuilds the
app on every edit and a test suite builds one per client, so the watcher
is leased and counted: the first holder starts it and the last one stops
it.

**A watcher in every worker.** A watcher is a thread, and a thread does
not survive `fork()` — but the engine's registration does, so a
`gunicorn --preload` worker would be refused a new one while nothing was
watching. Every lease re-arms itself in the child.

**Liveness and readiness as different questions.** `/healthz` never fails
on configuration: a process that cannot reload should stop receiving
traffic, not be restarted into reading the same broken file. `/readyz`
is where *nothing installed*, *reloads failing* and *too stale* answer 503.

**Diagnostics only when you say so.** `/_config/explain` and
`/_config/check` are not mounted at all without a guard — not
mounted-and-403, which would tell a scanner they exist.

## The seven

| Framework | Extra | Seam | Tier |
|---|---|---|---|
| FastAPI | `[fastapi]` | `lifespan`, `Depends`, `APIRouter`, ASGI middleware | Beta |
| Litestar | `[litestar]` | `InitPlugin.on_app_init` — lifespan, `Provide`, routes, middleware | Beta |
| Flask | `[flask]` | extension in `app.extensions`, `Blueprint`, `before_request` | Beta |
| Quart | `[quart]` | the async twin, on `while_serving` | Beta |
| Django | `[django]` | `AppConfig.ready()`, middleware, `urls`, a management command | Beta |
| Django REST Framework | `[drf]` | `APIView` + a permission class | Beta |
| django-ninja | `[ninja]` | a `Router`, and the operation `auth=` | Beta |
| Robyn | `[robyn]` | startup/shutdown events, `@scoped` handlers | **Experimental** |
| django-bolt | `[django-bolt]` | `BoltAPI(lifespan=…, middleware=…)`, `Router` | **Experimental** |

Each has a page or a section in
[the book](https://dynamic-config-rs.github.io/web/) and a runnable example in
[`examples/`](https://github.com/dynamic-config-rs/dynamic-config-python-web/tree/main/examples);
all nine answer the same twelve conformance cases, and where one cannot —
Robyn's request scope — the suite says so by name rather than skipping
quietly.

## What this package is not

It is **not** a second configuration API. Every value still comes from
`dynamic-config-py`, every diagnostic is the engine's, and the adapters
add no cache, no copy and no second source of truth — `db.current()` is
still the read path, and `current()` here is that read taken once per
request.

## License

MIT.
