Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@
- Python 3.10+ support
- Fully typed and tested
- Integrations with `aiogram`, `aiohttp`, `arq`, `Celery`, `FastAPI`, `FastStream`, `Flask`, `gRPC`, `Litestar`, `Starlette`, `taskiq`, and `Typer`
- Pytest integration (`modern-di-pytest`) — turns any DI dependency into a pytest fixture
- Pytest integration (`modern-di-pytest`) that turns any DI dependency into a pytest fixture

## Install

```bash
uv add modern-di # or: pip install modern-di
```

## Quick Start
## Quick start

```python
import dataclasses
Expand Down Expand Up @@ -77,8 +77,8 @@ See the [documentation](https://modern-di.modern-python.org) for scopes, lifecyc

Usage examples:

- with Litestar - [litestar-sqlalchemy-template](https://github.com/modern-python/litestar-sqlalchemy-template)
- with FastAPI - [fastapi-sqlalchemy-template](https://github.com/modern-python/fastapi-sqlalchemy-template)
- with Litestar: [litestar-sqlalchemy-template](https://github.com/modern-python/litestar-sqlalchemy-template)
- with FastAPI: [fastapi-sqlalchemy-template](https://github.com/modern-python/fastapi-sqlalchemy-template)

## 📚 [Documentation](https://modern-di.modern-python.org)

Expand Down
Binary file modified docs/assets/social-card.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 9 additions & 9 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@

Reference templates:

- Litestar — [litestar-sqlalchemy-template](https://github.com/modern-python/litestar-sqlalchemy-template)
- FastAPI — [fastapi-sqlalchemy-template](https://github.com/modern-python/fastapi-sqlalchemy-template)
- Litestar: [litestar-sqlalchemy-template](https://github.com/modern-python/litestar-sqlalchemy-template)
- FastAPI: [fastapi-sqlalchemy-template](https://github.com/modern-python/fastapi-sqlalchemy-template)

For end-to-end patterns drawn from real services, see the [Recipes](recipes/sqlalchemy.md) section.

Expand Down Expand Up @@ -175,17 +175,17 @@ child container for you automatically. Resolution itself is always synchronous;

## Where to next

- Framework integrations — [aiogram](integrations/aiogram.md), [aiohttp](integrations/aiohttp.md),
- Framework integrations: [aiogram](integrations/aiogram.md), [aiohttp](integrations/aiohttp.md),
[arq](integrations/arq.md), [Celery](integrations/celery.md), [FastAPI](integrations/fastapi.md),
[FastStream](integrations/faststream.md), [Flask](integrations/flask.md), [gRPC](integrations/grpc.md),
[Litestar](integrations/litestar.md), [Starlette](integrations/starlette.md),
[taskiq](integrations/taskiq.md), [Typer](integrations/typer.md), [Pytest](integrations/pytest.md).
The framework integrations build a scoped child container per request/task/call automatically,
and most close the APP container at shutdown. Flask, gRPC, and Typer have no shutdown hook, so
you close the root container yourself. The Pytest plugin exposes providers as fixtures.
- [Resolving](introduction/resolving.md) — how type-based auto-injection works.
- [Factories](providers/factories.md) — the provider you just used.
- [Scopes](providers/scopes.md) — the APP → REQUEST scope model in one page.
- [Lifecycle](providers/lifecycle.md) — finalizers, `close_async()`, validation.
- [Recipes](recipes/sqlalchemy.md) — async SQLAlchemy, lifespan-managed resources, testing with overrides.
- [Good and bad practices](recipes/good-and-bad-practices.md) — named footguns and the mechanism that catches each one.
- [Resolving](introduction/resolving.md): how type-based auto-injection works.
- [Factories](providers/factories.md): the provider you just used.
- [Scopes](providers/scopes.md): the APP → REQUEST scope model in one page.
- [Lifecycle](providers/lifecycle.md): finalizers, `close_async()`, validation.
- [Recipes](recipes/sqlalchemy.md): async SQLAlchemy, lifespan-managed resources, testing with overrides.
- [Good and bad practices](recipes/good-and-bad-practices.md): named footguns and the mechanism that catches each one.
18 changes: 9 additions & 9 deletions docs/integrations/aiogram.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ container.validate() # after setup_di — its connection providers are now regi

## Scopes

The integration creates one `Scope.REQUEST` child container **per update**.
The integration creates one `Scope.REQUEST` child container per update.
The middleware is installed on `dispatcher.update` as an
[outer middleware](https://docs.aiogram.dev/en/latest/dispatcher/middlewares.html),
so it wraps every update regardless of which router or handler ultimately
Expand Down Expand Up @@ -161,8 +161,8 @@ for how implicit and explicit resolution work.

The following context providers are also available for explicit import:

- `aiogram_update_provider` — provides the current `aiogram.types.Update`.
- `aiogram_event_provider` — provides the current `aiogram.types.TelegramObject`,
- `aiogram_update_provider` provides the current `aiogram.types.Update`.
- `aiogram_event_provider` provides the current `aiogram.types.TelegramObject`,
the concrete event unwrapped from the `Update` (e.g. a `Message` or
`CallbackQuery` instance).

Expand Down Expand Up @@ -211,9 +211,9 @@ async def log_message(

## See also

- [Testing with overrides](../recipes/testing-overrides.md) — swap providers in your tests.
- [Lifecycle](../providers/lifecycle.md) — finalizers and container teardown.
- [Scopes](../providers/scopes.md) — the APP → REQUEST lifetime model.
- [Testing with overrides](../recipes/testing-overrides.md): swap providers in your tests.
- [Lifecycle](../providers/lifecycle.md): finalizers and container teardown.
- [Scopes](../providers/scopes.md): the APP → REQUEST lifetime model.

## API

Expand All @@ -224,14 +224,14 @@ async def log_message(
| `inject` | Decorator for an aiogram handler; resolves its `FromDI`-annotated parameters. Not needed when `setup_di(..., auto_inject=True)` is used. Raises `RuntimeError` naming `setup_di` when an update reaches it without the middleware installed. |
| `fetch_di_container(dispatcher)` | Returns the root `Container` stored on the dispatcher. |
| `aiogram_update_provider` | `ContextProvider` for the current `aiogram.types.Update` (REQUEST scope). |
| `aiogram_event_provider` | `ContextProvider` for the current `aiogram.types.TelegramObject` (REQUEST scope) — the concrete event unwrapped from the `Update`. |
| `aiogram_event_provider` | `ContextProvider` for the current `aiogram.types.TelegramObject` (REQUEST scope), the concrete event unwrapped from the `Update`. |

## Usage with `aiogram-dialog`

[aiogram-dialog](https://github.com/Tishka17/aiogram_dialog) runs inside
aiogram's dispatch, so the per-update child container that `setup_di`'s
middleware already builds is reachable from dialog code. `modern_di_aiogram.dialog`
adds a dialog-aware `inject` for **getters** and **callbacks** (`on_click`,
adds a dialog-aware `inject` for getters and callbacks (`on_click`,
`on_start`/`on_close`, `on_process_result`). Install it with the normal
`setup_di(...)` and decorate your dialog functions:

Expand Down Expand Up @@ -277,7 +277,7 @@ and a callback via the positional `DialogManager`'s `.middleware_data`. Dialog D
requires the normal `setup_di(dispatcher, container)`, whose middleware provides
the per-update container.

- `modern_di_aiogram.dialog` has **no runtime dependency** on `aiogram-dialog`;
- `modern_di_aiogram.dialog` has no runtime dependency on `aiogram-dialog`;
install `aiogram-dialog` yourself.
- The `FromDI` marker is the same one used for handlers; it is re-exported from
`modern_di_aiogram.dialog` for convenience.
Expand Down
12 changes: 6 additions & 6 deletions docs/integrations/aiohttp.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Unlike FastAPI, Litestar, and Starlette, aiohttp has no separate WebSocket
object. A WebSocket is an upgraded `web.Request`, so `aiohttp_websocket_provider`
binds `web.Request` too, and is declared `bound_type=None` (not resolvable by
type, because `aiohttp_request_provider` already owns `web.Request`). That is why
you wire it **explicitly** with `FromDI(aiohttp_websocket_provider)` rather than
you wire it explicitly with `FromDI(aiohttp_websocket_provider)` rather than
by type annotation.

For per-message work, open a nested `Scope.REQUEST` child of the session
Expand Down Expand Up @@ -154,10 +154,10 @@ on the first request to a decorated method.

## See also

- [Testing with overrides](../recipes/testing-overrides.md) — swap providers in your tests.
- [Async SQLAlchemy](../recipes/sqlalchemy.md) — engine + session + repository through the request container.
- [Lifecycle](../providers/lifecycle.md) — finalizers and `close_async()`.
- [Scopes](../providers/scopes.md) — the APP → REQUEST lifetime model.
- [Testing with overrides](../recipes/testing-overrides.md): swap providers in your tests.
- [Async SQLAlchemy](../recipes/sqlalchemy.md): engine + session + repository through the request container.
- [Lifecycle](../providers/lifecycle.md): finalizers and `close_async()`.
- [Scopes](../providers/scopes.md): the APP → REQUEST lifetime model.

## API

Expand All @@ -169,4 +169,4 @@ on the first request to a decorated method.
| `fetch_di_container(app)` | Returns the root `Container` stored on the app. |
| `fetch_request_container(request)` | Returns the per-connection child container the middleware built (REQUEST for HTTP, SESSION for a WebSocket). Raises `RuntimeError` naming `setup_di` when the request did not pass through the middleware. |
| `aiohttp_request_provider` | `ContextProvider` for `web.Request` (REQUEST scope), auto-registered by type. |
| `aiohttp_websocket_provider` | `ContextProvider` for the WebSocket connection's `web.Request` (SESSION scope), `bound_type=None` — resolve via `FromDI(aiohttp_websocket_provider)`. |
| `aiohttp_websocket_provider` | `ContextProvider` for the WebSocket connection's `web.Request` (SESSION scope), `bound_type=None`; resolve via `FromDI(aiohttp_websocket_provider)`. |
12 changes: 6 additions & 6 deletions docs/integrations/arq.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ unchanged.

## Scopes

The integration builds one `Scope.REQUEST` child container **per job** in
The integration builds one `Scope.REQUEST` child container per job in
`on_job_start`. For an `@inject` task, the wrapper closes it with
`close_async()` when the task body exits, whether it returned or raised. Nested
or concurrent `@inject` calls in the same job share the child, and the last one
Expand Down Expand Up @@ -143,16 +143,16 @@ container.
`@inject` resolves dependencies by binding the task signature by name, which is
what makes injection order-insensitive. A task that mixes a `FromDI` parameter
with `*args` or `**kwargs` cannot be bound unambiguously, so `@inject` raises a
`TypeError` **at decoration time** rather than silently misrouting arguments.
`TypeError` at decoration time rather than silently misrouting arguments.
Give an `@inject` task explicit named parameters. (A task with no `FromDI`
parameter is untouched and may use `*args`/`**kwargs` freely.)

## See also

- [Testing with overrides](../recipes/testing-overrides.md) — swap providers in your tests.
- [Async resources via lifespan](../recipes/async-lifespan.md) — constructing async resources with finalizers.
- [Lifecycle](../providers/lifecycle.md) — finalizers and `close_async()`.
- [Scopes](../providers/scopes.md) — the APP → REQUEST lifetime model.
- [Testing with overrides](../recipes/testing-overrides.md): swap providers in your tests.
- [Async resources via lifespan](../recipes/async-lifespan.md): constructing async resources with finalizers.
- [Lifecycle](../providers/lifecycle.md): finalizers and `close_async()`.
- [Scopes](../providers/scopes.md): the APP → REQUEST lifetime model.

## API

Expand Down
12 changes: 6 additions & 6 deletions docs/integrations/celery.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def run_report(report: typing.Annotated[Report, FromDI(Report)]) -> str:

## Scopes

The integration creates a `Scope.REQUEST` child container **for each task invocation**, whether wired via `@inject` or [`DITask`](#the-ditask-base-class). REQUEST-scoped providers (and their finalizers) live for the duration of that one call; the child container is closed with `close_sync()` once the task returns, including when it raises. APP-scoped providers persist for the whole worker process: `setup_di` opens the APP container on `worker_process_init` (or `worker_init`) and closes it with `close_sync()` on `worker_process_shutdown` (or `worker_shutdown`).
The integration creates a `Scope.REQUEST` child container for each task invocation, whether wired via `@inject` or [`DITask`](#the-ditask-base-class). REQUEST-scoped providers (and their finalizers) live for the duration of that one call; the child container is closed with `close_sync()` once the task returns, including when it raises. APP-scoped providers persist for the whole worker process: `setup_di` opens the APP container on `worker_process_init` (or `worker_init`) and closes it with `close_sync()` on `worker_process_shutdown` (or `worker_shutdown`).

There is no `Scope.SESSION` for Celery: a task queue doesn't have a session concept comparable to websockets.

Expand Down Expand Up @@ -183,16 +183,16 @@ signals.worker_process_shutdown.send(sender=None) # a real worker fires this

## See also

- [Testing with overrides](../recipes/testing-overrides.md) — swap providers in your tests.
- [Multi-Group organization](../recipes/multi-group.md) — structuring a larger container.
- [Lifecycle](../providers/lifecycle.md) — finalizers and container teardown.
- [Scopes](../providers/scopes.md) — the APP → REQUEST lifetime model.
- [Testing with overrides](../recipes/testing-overrides.md): swap providers in your tests.
- [Multi-Group organization](../recipes/multi-group.md): structuring a larger container.
- [Lifecycle](../providers/lifecycle.md): finalizers and container teardown.
- [Scopes](../providers/scopes.md): the APP → REQUEST lifetime model.

## API

| Symbol | Description |
|---|---|
| `setup_di(app, container)` | Wire the APP-scope container into Celery — stores it on `app.conf` and opens/closes it on `worker_process_init`/`worker_process_shutdown` and `worker_init`/`worker_shutdown`. Returns the container. |
| `setup_di(app, container)` | Wire the APP-scope container into Celery: stores it on `app.conf` and opens/closes it on `worker_process_init`/`worker_process_shutdown` and `worker_init`/`worker_shutdown`. Returns the container. |
| `FromDI(provider_or_type)` | Marker for `Annotated[T, FromDI(...)]` in task signatures; accepts a provider instance or a plain type. |
| `@inject` | Decorator that builds a `Scope.REQUEST` child container per call, resolves `FromDI`-annotated parameters from it, and closes the child container with `close_sync()` afterwards. Raises `RuntimeError` naming `setup_di` when a task reaches it without `setup_di` called. A task with `FromDI` parameters that also declares `*args`/`**kwargs` raises `TypeError` at decoration. |
| `DITask` | `Task` subclass that applies `@inject` to a task's `run` method automatically; pass `task_cls=DITask` to `Celery(...)` or `base=DITask` to `@app.task(...)`. |
Expand Down
22 changes: 11 additions & 11 deletions docs/integrations/fastapi.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Usage with `FastAPI`

*More advanced example of usage with FastAPI - [fastapi-sqlalchemy-template](https://github.com/modern-python/fastapi-sqlalchemy-template)*
*More advanced example of usage with FastAPI: [fastapi-sqlalchemy-template](https://github.com/modern-python/fastapi-sqlalchemy-template)*

## How to use

Expand Down Expand Up @@ -66,13 +66,13 @@ async def get_report(
```

