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
.providerattribute. More at Passing providers to the objects.All other dependencies are injected “as is”.
Positional context arguments are appended after
Factorypositional dependencies.Keyword context arguments have the priority over the
Factorykeyword 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)