@provides¶
@injectable covers 99% of dependency wiring. Use @provides when the concrete
implementation of an interface must be chosen at runtime — typically based
on a feature flag, environment, or config rule.
When to reach for @provides¶
- A composite/strategy binding where the selected implementation depends on runtime state (feature flag, region, tenant, environment).
- A third-party SDK client that needs non-trivial construction and can't be
expressed as a single
@injectableclass.
If you just need to register a class, use @injectable. If you need a manual
binding for a value that never changes (e.g. a constant config object), prefer
extra_modules.
Basic usage¶
from abc import ABC, abstractmethod
from django_autowired import injectable, provides
class IPaymentGateway(ABC):
@abstractmethod
def charge(self, amount: int) -> str: ...
class StripeV1Gateway(IPaymentGateway):
def charge(self, amount: int) -> str:
return f"stripe-v1:{amount}"
class StripeV2Gateway(IPaymentGateway):
def charge(self, amount: int) -> str:
return f"stripe-v2:{amount}"
@provides(bind_to=IPaymentGateway)
def payment_gateway(flags: FeatureFlags) -> IPaymentGateway:
if flags.is_enabled("use_stripe_v2"):
return StripeV2Gateway()
return StripeV1Gateway()
FeatureFlags is resolved from the container just like any @injectable
dependency — provider functions support full dependency injection.
Signature¶
bind_to— required. The interface/type the factory's return value binds to. A provider withoutbind_tois rejected at decoration time.scope— Lifecycle scope the container applies to factory invocation.
Scope semantics¶
@provides is a factory — the framework owns the scope around it:
| Scope | Factory invoked |
|---|---|
SINGLETON (default) |
Once per container lifetime. Result cached. |
TRANSIENT |
On every container.get() call. |
THREAD |
Once per thread. Result cached per-thread. |
This matches Spring @Bean / Dagger @Provides semantics. Your factory does
not manage caching — the framework does.
Factory injection¶
Factory parameters are resolved from the container. All parameters must be type-annotated:
@provides(bind_to=IPaymentGateway)
def payment_gateway(
flags: FeatureFlags,
cfg: StripeConfig,
logger: ILogger,
) -> IPaymentGateway:
if flags.is_enabled("use_stripe_v2"):
return StripeV2Gateway(cfg, logger)
return StripeV1Gateway(cfg, logger)
A factory parameter without an annotation raises TypeError at build time.
One binding per target¶
Whether you use @injectable or @provides, each interface can be bound
exactly once. Any combination of:
- two
@injectableclasses with the samebind_to - two
@providesfunctions with the samebind_to - one
@injectable+ one@provideswith the samebind_to
…raises DuplicateBindingError. This is a feature: predictable, deterministic
resolution is a core guarantee of django-autowired.
If you need multiple candidates selected at runtime, express the conditional
logic inside a single @provides function that returns the correct
instance.
Eager evaluation¶
Every @provides factory runs once during container.initialize() to catch
errors at boot:
- Factory raises →
ProviderResolutionErrorwraps the exception. - Factory has unresolvable dependencies →
ProviderResolutionError.
For SINGLETON scope this eager invocation produces the cached instance. For
TRANSIENT and THREAD scope the instance is discarded — the eager run is
pure verification. This matches the library's "fail loudly at boot" principle.
What @provides cannot do¶
- Cannot decorate a class.
@providesonly decorates functions. - Cannot be called without
bind_to. RaisesTypeErrorimmediately. - Cannot chain/fallback. Multiple providers for the same target are an error, not an ordered list.
FAQ¶
Q: Should my factory return a class or an instance?
An instance. @provides is a factory. The framework handles scope/caching.
Q: What if the factory needs to construct something complex with many deps?
Declare those deps as factory parameters — they are resolved from the container
just like @injectable constructor parameters.
Q: Can a @provides factory depend on another @provides factory?
Yes. Dependency resolution across factories is handled by the backend.