Usage with taskiq¶
How to use¶
1. Install modern-di-taskiq¶
2. Apply to your application¶
import dataclasses
import typing
from modern_di import Container, Group, Scope, providers
from modern_di_taskiq import FromDI, setup_di
from taskiq import InMemoryBroker
@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 as_dict(self) -> dict[str, str]:
return {"service": self.settings.service_name}
class AppGroup(Group):
settings = providers.Factory(Settings, scope=Scope.APP, cache=True)
report = providers.Factory(Report, scope=Scope.REQUEST)
broker = InMemoryBroker()
container = Container(groups=[AppGroup])
setup_di(broker, container)
container.validate() # after setup_di — its connection providers are now registered
@broker.task
async def get_report(
report: typing.Annotated[Report, FromDI(Report)],
) -> dict[str, str]:
return report.as_dict()
setup_di(broker, container) stores the container on broker.state and registers TaskiqEvents.WORKER_STARTUP/WORKER_SHUTDOWN handlers that open/close it — those fire when the broker's worker process starts and stops, so a script that just calls tasks directly (like InMemoryBroker in a test) must drive the container lifecycle itself, e.g. async with broker: ... or an explicit container.open() / await container.close_async().
Deployment: run_receiver_task skips startup by default
taskiq.api.run_receiver_task(...) defaults run_startup=False, which
skips the worker startup that opens the root container — tasks still run
(the container is already open from construction), but nothing ever
closes it, so its finalizers never run at shutdown. Pass
run_startup=True (or close the root yourself around consuming) when
embedding a receiver with run_receiver_task.
Scopes¶
The integration creates a Scope.REQUEST child container for each task the worker executes. REQUEST-scoped providers (and their finalizers) live for the duration of that one task — the child container is closed after the task returns, including when it raises. APP-scoped providers persist for the whole worker process; setup_di opens the APP container on WORKER_STARTUP and runs await container.close_async() on WORKER_SHUTDOWN.
There is no Scope.SESSION for taskiq — a task queue doesn't have a session concept comparable to websockets.
Sync resolution, async cleanup¶
FromDI resolves its dependency with Container.resolve_dependency(...), which is synchronous — modern-di's resolution is always sync, regardless of the framework. The per-task Scope.REQUEST child container that resolution runs against is nevertheless torn down asynchronously: after the task handler finishes (or raises), the integration awaits container.close_async() on it. So async finalizers on REQUEST-scoped providers run correctly, while the factories themselves must build synchronously.
Framework context objects¶
taskiq.TaskiqMessage is automatically made available by the integration, so factories can declare it as a parameter and get the message that triggered the current task — see Framework Context Objects for how implicit and explicit resolution work.
The following context provider is also available for explicit import:
taskiq_message_provider— provides the currenttaskiq.TaskiqMessageobject.
Implicit (type-based) usage¶
import taskiq
from modern_di import Group, Scope, providers
def create_task_info(message: taskiq.TaskiqMessage) -> dict[str, str]:
return {
"task_id": message.task_id,
"task_name": message.task_name,
}
class AppGroup(Group):
# The message dependency is resolved by type annotation
task_info = providers.Factory(
create_task_info,
scope=Scope.REQUEST,
)
Explicit (provider-based) usage¶
import taskiq
import modern_di_taskiq
from modern_di import Group, Scope, providers
def create_task_info(message: taskiq.TaskiqMessage) -> dict[str, str]:
return {"task_id": message.task_id}
class AppGroup(Group):
task_info = providers.Factory(
create_task_info,
scope=Scope.REQUEST,
kwargs={"message": modern_di_taskiq.taskiq_message_provider},
)
See also¶
- Testing with overrides — swap providers in your tests.
- Async resources via lifespan — constructing async resources with finalizers.
- Lifecycle — finalizers and
close_async(). - Scopes — the APP → REQUEST lifetime model.
API¶
| Symbol | Description |
|---|---|
setup_di(broker, container) |
Wire the APP-scope container into taskiq — creates a REQUEST child container per task and opens/closes the APP container on worker startup/shutdown. |
FromDI(provider_or_type) |
Marker for Annotated[T, FromDI(...)] in task signatures; accepts a provider instance or a plain type. |
fetch_di_container(broker) |
Returns the APP-scope container registered with the taskiq broker. |
taskiq_message_provider |
ContextProvider for the current taskiq.TaskiqMessage. |