diff --git a/docs/adr/0001-caller-owns-the-root-container-lifecycle.md b/docs/adr/0001-caller-owns-the-root-container-lifecycle.md index ac5733c..dd09390 100644 --- a/docs/adr/0001-caller-owns-the-root-container-lifecycle.md +++ b/docs/adr/0001-caller-owns-the-root-container-lifecycle.md @@ -1,27 +1,13 @@ # The caller owns the root container's lifecycle -**Decision:** `setup_di` registers providers and hooks on the root container it is handed; it does -not open, validate, or close it. Constructing, validating, and closing the root container stay the -caller's, and we will not add an integration-owned lifecycle for it. - -The recurring proposal is for `setup_di` to take that over the way the ASGI integrations do — -theirs wrap the root container in the framework's lifespan, entering it on startup and closing it -on shutdown, so a user never writes a teardown line. - -Flask (WSGI) has no application startup or shutdown hook to attach the closing half to. -`before_request` and `teardown_appcontext` are per-request-ish, not per-process; in particular -`teardown_appcontext` fires on every app-context pop, including ones no request created, so it -cannot stand in for shutdown. An integration that took ownership here could therefore own only the -opening half: it would acquire `Scope.APP` resources it has no hook to finalize, and would hide -from the caller that finalization never happens — worse than the current split, which at least -tells the truth. Dishka's Flask integration reaches the same conclusion: it closes the per-request -child only, never the root. - -The price is two lines the caller writes: `fetch_di_container(app).close_sync()` at whatever -process-shutdown point the application actually has, and — since modern-di 3.1 validates nowhere -implicitly — `container.validate()` if boot-time fail-fast is wanted, after `setup_di` rather than -before it, because `setup_di` is what registers `flask_request_provider`. - -**Revisit trigger:** Flask grows an application-lifecycle hook — a startup/shutdown signal, or an -ASGI-style lifespan on the app object — that `setup_di` could attach a close to. Ownership becomes -symmetric at that moment and this should be reopened. +`setup_di` registers providers and hooks on the root container it is handed and never opens, +validates, or closes it. The ASGI integrations do take that over by wrapping the root container in +the framework's lifespan, but Flask (WSGI) has no application startup or shutdown hook to attach +the closing half to: `teardown_appcontext` fires on every app-context pop, including ones no +request created, so it cannot stand in for shutdown. An integration owning only the opening half +would acquire `Scope.APP` resources it has no hook to finalize and hide from the caller that +finalization never happens, which is worse than an honest split; Dishka's Flask integration closes +the per-request child only. The price is two caller-written lines: +`fetch_di_container(app).close_sync()` at the application's own shutdown point, and, since +modern-di validates nowhere implicitly, `container.validate()` after `setup_di`, which is what +registers `flask_request_provider`. Flask growing an application lifecycle hook would reopen this. diff --git a/docs/adr/0002-inject-does-not-rewrite-the-view-signature.md b/docs/adr/0002-inject-does-not-rewrite-the-view-signature.md index 010e393..b6716ed 100644 --- a/docs/adr/0002-inject-does-not-rewrite-the-view-signature.md +++ b/docs/adr/0002-inject-does-not-rewrite-the-view-signature.md @@ -1,24 +1,13 @@ # `inject` does not rewrite the view's signature -**Decision:** the wrapper `inject` returns keeps `*args, **kwargs` and `functools.wraps`; it does -not build a `__signature__` with the `FromDI`-annotated parameters stripped out. - -The sibling adapters `modern-di-celery` and `modern-di-aiogram` do exactly that rewrite, so the -obvious move when porting a fix between integrations is to bring it here too. They need it because -their frameworks read the callable's signature to decide what to pass, and would otherwise try to -supply — or reject the call over — a parameter DI owns. - -Flask never reads it. Its URL dispatcher calls `app.view_functions[endpoint](**url_args)` with the -arguments taken from the matched URL rule alone. A rewrite would change nothing about dispatch, -while adding a synthetic signature that has to be kept in step with the wrapped function and taking -the real one away from anything that introspects views. - -What `functools.wraps` *is* load-bearing for is `__name__`, which Flask turns into the endpoint -name at `@app.route` time — an invariant, recorded as -`test_inject_preserves_the_view_name_flask_derives_endpoints_from` in `tests/test_inject.py`, not -as prose here. - -**Revisit trigger:** something in the dispatch path starts reading view signatures — Flask itself -deriving arguments from them, or an extension within the supported `flask>=3,<4` range that wraps -`view_functions` and inspects what it wrapped. The injected parameters then become visible to a -caller that will try to fill them, and the rewrite earns its keep here too. +The wrapper `inject` returns keeps `*args, **kwargs` and `functools.wraps`; it does not build a +`__signature__` with the `FromDI`-annotated parameters stripped out. The sibling adapters +`modern-di-celery` and `modern-di-aiogram` do exactly that rewrite, so porting it here looks like +an obvious fix, but they need it because their frameworks read the callable's signature to decide +what to pass and would otherwise try to supply, or reject the call over, a parameter DI owns. +Flask never reads it: its URL dispatcher calls `app.view_functions[endpoint](**url_args)` with the +arguments taken from the matched URL rule alone, so a rewrite would change nothing about dispatch +while adding a synthetic signature to keep in step with the wrapped function and hiding the real +one from anything that introspects views. `functools.wraps` stays load-bearing for `__name__`, +which Flask turns into the endpoint name at `@app.route` time. Only something in the dispatch path +within the supported `flask>=3,<4` range reading view signatures would earn the rewrite.