Multi-bindings¶
Introduction¶
Many applications need to dispatch work to multiple implementations of the same interface at once. Consider a notification system that fans out to every registered channel — email, SMS, and push — whenever an event occurs. Rather than injecting a single channel, you need the entire collection so you can iterate over it and notify through each one.
waku's many() helper solves this by registering any number of implementations for an interface
and creating a collector that resolves them as Sequence[Interface] or list[Interface].
Basic usage¶
Pass the interface type followed by one or more implementation classes:
With this registration, any component that depends on list[INotificationChannel] or
Sequence[INotificationChannel] will receive a list containing instances of all three channels,
in the order they were registered.
Injection¶
You can request the collection using either Sequence[Interface] or list[Interface]:
Tip
Both Sequence[Interface] and list[Interface] resolve to the same instances.
Parameters¶
scope¶
Controls the lifetime of the resolved collection and its individual implementations.
Defaults to Scope.REQUEST.
cache¶
When True (the default), each implementation is resolved once per scope entry and reused
within that scope. Set to False for transient behavior — a fresh instance on every injection:
when¶
Conditionally activates the entire multi-binding. See Conditional Providers for full details on markers and activators:
collect¶
Controls whether the collector (which aggregates implementations into Sequence[T] and
list[T]) is created. Defaults to True.
Set collect=False when you want to register implementations in one module but let another
module handle the collection:
Warning
When collect=False, you must provide at least one implementation. Calling
many(INotificationChannel, collect=False) with no implementations raises ValueError.
When collect=True (the default), passing no implementations is valid — it creates a
collector that resolves to an empty list. This is useful when implementations are registered
in child modules.
Note
many() accepts the same source types as any other provider helper — classes,
factory functions, and generators. See Providers — Source types
for details.
Full example: notification dispatcher¶
A common pattern is collecting all channels and dispatching notifications through each one:
many() reference¶
from waku.di import many
def many(
interface: Any,
*implementations: Any,
scope: Scope = Scope.REQUEST,
cache: bool = True,
when: BaseMarker | None = None,
collect: bool = True,
) -> Provider:
"""Register multiple implementations as a collection.
Args:
interface: Interface type for the collection.
*implementations: Implementation types or factory functions.
scope: Lifetime scope (default: Scope.REQUEST).
cache: Cache instances within scope (default: True).
when: Marker for conditional activation (default: None).
collect: Create Sequence[T]/list[T] collector (default: True).
Returns:
Provider configured for collection resolution.
Raises:
ValueError: If no implementations and collect is False.
"""
| Parameter | Default | Description |
|---|---|---|
interface |
(required) | The interface type all implementations satisfy |
*implementations |
() |
Classes or factory callables to register |
scope |
Scope.REQUEST |
Lifetime scope for the collection |
cache |
True |
Cache resolved instances within scope |
when |
None |
Conditional activation marker |
collect |
True |
Create Sequence[T] collector and list[T] alias |
How it works¶
Under the hood, many() builds a Dishka Provider with three layers:
- Individual registrations — each implementation is registered via
provider.provide(impl, provides=interface, cache=cache, when=when). - Collector —
provider.collect(interface, scope=scope, cache=cache, provides=Sequence[interface])aggregates all registered implementations (Dishka's built-in collection mechanism). - Alias —
provider.alias(Sequence[interface], provides=list[interface], cache=cache)makes the collection available aslist[interface]too.
When collect=False, only step 1 runs. This lets you split registration across modules while
keeping a single collection point.
Further reading¶
- Providers — provider types and scopes
- Conditional Providers —
when=parameter and markers - Modules — module system and provider registration
- Dishka collections — underlying collection mechanism