From c58f965ec08fb25f9a128d083f1b976c6536b31b Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Mon, 13 Jul 2026 23:38:35 +0300 Subject: [PATCH 1/3] docs(profile): steer newcomers to modern-di, name the decorator-free edge that-depends' own README says "if you're starting a new project, consider modern-di", but nothing on the org surfaces said so -- and the two canonical blurbs actively inverted it: modern-di reads "Powerful", that-depends reads "Simple". A newcomer with no other signal picks "simple", i.e. the one we steer away from. Adds a one-line steer above the DI table on profile/README.md and the docs site: modern-di for new projects, that-depends when you need async resolution. Leaves the canonical one-liners untouched, since those are synced across the GitHub description and pyproject on both repos. Also plays modern-di's strongest card in the launch copy. Verified against source, not taken on trust: modern_di defines and exports no DI decorator, while dishka needs @provide on providers and @inject on every handler. The copy states the limit of the claim too -- FromDI(...) at the FastAPI boundary is still a marker -- so the claim survives the scrutiny an HN thread applies. --- docs/index.md | 7 +++++++ planning/launch-playbook.md | 8 +++++--- profile/README.md | 5 +++++ 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index a2b9af4..8b8b971 100644 --- a/docs/index.md +++ b/docs/index.md @@ -96,6 +96,13 @@ catalog below. ## Dependency injection { #di } +!!! tip "Which one should I use?" + + Start new projects on [`modern-di`](https://github.com/modern-python/modern-di) + — sync resolution, a minimal core, and the integrations listed below. Reach for + [`that-depends`](https://github.com/modern-python/that-depends) when you need + **async resolution**; it stays maintained and ships a migration guide. + - [`modern-di`](https://github.com/modern-python/modern-di) — powerful DI framework with scopes. - [`modern-di-aiogram`](https://github.com/modern-python/modern-di-aiogram) — `modern-di` integration for aiogram. - [`modern-di-aiohttp`](https://github.com/modern-python/modern-di-aiohttp) — `modern-di` integration for aiohttp. diff --git a/planning/launch-playbook.md b/planning/launch-playbook.md index 361b74e..09da2d4 100644 --- a/planning/launch-playbook.md +++ b/planning/launch-playbook.md @@ -35,7 +35,7 @@ Suggested order: **modern-di (HN+Reddit)** → **stack announcement** → > I kept building a FastAPI API, a FastStream worker, and a Typer CLI that all shared the same business logic — and rewiring the same dependencies three times, once per entrypoint. modern-di is my attempt at *one* typed wiring shared across all of them. > > What it does: -> - **Type-based autowiring** — your constructor type hints *are* the graph; no `Provide[...]` markers or string keys. +> - **Type-based autowiring, no decorators** — your constructor type hints *are* the graph. modern-di ships no `@inject`/`@provide` decorator at all; your handlers keep only the framework's own route decorator. > - **Explicit scopes** (APP → REQUEST → …) with **build-time** cycle- and scope-violation checks. > - **Sync resolution by design** — async setup/teardown lives in the framework lifespan, not in resolution. This is deliberate and permanent, not a missing feature. > - **Official integrations** for FastAPI, Litestar, Starlette, Flask, aiohttp, FastStream, Celery, arq, taskiq, aiogram, gRPC, and Typer — plus a first-party pytest plugin that turns any dependency into a fixture. @@ -43,7 +43,9 @@ Suggested order: **modern-di (HN+Reddit)** → **stack announcement** → > > Where it honestly stands: > - If you're building a single FastAPI service and everything is request-scoped, FastAPI's `Depends` is enough — you don't need this. -> - The closest library is **Dishka**, which is considerably more established (~1.2k stars to my ~60) and supports async resolution and custom scopes. If you need either, use Dishka. Integration coverage is now roughly comparable between the two. modern-di's bets are a simpler sync-only model, the first-party pytest plugin, and being one consistent small stack. +> - The closest library is **Dishka**, which is considerably more established (~1.2k stars to my ~60) and supports async resolution and custom scopes. If you need either, use Dishka. Integration coverage is now roughly comparable between the two. modern-di's bets are the decorator-free model (Dishka needs `@provide` on your providers and an `@inject` on every handler; modern-di needs neither), a simpler sync-only core, the first-party pytest plugin, and being one consistent small stack. +> +> To be precise about "decorator-free," since it's the claim I'd most want challenged: the *graph* is pure type annotations, and there is no DI decorator anywhere. At the web-framework boundary you do still name the provider — `FromDI(Dependencies.user_service)` in a FastAPI signature — because that's how FastAPI's own DI hooks in. > - It's young but actively developed, and deliberately conservative — the docs have a "design decisions" page for what it leaves out on purpose (auto-binding, in-package integrations, graph rendering). > > Docs include a full comparison and a "do you even need a DI container?" page: https://modern-di.modern-python.org @@ -71,7 +73,7 @@ comment; the honest "when not to use it" is what earns goodwill. > **Comparison** > - **vs FastAPI `Depends` / Litestar `Provide`:** great inside one app, but don't span workers/CLIs or give typed app-scoped singletons; modern-di shares one wiring across all entrypoints. > - **vs `dependency-injector`:** type-based autowiring instead of `Provide[...]` markers; nested request scopes; first-party pytest plugin. -> - **vs Dishka** (the closest library): Dishka is considerably more established (~1.2k stars) and supports async resolution + custom scopes — pick it if you need those. Integration coverage is now roughly comparable. modern-di bets on a simpler sync-only model and a first-party pytest plugin. +> - **vs Dishka** (the closest library): Dishka is considerably more established (~1.2k stars) and supports async resolution + custom scopes — pick it if you need those. Integration coverage is now roughly comparable. modern-di bets on a decorator-free model (no `@provide`, no `@inject` on your handlers), a simpler sync-only core, and a first-party pytest plugin. > - **vs `that-depends`** (my earlier framework): modern-di adds explicit scopes and drops global state; that-depends stays maintained for async resolution. > > Docs + comparison: https://modern-di.modern-python.org diff --git a/profile/README.md b/profile/README.md index 456569e..ec514c3 100644 --- a/profile/README.md +++ b/profile/README.md @@ -21,6 +21,11 @@ Open-source templates and libraries for building production-ready Python applica ### Dependency injection +**Which one?** Start new projects on [`modern-di`](https://github.com/modern-python/modern-di) +— sync resolution, minimal core, and the integrations below. Reach for +[`that-depends`](https://github.com/modern-python/that-depends) when you need +**async resolution**; it stays maintained, and it ships a migration guide. + | Project | What it is | Stars | Downloads | |---|---|---|---| | [`modern-di`](https://github.com/modern-python/modern-di) | Powerful dependency-injection framework with IoC container and scopes | [![Stars](https://img.shields.io/github/stars/modern-python/modern-di)](https://github.com/modern-python/modern-di/stargazers) | [![Downloads](https://static.pepy.tech/badge/modern-di/month)](https://pepy.tech/projects/modern-di) | From 83c1ba7370140f83b15ddac6c0ce43613dce1537 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Mon, 13 Jul 2026 23:47:01 +0300 Subject: [PATCH 2/3] fix(playbook): correct the decorator claim -- @inject IS needed in 7 integrations The previous copy said modern-di "ships no @inject/@provide decorator at all". That was wrong: it generalized from FastAPI and Litestar. Checked all 12 integrations -- @inject is required in Starlette, Flask, aiohttp, Celery, arq, aiogram and Typer, and only FastAPI, Litestar, FastStream and taskiq are free of it. The universal, defensible difference is narrower: providers are plain class attributes, so there is no @provide anywhere, where dishka requires one on every provider. The @inject advantage is real but holds in 4 of 11 integrations, not all of them. Copy now states both the claim and its limits, so it survives the scrutiny an HN thread applies -- which is the entire point of the honest framing. --- planning/launch-playbook.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/planning/launch-playbook.md b/planning/launch-playbook.md index 09da2d4..fec9334 100644 --- a/planning/launch-playbook.md +++ b/planning/launch-playbook.md @@ -35,7 +35,7 @@ Suggested order: **modern-di (HN+Reddit)** → **stack announcement** → > I kept building a FastAPI API, a FastStream worker, and a Typer CLI that all shared the same business logic — and rewiring the same dependencies three times, once per entrypoint. modern-di is my attempt at *one* typed wiring shared across all of them. > > What it does: -> - **Type-based autowiring, no decorators** — your constructor type hints *are* the graph. modern-di ships no `@inject`/`@provide` decorator at all; your handlers keep only the framework's own route decorator. +> - **Type-based autowiring** — your constructor type hints *are* the graph, and providers are plain class attributes on a `Group`. There is no `@provide` decorator and no string keys. In the FastAPI, Litestar, FastStream and taskiq integrations your handlers need no DI decorator either — just the framework's own route decorator. > - **Explicit scopes** (APP → REQUEST → …) with **build-time** cycle- and scope-violation checks. > - **Sync resolution by design** — async setup/teardown lives in the framework lifespan, not in resolution. This is deliberate and permanent, not a missing feature. > - **Official integrations** for FastAPI, Litestar, Starlette, Flask, aiohttp, FastStream, Celery, arq, taskiq, aiogram, gRPC, and Typer — plus a first-party pytest plugin that turns any dependency into a fixture. @@ -43,9 +43,9 @@ Suggested order: **modern-di (HN+Reddit)** → **stack announcement** → > > Where it honestly stands: > - If you're building a single FastAPI service and everything is request-scoped, FastAPI's `Depends` is enough — you don't need this. -> - The closest library is **Dishka**, which is considerably more established (~1.2k stars to my ~60) and supports async resolution and custom scopes. If you need either, use Dishka. Integration coverage is now roughly comparable between the two. modern-di's bets are the decorator-free model (Dishka needs `@provide` on your providers and an `@inject` on every handler; modern-di needs neither), a simpler sync-only core, the first-party pytest plugin, and being one consistent small stack. +> - The closest library is **Dishka**, which is considerably more established (~1.2k stars to my ~60) and supports async resolution and custom scopes. If you need either, use Dishka. Integration coverage is now roughly comparable between the two. modern-di's bets are a lighter declaration model, a simpler sync-only core, the first-party pytest plugin, and being one consistent small stack. > -> To be precise about "decorator-free," since it's the claim I'd most want challenged: the *graph* is pure type annotations, and there is no DI decorator anywhere. At the web-framework boundary you do still name the provider — `FromDI(Dependencies.user_service)` in a FastAPI signature — because that's how FastAPI's own DI hooks in. +> On the declaration model, precisely — because I'd rather state the limits than have them found: providers are plain class attributes, so there's no `@provide` anywhere, and in the FastAPI/Litestar/FastStream/taskiq integrations handlers need no DI decorator. But seven of the other integrations (Starlette, Flask, aiohttp, Celery, arq, aiogram, Typer) *do* need an `@inject`, and at the FastAPI boundary you still name the provider (`FromDI(Dependencies.user_service)`), because that's how FastAPI's own DI hooks in. So: lighter than Dishka on declarations, not "decorator-free." > - It's young but actively developed, and deliberately conservative — the docs have a "design decisions" page for what it leaves out on purpose (auto-binding, in-package integrations, graph rendering). > > Docs include a full comparison and a "do you even need a DI container?" page: https://modern-di.modern-python.org @@ -73,7 +73,7 @@ comment; the honest "when not to use it" is what earns goodwill. > **Comparison** > - **vs FastAPI `Depends` / Litestar `Provide`:** great inside one app, but don't span workers/CLIs or give typed app-scoped singletons; modern-di shares one wiring across all entrypoints. > - **vs `dependency-injector`:** type-based autowiring instead of `Provide[...]` markers; nested request scopes; first-party pytest plugin. -> - **vs Dishka** (the closest library): Dishka is considerably more established (~1.2k stars) and supports async resolution + custom scopes — pick it if you need those. Integration coverage is now roughly comparable. modern-di bets on a decorator-free model (no `@provide`, no `@inject` on your handlers), a simpler sync-only core, and a first-party pytest plugin. +> - **vs Dishka** (the closest library): Dishka is considerably more established (~1.2k stars) and supports async resolution + custom scopes — pick it if you need those. Integration coverage is now roughly comparable. modern-di bets on a lighter declaration model (providers are plain class attributes — no `@provide`; and no `@inject` in the FastAPI/Litestar/FastStream/taskiq integrations, though seven others do need one), a simpler sync-only core, and a first-party pytest plugin. > - **vs `that-depends`** (my earlier framework): modern-di adds explicit scopes and drops global state; that-depends stays maintained for async resolution. > > Docs + comparison: https://modern-di.modern-python.org From 11c4cc18a73ece3e839639d10bf225609a4fdea0 Mon Sep 17 00:00:00 2001 From: Artur Shiriev Date: Mon, 13 Jul 2026 23:53:54 +0300 Subject: [PATCH 3/3] fix(playbook): "sync-only" was wrong -- async apps are first-class modern-di is not sync-only. The container is an async context manager, async finalizers are supported (there is a dedicated AsyncFinalizerInSyncCloseError pointing at close_async), and async resources -- an aiohttp session, an asyncpg pool -- are constructed in the framework lifespan and injected by type via a ContextProvider, which is a documented recipe. Most of the 13 integrations are async frameworks. What is actually synchronous is the resolve path: container.resolve(X) never awaits. That is the claim the copy should make, and it now does, across all ten places that said "sync-only" or "sync resolution by design". Also drops the advice to use Dishka for async. The ContextProvider recipe is the answer, so the copy points there instead. Dishka is still named honestly as the more established alternative -- we just do not send people to it. Fixes the same misframing in the org-profile and docs-site DI steers added earlier on this branch. --- docs/index.md | 9 ++++++--- planning/launch-playbook.md | 24 ++++++++++++++---------- profile/README.md | 8 +++++--- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/docs/index.md b/docs/index.md index 8b8b971..327f25e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -99,9 +99,12 @@ catalog below. !!! tip "Which one should I use?" Start new projects on [`modern-di`](https://github.com/modern-python/modern-di) - — sync resolution, a minimal core, and the integrations listed below. Reach for - [`that-depends`](https://github.com/modern-python/that-depends) when you need - **async resolution**; it stays maintained and ships a migration guide. + — a minimal core plus the integrations listed below. Async apps are first-class: + the resolve path itself stays synchronous, and resources that need to `await` are + constructed in the framework lifespan + ([recipe](https://modern-di.modern-python.org/recipes/async-lifespan/)). + [`that-depends`](https://github.com/modern-python/that-depends) is the async-first + sibling and stays maintained; it ships a migration guide to `modern-di`. - [`modern-di`](https://github.com/modern-python/modern-di) — powerful DI framework with scopes. - [`modern-di-aiogram`](https://github.com/modern-python/modern-di-aiogram) — `modern-di` integration for aiogram. diff --git a/planning/launch-playbook.md b/planning/launch-playbook.md index fec9334..6b85eb2 100644 --- a/planning/launch-playbook.md +++ b/planning/launch-playbook.md @@ -37,13 +37,14 @@ Suggested order: **modern-di (HN+Reddit)** → **stack announcement** → > What it does: > - **Type-based autowiring** — your constructor type hints *are* the graph, and providers are plain class attributes on a `Group`. There is no `@provide` decorator and no string keys. In the FastAPI, Litestar, FastStream and taskiq integrations your handlers need no DI decorator either — just the framework's own route decorator. > - **Explicit scopes** (APP → REQUEST → …) with **build-time** cycle- and scope-violation checks. -> - **Sync resolution by design** — async setup/teardown lives in the framework lifespan, not in resolution. This is deliberate and permanent, not a missing feature. +> - **Async apps are first-class; the *resolve path* is synchronous.** `container.resolve(X)` never awaits — but the container is an async context manager, async finalizers are supported, and genuinely async resources (an `aiohttp` session, an `asyncpg` pool) are constructed in the framework lifespan and injected by type. Most of the integrations are async frameworks. Keeping `await` out of resolution is deliberate and permanent, not a missing feature. > - **Official integrations** for FastAPI, Litestar, Starlette, Flask, aiohttp, FastStream, Celery, arq, taskiq, aiogram, gRPC, and Typer — plus a first-party pytest plugin that turns any dependency into a fixture. > - Zero-dependency core, MIT, 116 releases. > > Where it honestly stands: > - If you're building a single FastAPI service and everything is request-scoped, FastAPI's `Depends` is enough — you don't need this. -> - The closest library is **Dishka**, which is considerably more established (~1.2k stars to my ~60) and supports async resolution and custom scopes. If you need either, use Dishka. Integration coverage is now roughly comparable between the two. modern-di's bets are a lighter declaration model, a simpler sync-only core, the first-party pytest plugin, and being one consistent small stack. +> - The closest library is **Dishka** — considerably more established (~1.2k stars to my ~60), with custom scopes and `await` inside resolution. modern-di deliberately takes the other road: an async resource (an `aiohttp` session, an `asyncpg` pool) is constructed in the framework lifespan and injected by type via a `ContextProvider`, so the resolve path stays synchronous. There's a recipe for exactly this: https://modern-di.modern-python.org/recipes/async-lifespan/ +> - Integration coverage between the two is now roughly comparable. modern-di's bets are a lighter declaration model, a smaller core, the first-party pytest plugin, and being one consistent small stack. > > On the declaration model, precisely — because I'd rather state the limits than have them found: providers are plain class attributes, so there's no `@provide` anywhere, and in the FastAPI/Litestar/FastStream/taskiq integrations handlers need no DI decorator. But seven of the other integrations (Starlette, Flask, aiohttp, Celery, arq, aiogram, Typer) *do* need an `@inject`, and at the FastAPI boundary you still name the provider (`FromDI(Dependencies.user_service)`), because that's how FastAPI's own DI hooks in. So: lighter than Dishka on declarations, not "decorator-free." > - It's young but actively developed, and deliberately conservative — the docs have a "design decisions" page for what it leaves out on purpose (auto-binding, in-package integrations, graph rendering). @@ -51,7 +52,7 @@ Suggested order: **modern-di (HN+Reddit)** → **stack announcement** → > Docs include a full comparison and a "do you even need a DI container?" page: https://modern-di.modern-python.org > Repo: https://github.com/modern-python/modern-di > -> Happy to dig into the design — especially the sync-only decision. +> Happy to dig into the design — especially the decision to keep `await` out of the resolve path. Etiquette: post Tue–Thu ~8–10am ET; never ask for upvotes; reply to every comment; the honest "when not to use it" is what earns goodwill. @@ -65,7 +66,7 @@ comment; the honest "when not to use it" is what earns goodwill. **Body:** > **What My Project Does** -> modern-di is a dependency-injection framework. You declare providers once (type-based autowiring from constructor hints), and resolve them with explicit scopes (APP → REQUEST → …) and build-time cycle/scope checks. The same container wires your FastAPI app, your FastStream workers, your Celery/arq/taskiq tasks, your Typer CLI, and your tests — via 13 official integrations and a first-party pytest plugin that turns any dependency into a fixture. Sync resolution by design; async lives in the framework lifespan. +> modern-di is a dependency-injection framework. You declare providers once (type-based autowiring from constructor hints), and resolve them with explicit scopes (APP → REQUEST → …) and build-time cycle/scope checks. The same container wires your FastAPI app, your FastStream workers, your Celery/arq/taskiq tasks, your Typer CLI, and your tests — via 13 official integrations and a first-party pytest plugin that turns any dependency into a fixture. Async apps are first-class — most of the integrations are async frameworks; what's synchronous is the *resolve path* itself, with async construction and teardown handled in the framework lifespan. > > **Target Audience** > Python teams whose business logic runs behind *more than one entrypoint* (an API plus workers/CLIs) and who want one wiring instead of three. It's production-intended but young — early adopters welcome. If you have a single web service where everything is request-scoped, framework-native `Depends`/`Provide` is enough and modern-di is overkill. @@ -73,8 +74,8 @@ comment; the honest "when not to use it" is what earns goodwill. > **Comparison** > - **vs FastAPI `Depends` / Litestar `Provide`:** great inside one app, but don't span workers/CLIs or give typed app-scoped singletons; modern-di shares one wiring across all entrypoints. > - **vs `dependency-injector`:** type-based autowiring instead of `Provide[...]` markers; nested request scopes; first-party pytest plugin. -> - **vs Dishka** (the closest library): Dishka is considerably more established (~1.2k stars) and supports async resolution + custom scopes — pick it if you need those. Integration coverage is now roughly comparable. modern-di bets on a lighter declaration model (providers are plain class attributes — no `@provide`; and no `@inject` in the FastAPI/Litestar/FastStream/taskiq integrations, though seven others do need one), a simpler sync-only core, and a first-party pytest plugin. -> - **vs `that-depends`** (my earlier framework): modern-di adds explicit scopes and drops global state; that-depends stays maintained for async resolution. +> - **vs Dishka** (the closest library): Dishka is considerably more established (~1.2k stars), has custom scopes, and awaits inside resolution. modern-di takes the other road — async resources are built in the lifespan and injected by type through a `ContextProvider` ([recipe](https://modern-di.modern-python.org/recipes/async-lifespan/)), so resolution stays sync. Integration coverage is now roughly comparable. modern-di bets on a lighter declaration model (providers are plain class attributes — no `@provide`; and no `@inject` in the FastAPI/Litestar/FastStream/taskiq integrations, though seven others do need one), a smaller core, and a first-party pytest plugin. +> - **vs `that-depends`** (my earlier framework): modern-di adds explicit scopes and drops global state; that-depends remains the async-first sibling and stays maintained. > > Docs + comparison: https://modern-di.modern-python.org @@ -85,7 +86,7 @@ comment; the honest "when not to use it" is what earns goodwill. Submit the link **https://modern-di.modern-python.org** (the docs, not the bare repo), tag `python`. Authored comment: -> Typed DI for Python with explicit scopes, built so one container wires a FastAPI app, FastStream workers, and a Typer CLI together. Sync-only by design. The docs include an honest comparison vs dependency-injector/Dishka and a "do you even need DI?" section. Author here — happy to discuss the sync-only call. +> Typed DI for Python with explicit scopes, built so one container wires a FastAPI app, FastStream workers, and a Typer CLI together. Async apps are first-class; the resolve path itself never awaits, by design. The docs include an honest comparison vs dependency-injector/Dishka and a "do you even need DI?" section. Author here — happy to discuss the no-await-in-resolve call. Only post if you have a Lobsters account with karma; it's allergic to drive-by self-promo. @@ -132,9 +133,12 @@ of four parallel copies that drift apart. Thirteen official integrations cover t web frameworks (FastAPI, Litestar, Starlette, Flask, aiohttp), the task queues (Celery, arq, taskiq), messaging, gRPC, and the CLI. It uses type-based autowiring (your constructor hints are the graph), explicit scopes with build-time validation, -and a first-party pytest plugin that turns any dependency into a fixture. It's -deliberately sync-only: -async setup and teardown belong in the framework lifespan, not in resolution. If +and a first-party pytest plugin that turns any dependency into a fixture. Async +applications are first-class — most of those integrations are async frameworks. +What is deliberately synchronous is the *resolve path*: a resource that genuinely +needs to `await` (an `aiohttp` session, an `asyncpg` pool) is constructed in the +framework lifespan and injected by type through a `ContextProvider`, so resolution +itself never awaits. If you only have a single web service, your framework's own `Depends` is enough — modern-di earns its place when you have a second entrypoint. (The docs include an honest comparison with dependency-injector and Dishka, and a "do you even need diff --git a/profile/README.md b/profile/README.md index ec514c3..800443f 100644 --- a/profile/README.md +++ b/profile/README.md @@ -22,9 +22,11 @@ Open-source templates and libraries for building production-ready Python applica ### Dependency injection **Which one?** Start new projects on [`modern-di`](https://github.com/modern-python/modern-di) -— sync resolution, minimal core, and the integrations below. Reach for -[`that-depends`](https://github.com/modern-python/that-depends) when you need -**async resolution**; it stays maintained, and it ships a migration guide. +— a minimal core plus the integrations below. Async apps are first-class: the +resolve path itself stays synchronous, and resources that need to `await` are built +in the framework lifespan. [`that-depends`](https://github.com/modern-python/that-depends) +is the async-first sibling and stays maintained; it ships a migration guide to +`modern-di`. | Project | What it is | Stars | Downloads | |---|---|---|---|