Conditional Providers¶
Introduction¶
In many applications, the correct implementation of a service depends on the runtime environment. You might use Redis in production but an in-memory store in development, enable debug tooling only when a flag is set, or activate an adapter only when a companion service is registered.
waku supports conditional providers through the when= parameter available on every provider
helper (singleton, scoped, transient, object_, many). Combined with markers and
activator functions, this lets you register multiple implementations of the same interface
and let the framework choose the right one at startup.
Markers¶
A Marker is a named boolean flag that controls whether a provider is active. Markers are
created as simple instances and used in two places: on the when= parameter of a provider,
and in an activator() call that decides the marker's value at startup.
| markers.py | |
|---|---|
Markers are inert on their own — they only become meaningful once you wire them to an
activator function and attach them to providers via when=.
Activator functions¶
An activator is a callable that returns bool. At container construction time, Dishka calls
every registered activator and uses the result to decide which markers are active.
The activator() helper creates a Provider that you register alongside your other providers:
Note
The activator function's parameters are resolved from the container context. If your
function accepts AppConfig, Dishka will inject the AppConfig instance from the context
dictionary. Activators with no parameters are also valid — they are called with no arguments.
Providing config via context¶
To make configuration available to activator functions, register a contextual provider at
the APP scope and pass the actual instance through the context parameter of WakuFactory.
When the container starts:
- The container resolves
AppConfigfrom the context dictionary. - Each activator function receives the config and returns
TrueorFalse. - Providers whose
when=marker evaluated toTruebecome active; the rest are skipped.
The example below demonstrates this pattern end-to-end.
Full example: environment-based service selection¶
A self-contained example that selects a cache implementation based on configuration:
Has(Type) — presence-based activation¶
Has(Type) activates a provider only when the specified type is registered somewhere in the
container. No activator function is needed — the container checks its own registry at build time.
This is useful for feature-flag-style activation where a feature is enabled by the mere presence of a dependency:
If FeatureA is removed from the providers list, the container fails to build with
GraphMissingFactoryError during graph validation.
Warning
Unlike Marker-based activation, Has(Type) is evaluated during container graph validation.
If the referenced type is missing, the container fails to build rather than silently skipping
the provider. This makes Has a good choice for hard dependencies between features.
Marker composition¶
Markers support boolean-style composition using Python operators. This lets you express complex activation conditions without writing custom activator logic:
Negation (~)¶
Activate when a marker is not active:
from waku.di import Marker, scoped
USE_REDIS = Marker('use_redis')
scoped(ICache, RedisCache, when=USE_REDIS)
scoped(ICache, InMemoryCache, when=~USE_REDIS)
AND (&)¶
Activate only when both markers are active:
from waku.di import Marker, scoped
DEBUG = Marker('debug')
PRODUCTION = Marker('production')
scoped(DebugProductionService, when=DEBUG & PRODUCTION)
OR (|)¶
Activate when either marker is active:
Tip
Composition operators work with both Marker and Has, and you can mix them freely.
For example, Marker('debug') & Has(MetricsService) activates only when the debug marker
is on and MetricsService is registered.
activator() reference¶
from waku.di import activator
def activator(fn: Callable[..., bool], *markers: Any) -> Provider:
"""Create a Provider with an activator for simple cases.
Args:
fn: Callable that returns bool to determine marker activation.
*markers: Marker instances or types to activate.
Returns:
Provider with the activator registered.
"""
A single activator call can activate multiple markers:
| multi_marker_activator.py | |
|---|---|
Note
The activator() helper returns a Provider. Register it in your module's providers
list just like any other provider. You can register multiple activators in the same module,
each controlling different markers.
Further reading¶
- Providers — provider types and scopes
- Modules — module system and provider registration
- Dishka conditional activation — advanced patterns from the underlying DI framework