From fe0ceccc71b0f67acaf5ba4e8df4f95b4cb744a9 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Sat, 19 Sep 2026 14:10:17 +0300 Subject: [PATCH] docs(adr): compress to 2 records in the domain-modeling format Drop 0002 (per-update context stays a literal): the context dict is internal and swapping it for two `bind` calls costs nothing, so the record fails the hard-to-reverse and real-trade-off halves of the admission test. Renumber 0003 (no auto_inject for dialogs) to 0002, keeping the original relative order. No merges. Both survivors become one paragraph each, and the `_inject_router` description is corrected to what the code does now. --- .../adr/0001-dialog-support-is-a-submodule.md | 34 ++++++------------- docs/adr/0002-no-auto-inject-for-dialogs.md | 13 +++++++ ...0002-per-update-context-stays-a-literal.md | 22 ------------ docs/adr/0003-no-auto-inject-for-dialogs.md | 23 ------------- docs/agents/domain.md | 2 +- 5 files changed, 25 insertions(+), 69 deletions(-) create mode 100644 docs/adr/0002-no-auto-inject-for-dialogs.md delete mode 100644 docs/adr/0002-per-update-context-stays-a-literal.md delete mode 100644 docs/adr/0003-no-auto-inject-for-dialogs.md diff --git a/docs/adr/0001-dialog-support-is-a-submodule.md b/docs/adr/0001-dialog-support-is-a-submodule.md index 1189e49..672874e 100644 --- a/docs/adr/0001-dialog-support-is-a-submodule.md +++ b/docs/adr/0001-dialog-support-is-a-submodule.md @@ -1,25 +1,13 @@ # aiogram-dialog support is a submodule, not a separate package -**Decision:** DI for [aiogram-dialog](https://github.com/Tishka17/aiogram_dialog) getters and -callbacks ships as `modern_di_aiogram.dialog` inside this package; we will not publish a separate -`modern-di-aiogram-dialog` distribution, and aiogram-dialog stays a test dependency only. - -The alternative — its own repository and PyPI package, matching how every other framework -integration in `modern-python` is packaged — was rejected because aiogram-dialog is not another -entrypoint. It runs *inside* aiogram's dispatch, so the container lifecycle it needs already -exists: `setup_di`'s middleware has built the per-update child container and put it in the -per-update `data`, which is the same dict aiogram-dialog hands getters as `**kwargs` and exposes on -`DialogManager.middleware_data`. What is left to write is a dialog-aware `inject` that locates that -container by call shape. A separate package for that is disproportionate, and it would have to -re-export this package's marker and private child-container key across a distribution boundary, -turning both into public API for the sake of the split. - -Keeping it here is what makes the runtime dependency avoidable: the lookup is structural -(duck-typed on `.middleware_data`), `dialog.py` imports nothing from `aiogram_dialog`, and a user -who never touches dialogs pays nothing for the module existing. A separate package would have had -to declare the dependency it exists for. - -**Revisit trigger:** dialog support needs setup of its own — its own container lifecycle, its own -middleware, or a runtime import of `aiogram_dialog` — or it grows large enough that its release -cadence stops matching the handler integration's. At that point the boundary is real and a separate -package earns its keep. +DI for [aiogram-dialog](https://github.com/Tishka17/aiogram_dialog) getters and callbacks ships as +`modern_di_aiogram.dialog`, not as its own repository and PyPI distribution the way every other +`modern-di` framework integration is packaged. aiogram-dialog is not another entrypoint: it runs +inside aiogram's dispatch, so `setup_di`'s middleware has already built the per-update child +container and put it in the per-update `data`, which is the same dict aiogram-dialog hands getters +as `**kwargs` and exposes on `DialogManager.middleware_data`. What is left to write is an `inject` +that locates that container by call shape, and a distribution for that would have to re-export this +package's marker and its private container lookup across a release boundary, turning both into +public API for the sake of the split. Staying here is also what keeps the runtime dependency +avoidable: the lookup is duck-typed on `.middleware_data`, `dialog.py` imports nothing from +`aiogram_dialog`, and aiogram-dialog stays a test dependency only. diff --git a/docs/adr/0002-no-auto-inject-for-dialogs.md b/docs/adr/0002-no-auto-inject-for-dialogs.md new file mode 100644 index 0000000..f3c6eee --- /dev/null +++ b/docs/adr/0002-no-auto-inject-for-dialogs.md @@ -0,0 +1,13 @@ +# No `auto_inject` for dialog getters and callbacks + +`setup_di(..., auto_inject=True)` sweeps handlers only; dialog getters and callbacks opt in one at a +time with `@modern_di_aiogram.dialog.inject`. The sweep works on handlers because a dispatcher owns +a registry of them, so `_inject_router` can walk `chain_tail` and every observer other than +`update`, rewriting the callback of each handler not already marked injected. Dialogs have no such +registry: getters and callbacks are attributes of `Window` and widget objects, reachable only by +traversing another library's object tree by structure, which is the one thing `dialog.py` avoids so +that it can stay free of a runtime `aiogram_dialog` import +([ADR-0001](0001-dialog-support-is-a-submodule.md)). The payoff would be smaller too: `auto_inject` +exists so an existing bot with many registered handlers needs no decorator added to each, while a +`Window` names its getter and callbacks at their definition site, where `@inject` is already where a +reader is looking. diff --git a/docs/adr/0002-per-update-context-stays-a-literal.md b/docs/adr/0002-per-update-context-stays-a-literal.md deleted file mode 100644 index 29418d9..0000000 --- a/docs/adr/0002-per-update-context-stays-a-literal.md +++ /dev/null @@ -1,22 +0,0 @@ -# The per-update context stays a literal; `bind()` is not adopted - -**Decision:** the middleware keeps building its child-container context as a hand-written dict — -`{Update: event, TelegramObject: cast("Update", event).event}` at a hardcoded `Scope.REQUEST` — and -does not route it through `modern_di.integrations.bind` / `classify_connection`. Layer 2 of the -integration kit (`from_di`, `parse_markers`, `resolve_markers`, `is_injected`, `mark_injected`) is -adopted; Layer 1 is not. - -When `modern-di` 2.28 shipped `modern_di.integrations`, this package converted every hand-rolled -primitive it had — except this one. `bind(provider, connection)` classifies a *single* connection -object and produces the context entry for it. Here one incoming `event` seeds **two** aiogram types, -and asymmetrically: `Update` is bound to the event itself, `TelegramObject` to `event.event`, -aiogram's resolved inner object for that update. There is no isinstance dispatch to delegate either, -because the scope is fixed. So `bind` cannot derive this dict; it would have to be called twice and -the results merged, which is longer than the literal and hides where the asymmetry lives. - -`modern-di`'s own kit design records aiogram as the documented Layer-1 outlier for exactly this -reason, so the literal is the upstream-sanctioned shape, not a conversion this repo skipped. - -**Revisit trigger:** the kit grows a multi-provider or merge-shaped `bind`, or aiogram's update -model changes so the context becomes a single-connection classification — the shape `bind` is built -for. diff --git a/docs/adr/0003-no-auto-inject-for-dialogs.md b/docs/adr/0003-no-auto-inject-for-dialogs.md deleted file mode 100644 index f0aef34..0000000 --- a/docs/adr/0003-no-auto-inject-for-dialogs.md +++ /dev/null @@ -1,23 +0,0 @@ -# No `auto_inject` for dialog getters and callbacks - -**Decision:** `setup_di(..., auto_inject=True)` sweeps handlers only. Dialog getters and callbacks -opt in one at a time with `@modern_di_aiogram.dialog.inject`; there is no dialog equivalent of the -startup sweep. - -`auto_inject` works on handlers because a dispatcher owns a registry of them: the startup callback -walks `chain_tail`, then each router's observers, and rewrites the callback on every `HandlerObject` -it finds. Dialogs have no comparable registry. Getters and callbacks are attributes of `Window` and -widget objects, reachable only by walking whatever the user passed to `setup_dialogs`, through -widget containers this package does not own and aiogram-dialog does not expose for that purpose. A -sweep would mean traversing another library's object tree by structure — the one thing the dialog -module deliberately avoids, since it is what lets it stay free of a runtime `aiogram_dialog` import. - -Nor is the payoff the same. `auto_inject` exists so an existing bot with many registered handlers -does not need a decorator added to each; dialog code is written against `Window` definitions where -the getter and callbacks are already named explicitly at their definition site, so `@inject` sits -directly where a reader is looking. Dishka, the comparable integration, draws the line in the same -place. - -**Revisit trigger:** aiogram-dialog exposes a registry of dialogs, windows or widgets that can be -walked as a supported API rather than by structure — at which point a dialog sweep costs about what -`_inject_router` costs, and the runtime-import question can be answered on its own merits. diff --git a/docs/agents/domain.md b/docs/agents/domain.md index 93ae7ae..ca186dc 100644 --- a/docs/agents/domain.md +++ b/docs/agents/domain.md @@ -18,7 +18,7 @@ Single-context repo: ├── CONTEXT.md ├── docs/adr/ │ ├── 0001-dialog-support-is-a-submodule.md -│ └── 0002-per-update-context-stays-a-literal.md +│ └── 0002-no-auto-inject-for-dialogs.md └── modern_di_aiogram/ ```