!!! warning "Deployment: mounted sub-apps and disabled lifespan"
FastAPI only opens the root container from the ASGI **lifespan** event. A
`setup_di`-wired app **mounted as a sub-application** (`app.mount("/sub",
FastAPI only opens the root container from the ASGI lifespan event. A
`setup_di`-wired app mounted as a sub-application (`app.mount("/sub",
subapp)`) never receives that event from its parent, and deployments that
disable lifespan (e.g. Mangum `lifespan="off"`) skip it too. Requests
still succeed (the container is already open from construction), but
nothing ever closes it, so its finalizers never run at shutdown. Call
`setup_di` on the **top-level served app**, or close the root yourself
`setup_di` on the top-level served app, or close the root yourself
(`await container.close_async()`) at shutdown.

## Websockets
Expand Down Expand Up @@ -116,8 +116,8 @@ and explicit resolution work.

The following context providers are available for import:

- `fastapi_request_provider` - Provides the current `fastapi.Request` object
- `fastapi_websocket_provider` - Provides the current `fastapi.WebSocket` object
- `fastapi_request_provider` provides the current `fastapi.Request` object
- `fastapi_websocket_provider` provides the current `fastapi.WebSocket` object

### Implicit (type-based) usage

Expand Down Expand Up @@ -169,18 +169,18 @@ class AppGroup(Group):

## See also

- [Testing with overrides](../recipes/testing-overrides.md) — swap providers in your tests.
- [Async SQLAlchemy](../recipes/sqlalchemy.md) — engine + session + repository through the request container.
- [Lifecycle](../providers/lifecycle.md) — finalizers and `close_async()`.
- [Scopes](../providers/scopes.md) — the APP → REQUEST lifetime model.
- [Testing with overrides](../recipes/testing-overrides.md): swap providers in your tests.
- [Async SQLAlchemy](../recipes/sqlalchemy.md): engine + session + repository through the request container.
- [Lifecycle](../providers/lifecycle.md): finalizers and `close_async()`.
- [Scopes](../providers/scopes.md): the APP → REQUEST lifetime model.

## API

| Symbol | Description |
|---|---|
| `setup_di(app, container)` | Registers the container on the FastAPI app and appends a lifespan that closes it on shutdown (merges with any existing `lifespan=`); returns the container. |
| `FromDI(dependency, *, use_cache=True)` | A `fastapi.Depends` wrapper that resolves a provider (or type) from the per-request child container. Raises `RuntimeError` naming `setup_di` when a request reaches it without `setup_di` called. |
| `build_di_container(connection)` | A `fastapi.Depends` callable that yields the per-request child container — REQUEST scope for an HTTP request, SESSION scope for a WebSocket. |
| `build_di_container(connection)` | A `fastapi.Depends` callable that yields the per-request child container: REQUEST scope for an HTTP request, SESSION scope for a WebSocket. |
| `fastapi_request_provider` | `ContextProvider` for `fastapi.Request` (REQUEST scope), auto-registered. |
| `fastapi_websocket_provider` | `ContextProvider` for `fastapi.WebSocket` (SESSION scope), auto-registered. |
| `fetch_di_container(app)` | Returns the root `Container` stored on the app. Raises `RuntimeError` naming `setup_di` when called on an app without `setup_di` called. |
Loading
Loading