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.

../_images/factory_init_injections.png
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)

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:

../_images/factory_init_injections_underlying.png

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.

../_images/factory_delegation.png
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.

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.

../_images/abstract_factory.png
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. When you call the FactoryAggregate it delegates the call to one of the factories.

The aggregated factories are associated with the string names. When you call the FactoryAggregate you have to provide one of the these names as a first argument. FactoryAggregate looks for the factory with a matching name and delegates it the work. The rest of the arguments are passed to the delegated Factory.

../_images/factory_aggregate.png
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 factories using the .factories attribute of the FactoryAggregate. To get a game factories dictionary from the previous example you can use game_factory.factories 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”.