Skip to content

Modern DI

Welcome to the modern-di documentation!

modern-di is a Python dependency injection framework which supports the following:

  • Automatic dependencies graph based on type annotations
  • Also, explicit dependencies are allowed where needed
  • Scopes and context management
  • Python 3.10+ support
  • Fully typed and tested
  • Integrations with FastAPI, FastStream, LiteStar, Typer, and pytest

Reference templates:

For end-to-end patterns drawn from real services, see the Recipes section.


Quickstart

1. Install modern-di

uv add modern-di
pip install modern-di
poetry add modern-di

If you want a framework integration, install the matching adapter — e.g. modern-di-fastapi, modern-di-litestar, modern-di-faststream, modern-di-typer. For pytest support, install modern-di-pytest.

2. Describe your dependencies

Two providers, two scopes: a Settings shared by the whole process, and a UserRepository rebuilt per request.

import dataclasses


@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class Settings:
    database_url: str = "postgresql+asyncpg://localhost/app"


@dataclasses.dataclass(kw_only=True, slots=True)
class UserRepository:
    settings: Settings    # auto-injected by type

    def find(self, user_id: int) -> dict[str, int]:
        return {"id": user_id}

3. Declare a Group

A Group is a namespace that lists your providers. You instantiate a Container from it; the Group itself is schema only.

from modern_di import Group, Scope, providers


class Dependencies(Group):
    settings = providers.Factory(
        scope=Scope.APP,                          # APP is the default
        creator=Settings,
        cache_settings=providers.CacheSettings(),  # one Settings for the whole app
    )

    user_repository = providers.Factory(
        scope=Scope.REQUEST,                       # rebuilt for each request
        creator=UserRepository,
    )

4.1. Integrate with your framework

Pick the integration you need:

The integration package builds the per-request child container automatically and closes the APP container at shutdown.

4.2. Or use modern-di directly

from modern_di import Container, Scope


async def main() -> None:
    # Pass validate=True to detect cycles and scope-chain errors at startup
    async with Container(groups=[Dependencies], validate=True) as container:
        # APP-scoped providers resolve straight from the container
        settings = container.resolve(Settings)

        # REQUEST-scoped providers need a REQUEST child container
        async with container.build_child_container(scope=Scope.REQUEST) as request:
            repo = request.resolve(UserRepository)
            user = repo.find(42)

        # Request-scope finalizers ran on `async with` exit
    # App-scope finalizers ran on the outer `async with` exit

Where to next

  • Scopes — the APP → REQUEST lifetime model in one page.
  • Lifecycle — finalizers, close_async(), validation.
  • Recipes — async SQLAlchemy, lifespan-managed resources, testing with overrides.