modern-di integration for Litestar.
Full guide: Litestar integration docs
Usage example: litestar-sqlalchemy-template
uv add modern-di-litestar # or: pip install modern-di-litestarimport dataclasses
from modern_di import Group, Scope, providers
@dataclasses.dataclass(kw_only=True)
class Database:
url: str
@dataclasses.dataclass(kw_only=True)
class UserRepository:
db: Database
class AppDependencies(Group):
database = providers.Factory(creator=Database, kwargs={"url": "sqlite:///app.db"})
user_repo = providers.Factory(scope=Scope.REQUEST, creator=UserRepository)import litestar
from modern_di import Container
from modern_di_litestar import ModernDIPlugin
groups = [AppDependencies]
container = Container(groups=groups)
app = litestar.Litestar(
plugins=[ModernDIPlugin(container, autowired_groups=groups)],
)
container.validate() # optional fail-fast; the plugin has registered its providers by nowPassing autowired_groups to ModernDIPlugin autowires each provider as a Litestar dependency keyed by its attribute name (database, user_repo), so a route receives one by declaring a parameter with that name. The name di_container is reserved for the plugin's own dependency (section 5). Creating the app raises ImproperlyConfiguredException if a group declares a provider under that name, or if a di_container dependency is already registered when the plugin initializes, whether through Litestar(dependencies=...) or an earlier plugin.
Mark every injected parameter with NamedDependency[...] from litestar.di. Litestar 2.23 deprecated
inferring a dependency from a plain annotation, and Litestar 3.0 removes it.
from litestar.di import NamedDependency
from modern_di_litestar import FromDI
@litestar.get(
"/users",
dependencies={"repo": FromDI(UserRepository)},
)
async def list_users(repo: NamedDependency[UserRepository]) -> list[str]: ...FromDI accepts a type (UserRepository) or a provider instance (AppDependencies.user_repo). Pass a provider instance only for providers outside autowired_groups: Litestar rejects one provider registered under two keys and raises ImproperlyConfiguredException.
When autowired_groups is passed to ModernDIPlugin, a route receives a provider by naming a parameter after it:
@litestar.get("/users")
async def list_users(user_repo: NamedDependency[UserRepository]) -> list[str]: ...def request_method(request: litestar.Request) -> str:
return request.method
class AppDependencies(Group):
...
request_method = providers.Factory(
scope=Scope.REQUEST,
creator=request_method,
bound_type=None,
)litestar_request_provider and litestar_websocket_provider are pre-built ContextProvider instances that make the current Request / WebSocket objects resolvable within DI.
The plugin registers a di_container dependency that yields the per-connection child container (REQUEST scope for HTTP, SESSION scope for WebSocket). For work that should live shorter than a request, build a child container from it inside a route:
@litestar.get("/")
async def handler(di_container: NamedDependency[Container]) -> None:
async with di_container.build_child_container() as action_container:
result = action_container.resolve_provider(AppDependencies.some_action_scoped_factory)from modern_di_litestar import fetch_di_container
container = fetch_di_container(app)By default the plugin opens the root container before any of your startup code runs and closes it after all of your shutdown code has run. Your lifespan context managers and your on_startup and on_shutdown hooks can all resolve APP-scoped dependencies, and the APP-scoped finalizers run last. The plugin puts its own manager first in lifespan and its own hook last in on_shutdown. This holds for code passed to Litestar(...) or added by an on_app_init handler or a plugin listed before ModernDIPlugin. A plugin listed after it can still add a manager ahead of it or a shutdown hook behind it.
When the container outlives the app, for example because several apps or a worker share it, pass manage_lifespan=False and open and close it yourself:
container = Container(groups=groups)
app = litestar.Litestar(
plugins=[ModernDIPlugin(container, autowired_groups=groups, manage_lifespan=False)],
)
async with container:
... # run the app and anything else that shares the containerWith the flag off the plugin still registers its providers and still builds and closes a child container for every request and websocket session. Only the root is left to you, and its APP-scoped finalizers run when your async with exits.
Litestar runs dependency cleanup when the handler returns, before it sends the response, so the per-connection child container is already closed while a Stream or ServerSentEvent body runs. A REQUEST-scoped dependency injected with FromDI, such as a database session, has been finalized by then. To use REQUEST-scoped dependencies in the body, open a REQUEST child of the root container inside the generator, as the streaming section of the guide shows.
| Symbol | Description |
|---|---|
ModernDIPlugin(container, autowired_groups=None, *, manage_lifespan=True) |
Litestar InitPlugin that wires the DI container into the app lifecycle. With manage_lifespan=False the app's lifespan leaves the root container alone and you open and close it yourself |
FromDI(dependency) |
Returns a Litestar Provide that resolves a provider (or type) per connection |
litestar_request_provider |
ContextProvider for the current litestar.Request (REQUEST scope) |
litestar_websocket_provider |
ContextProvider for the current litestar.WebSocket (SESSION scope) |
fetch_di_container(app) |
Retrieves the root container from app.state |
π¦ PyPI
π License
Built on modern-di, a dependency-injection framework with an IoC container and scopes.
Browse the full list of templates and libraries in
modern-python; the org profile has the categorized index.