Usage with Typer¶
How to use¶
1. Install modern-di-typer¶
2. Apply to your application¶
import dataclasses
import typing
import modern_di_typer
import typer
from modern_di import Container, Group, Scope, providers
@dataclasses.dataclass(kw_only=True, slots=True, frozen=True)
class Settings:
service_name: str = "catalog"
@dataclasses.dataclass(kw_only=True, slots=True)
class Report:
settings: Settings # APP-scoped, injected by type
def render(self) -> str:
return f"service={self.settings.service_name}"
class AppGroup(Group):
settings = providers.Factory(Settings, scope=Scope.APP, cache=True)
report = providers.Factory(Report, scope=Scope.REQUEST)
app = typer.Typer()
container = Container(groups=[AppGroup], validate=True)
modern_di_typer.setup_di(app, container)
@app.command()
@modern_di_typer.inject
def status(
report: typing.Annotated[Report, modern_di_typer.FromDI(Report)], # resolve by type
) -> None:
typer.echo(report.render())
if __name__ == "__main__":
with container: # runs APP-scope finalizers on exit
app()
@modern_di_typer.inject builds a REQUEST child container for each command invocation and resolves FromDI-annotated parameters from it. The outer with container: ensures APP-scope finalizers run when the CLI exits.
Action scope¶
To resolve Scope.ACTION dependencies, inject modern_di.Container — @inject supplies the
REQUEST-scoped container it creates per invocation. Call build_child_container() on it to enter
ACTION scope:
Building on the first example's app and container:
import modern_di
import modern_di_typer
import typing
from modern_di import Group, Scope, providers
class Job:
def run(self) -> None: ...
class AppGroup(Group):
job = providers.Factory(Job, scope=Scope.ACTION, bound_type=None)
@app.command()
@modern_di_typer.inject
def run_job(
container: typing.Annotated[modern_di.Container, modern_di_typer.FromDI(modern_di.Container)],
) -> None:
with container.build_child_container() as action_container:
job = action_container.resolve_provider(AppGroup.job)
job.run()
See also¶
- Testing with overrides — swap providers in your tests.
- Lifecycle — finalizers and container teardown.
- Scopes — the APP → REQUEST lifetime model.
API¶
| Symbol | Description |
|---|---|
setup_di(app, container) |
Register the app-scoped container with a Typer app |
@inject |
Decorator that resolves FromDI-annotated parameters before the command runs |
FromDI(provider_or_type) |
Marker for Annotated[T, FromDI(...)]; accepts a provider instance or a plain type |
fetch_di_container(ctx) |
Returns the app-scoped container from ctx.obj |