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, andpytest
Reference templates:
- LiteStar — litestar-sqlalchemy-template
- FastAPI — fastapi-sqlalchemy-template
For end-to-end patterns drawn from real services, see the Recipes section.
Quickstart¶
1. Install 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