Factory provider¶
Factory
provider creates new objects.
from dependency_injector import containers, providers
class User:
...
class Container(containers.DeclarativeContainer):
user_factory = providers.Factory(User)
if __name__ == "__main__":
container = Container()
user1 = container.user_factory()
user2 = container.user_factory()
The first argument of the Factory
provider is a class, a factory function or a method
that creates an object.
The rest of the Factory
positional and keyword arguments are the dependencies.
Factory
injects the dependencies every time when creates a new object. The dependencies are
injected following these rules:
If the dependency is a provider, this provider is called and the result of the call is injected.
If you need to inject the provider itself, you should use the
.provider
attribute. More at Passing providers to the objects.All other dependencies are injected “as is”.
Positional context arguments are appended after
Factory
positional dependencies.Keyword context arguments have the priority over the
Factory
keyword dependencies with the same name.
from dependency_injector import containers, providers
class Photo:
...
class User:
def __init__(self, uid: int, main_photo: Photo) -> None:
self.uid = uid
self.main_photo = main_photo
class Container(containers.DeclarativeContainer):
photo_factory = providers.Factory(Photo)
user_factory = providers.Factory(
User,
main_photo=photo_factory,
)
if __name__ == "__main__":
container = Container()
user1 = container.user_factory(1)
# Same as: # user1 = User(1, main_photo=Photo())
user2 = container.user_factory(2)
# Same as: # user2 = User(2, main_photo=Photo())
another_photo = Photo()
user3 = container.user_factory(
uid=3,
main_photo=another_photo,
)
# Same as: # user3 = User(uid=3, main_photo=another_photo)
Factory
provider can inject attributes. Use .add_attributes()
method to specify
attribute injections.
from dependency_injector import containers, providers
class Client:
...
class Service:
def __init__(self) -> None:
self.client = None
class Container(containers.DeclarativeContainer):
client = providers.Factory(Client)
service = providers.Factory(Service)
service.add_attributes(client=client)
if __name__ == "__main__":
container = Container()
service = container.service()
assert isinstance(service.client, Client)
Passing arguments to the underlying providers¶
Factory
provider can pass the arguments to the underlying providers. This helps when you need
to assemble a nested objects graph and pass the arguments deep inside.
Consider the example:
To create an Algorithm
you need to provide all the dependencies: ClassificationTask
,
Loss
, and Regularizer
. The last object in the chain, the Regularizer
has a dependency
on the alpha
value. The alpha
value varies from algorithm to algorithm:
Algorithm(
task=ClassificationTask(
loss=Loss(
regularizer=Regularizer(
alpha=alpha, # <-- the dependency
),
),
),
)
Factory
provider helps to deal with the such assembly. You need to create the factories for
all the classes and use special double-underscore __
syntax for passing the alpha
argument:
from dependency_injector import containers, providers
class Regularizer:
def __init__(self, alpha: float) -> None:
self.alpha = alpha
class Loss:
def __init__(self, regularizer: Regularizer) -> None:
self.regularizer = regularizer
class ClassificationTask:
def __init__(self, loss: Loss) -> None:
self.loss = loss
class Algorithm:
def __init__(self, task: ClassificationTask) -> None:
self.task = task
class Container(containers.DeclarativeContainer):
algorithm_factory = providers.Factory(
Algorithm,
task=providers.Factory(
ClassificationTask,
loss=providers.Factory(
Loss,
regularizer=providers.Factory(
Regularizer,
),
),
),
)
if __name__ == "__main__":
container = Container()
algorithm_1 = container.algorithm_factory(
task__loss__regularizer__alpha=0.5,
)
assert algorithm_1.task.loss.regularizer.alpha == 0.5
algorithm_2 = container.algorithm_factory(
task__loss__regularizer__alpha=0.7,
)
assert algorithm_2.task.loss.regularizer.alpha == 0.7
When you use __
separator in the name of the keyword argument the Factory
looks for
the dependency with the same name as the left part of the __
expression.
<dependency>__<keyword for the underlying provider>=<value>
If <dependency>
is found the underlying provider will receive the
<keyword for the underlying provider>=<value>
as an argument.
Passing providers to the objects¶
When you need to inject the provider itself, but not the result of its call, use the .provider
attribute of the provider that you’re going to inject.
from typing import Callable, List
from dependency_injector import containers, providers
class User:
def __init__(self, uid: int) -> None:
self.uid = uid
class UserRepository:
def __init__(self, user_factory: Callable[..., User]) -> None:
self.user_factory = user_factory
def get_all(self) -> List[User]:
return [
self.user_factory(**user_data)
for user_data in [{"uid": 1}, {"uid": 2}]
]
class Container(containers.DeclarativeContainer):
user_factory = providers.Factory(User)
user_repository_factory = providers.Factory(
UserRepository,
user_factory=user_factory.provider,
)
if __name__ == "__main__":
container = Container()
user_repository = container.user_repository_factory()
user1, user2 = user_repository.get_all()
assert user1.uid == 1
assert user2.uid == 2
Note
Any provider has a .provider
attribute.
String imports¶
Factory
provider can handle string imports:
class Container(containers.DeclarativeContainer):
service = providers.Factory("myapp.mypackage.mymodule.Service")
You can also make a relative import:
# in myapp/container.py
class Container(containers.DeclarativeContainer):
service = providers.Factory(".mypackage.mymodule.Service")
or import a member of the current module just specifying its name:
class Service:
...
class Container(containers.DeclarativeContainer):
service = providers.Factory("Service")
Note
Singleton
, Callable
, Resource
, and Coroutine
providers handle string imports
the same way as a Factory
provider.
Specializing the provided type¶
You can create a specialized Factory
provider that provides only specific type. For doing
this you need to create a subclass of the Factory
provider and define the provided_type
class attribute.
from dependency_injector import containers, providers, errors
class BaseService:
...
class SomeService(BaseService):
...
class ServiceProvider(providers.Factory):
provided_type = BaseService
# Creating service provider with a correct provided type:
class Services(containers.DeclarativeContainer):
some_service_provider = ServiceProvider(SomeService)
# Trying to create service provider an incorrect provided type:
try:
class Container(containers.DeclarativeContainer):
some_service_provider = ServiceProvider(object)
except errors.Error as exception:
print(exception)
# The output is:
# <class "__main__.ServiceProvider"> can provide only
# <class "__main__.BaseService"> instances
Abstract factory¶
AbstractFactory
provider helps when you need to create a provider of some base class
and the particular implementation is not yet know. AbstractFactory
provider is a Factory
provider with two peculiarities:
Provides only objects of a specified type.
Must be overridden before usage.
import abc
import dataclasses
import random
from typing import List
from dependency_injector import containers, providers
class AbstractCacheClient(metaclass=abc.ABCMeta):
...
@dataclasses.dataclass
class RedisCacheClient(AbstractCacheClient):
host: str
port: int
db: int
@dataclasses.dataclass
class MemcachedCacheClient(AbstractCacheClient):
hosts: List[str]
port: int
prefix: str
@dataclasses.dataclass
class Service:
cache: AbstractCacheClient
class Container(containers.DeclarativeContainer):
cache_client_factory = providers.AbstractFactory(AbstractCacheClient)
service_factory = providers.Factory(
Service,
cache=cache_client_factory,
)
if __name__ == "__main__":
container = Container()
cache_type = random.choice(["redis", "memcached"])
if cache_type == "redis":
container.cache_client_factory.override(
providers.Factory(
RedisCacheClient,
host="localhost",
port=6379,
db=0,
),
)
elif cache_type == "memcached":
container.cache_client_factory.override(
providers.Factory(
MemcachedCacheClient,
hosts=["10.0.1.1"],
port=11211,
prefix="my_app",
),
)
service = container.service_factory()
print(service.cache)
# The output depends on cache_type variable value.
#
# If the value is "redis":
# RedisCacheClient(host="localhost", port=6379, db=0)
#
# If the value is "memcached":
# MemcachedCacheClient(hosts=["10.0.1.1"], port=11211, prefix="my_app")
#
# If the value is None:
# Error: AbstractFactory(<class "__main__.AbstractCacheClient">) must be
# overridden before calling
Factory aggregate¶
FactoryAggregate
provider aggregates multiple factories.
See also
Aggregate provider – it’s a successor of FactoryAggregate
provider that can aggregate
any type of provider, not only Factory
.
The aggregated factories are associated with the string keys. When you call the
FactoryAggregate
you have to provide one of the these keys as a first argument.
FactoryAggregate
looks for the factory with a matching key and calls it with the rest of the arguments.
import dataclasses
import sys
from dependency_injector import containers, providers
@dataclasses.dataclass
class Game:
player1: str
player2: str
def play(self):
print(
f"{self.player1} and {self.player2} are "
f"playing {self.__class__.__name__.lower()}"
)
class Chess(Game):
...
class Checkers(Game):
...
class Ludo(Game):
...
class Container(containers.DeclarativeContainer):
game_factory = providers.FactoryAggregate(
chess=providers.Factory(Chess),
checkers=providers.Factory(Checkers),
ludo=providers.Factory(Ludo),
)
if __name__ == "__main__":
game_type = sys.argv[1].lower()
player1 = sys.argv[2].capitalize()
player2 = sys.argv[3].capitalize()
container = Container()
selected_game = container.game_factory(game_type, player1, player2)
selected_game.play()
# $ python factory_aggregate.py chess John Jane
# John and Jane are playing chess
#
# $ python factory_aggregate.py checkers John Jane
# John and Jane are playing checkers
#
# $ python factory_aggregate.py ludo John Jane
# John and Jane are playing ludo
You can get a dictionary of the aggregated providers using .providers
attribute.
To get a game provider dictionary from the previous example you can use
game_factory.providers
attribute.
You can also access an aggregated factory as an attribute. To create the Chess
object from the
previous example you can do chess = game_factory.chess("John", "Jane")
.
Note
You can not override the FactoryAggregate
provider.
Note
When you inject the FactoryAggregate
provider it is passed “as is”.
To use non-string keys or string keys with .
and -
, you can provide a dictionary as a positional argument:
providers.FactoryAggregate({
SomeClass: providers.Factory(...),
"key.with.periods": providers.Factory(...),
"key-with-dashes": providers.Factory(...),
})
Example:
from dependency_injector import containers, providers
class Command:
...
class CommandA(Command):
...
class CommandB(Command):
...
class Handler:
...
class HandlerA(Handler):
...
class HandlerB(Handler):
...
class Container(containers.DeclarativeContainer):
handler_factory = providers.FactoryAggregate({
CommandA: providers.Factory(HandlerA),
CommandB: providers.Factory(HandlerB),
})
if __name__ == "__main__":
container = Container()
handler_a = container.handler_factory(CommandA)
handler_b = container.handler_factory(CommandB)
assert isinstance(handler_a, HandlerA)
assert isinstance(handler_b, HandlerB)