Flask integration¶
RequestScope ¶
Bases: RequestScope[WSGIEnvironment, StartResponse]
WSGI middleware that opens a depin scope around every Flask request.
Implemented directly against the WSGI protocol, so it wraps the whole
application — including the error handling and the teardown callbacks
Flask runs inside wsgi_app.
The container is published to the request's context for the duration of
the scope, so depin.hosted_container() reaches it from anywhere inside
the request. It places a flask.Request built from the environment into
the active scope frame, so scoped providers can read headers, URL, and
cookies without touching Flask's own context locals.
That request must not be used to read the body — form, json,
data, files. It is built from the same environment Flask builds its
own request from, and the two share one environ['wsgi.input'] stream.
Reading the body through the seed consumes that stream, and Flask's own
parse then finds it empty and returns 400. This is the one seed that can
take the body from the handler: depin.ext.starlette and
depin.ext.litestar seed a request with no receive channel, so neither
reaches the stream the route handler reads — the Starlette seed raises on
every body read, and the Litestar one either raises or replays the body
Litestar has already parsed. Treat the body as a route concern, not a
provider input.
Unlike the ASGI integrations, which hand the framework the class and let it
construct the middleware, Flask is given an instance: app.wsgi_app is
the application, and wrapping it is an assignment. The wrapping is
therefore explicit about order — install it once, after any other
wsgi_app wrapper whose work should happen inside the depin scope.
Flask declares wsgi_app as a method, so mypy reports method-assign
on the assignment below — for this middleware exactly as it does for every
other WSGI middleware, ProxyFix included. Waive it at that one line, or
keep the wrapper as the object the server is pointed at
(application = RequestScope(app.wsgi_app, di)) and leave the Flask
instance alone.
The scope ends when the application returns, not when the response is
finished, which is a limit of WSGI rather than of Flask: a streaming
response's body is pulled by the server after RequestScope has drained,
so nothing inside a streaming generator can resolve. Resolve what the
generator needs before returning the response and close over the values.
depin.ext.wsgi.RequestScope states the whole trade-off.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
app
|
WSGIApplication
|
The downstream WSGI application this middleware wraps — normally
|
required |
container
|
FrozenContainer
|
The frozen container to host for the duration of each request. |
required |
Raises:
| Type | Description |
|---|---|
TeardownError
|
An async provider left a teardown in the request's synchronous scope. A WSGI application cannot await, so an async provider has no place in one. |
ExceptionGroup
|
One or more teardowns failed when the request's scope closed. Every failure is included; one does not hide another. |
Example
Install the middleware once, by pointing the server at the wrapper::
app = Flask(__name__)
application = RequestScope(app.wsgi_app, di)
Or rebind the application in place, which is Flask's own idiom and
needs the method-assign waiver described above on that one line::
app.wsgi_app = RequestScope(app.wsgi_app, di)
seed_request ¶
seed_request(environ: WSGIEnvironment) -> ScopeSeed
Build the flask.Request that RequestScope places into each request frame.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
environ
|
WSGIEnvironment
|
The WSGI environment of the request being opened. |
required |
Returns:
| Type | Description |
|---|---|
ScopeSeed
|
The key to bind the request under, and the request itself. |
Example
from wsgiref.util import setup_testing_defaults environ = {'PATH_INFO': '/orders', 'HTTP_X_TENANT': 'acme'} setup_testing_defaults(environ) seed = seed_request(environ) seed.key is Request True seed.value.headers['x-tenant'] 'acme'