Factory providers

Factory provider creates new instance of specified class on every call.

Nothing could be better than brief example:

../_images/factory.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
"""`Factory` providers example."""

import collections

import dependency_injector.providers as providers


User = collections.namedtuple('User', [])

# Factory provider creates new instance of specified class on every call.
users_factory = providers.Factory(User)

# Creating several User objects:
user1 = users_factory()  # Same as: user1 = User()
user2 = users_factory()  # Same as: user2 = User()

Factory providers and __init__ injections

Factory takes a various number of positional and keyword arguments that are used as __init__() injections. Every time, when Factory creates new one instance, positional and keyword argument injections would be passed as an instance’s arguments.

Injections are done according to the next rules:

  • All providers (instances of Provider) are called every time when injection needs to be done.
  • Providers could be injected “as is” (delegated), if it is defined obviously. Check out Factory providers delegation.
  • All other injectable values are provided “as is”.
  • Positional context arguments will be appended after Factory positional injections.
  • Keyword context arguments have priority on Factory keyword injections and will be merged over them.

For example, if injectable value of injection is a Factory, it will provide new one instance (as a result of its call) every time, when injection needs to be done.

Example below is a little bit more complicated. It shows how to create Factory of particular class with __init__() injections which injectable values are also provided by another factories:

../_images/factory_init_injections.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
"""`Factory` providers init injections example."""

import collections

import dependency_injector.providers as providers


CreditCard = collections.namedtuple('CreditCard', [])
Photo = collections.namedtuple('Photo', [])
User = collections.namedtuple('User', ['uid', 'main_photo', 'credit_card'])

# User, Photo and CreditCard factories:
credit_cards_factory = providers.Factory(CreditCard)
photos_factory = providers.Factory(Photo)
users_factory = providers.Factory(User,
                                  main_photo=photos_factory,
                                  credit_card=credit_cards_factory)

# Creating several User objects:
user1 = users_factory(1)
# Same as: user1 = User(1,
#                       main_photo=Photo(),
#                       credit_card=CreditCard())
user2 = users_factory(2)
# Same as: user2 = User(2,
#                       main_photo=Photo(),
#                       credit_card=CreditCard())


# Context keyword arguments have priority on keyword argument injections:
main_photo = Photo()
credit_card = CreditCard()

user3 = users_factory(3,
                      main_photo=main_photo,
                      credit_card=credit_card)
# Same as: user3 = User(3,
#                       main_photo=main_photo,
#                       credit_card=credit_card)

Factory providers delegation

Factory provider could be delegated to any other provider via any kind of injection.

As it was mentioned earlier, if Factory is injectable value, it will be called every time when injection needs to be done. But sometimes there is a need to inject Factory provider itself (not a result of its call) as a dependency. Such injections are called - delegated provider injections.

Saying in other words, delegation of factories - is a way to inject factories themselves, instead of results of their calls.

Factory delegation is performed by wrapping delegated Factory into special provider type - Delegate, that just returns wrapped Factory.

Actually, there are three ways for creating factory delegates:

  • DelegatedFactory(...) - use special type of factory - DelegatedFactory. Such factories are always injected as delegates (“as is”).
  • Delegate(Factory(...)) - obviously wrapping factory into Delegate provider.
  • Factory(...).delegate() - calling factory Factory.delegate() method, that returns delegate wrapper for current factory.
  • Factory(...).provider - getting factory Factory.provider attribute, that returns delegate wrapper for current factory (alias of Factory(...).delegate() method).

Example:

../_images/factory_delegation.png
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
"""`Factory` providers delegation example."""

import collections

import dependency_injector.providers as providers


Photo = collections.namedtuple('Photo', [])


class User(object):
    """Example user model."""

    def __init__(self, photos_factory):
        """Initializer."""
        self.photos_factory = photos_factory
        self._main_photo = None

    @property
    def main_photo(self):
        """Return user's main photo."""
        if not self._main_photo:
            self._main_photo = self.photos_factory()
        return self._main_photo


# Defining User and Photo factories using DelegatedFactory provider:
photos_factory = providers.DelegatedFactory(Photo)
users_factory = providers.DelegatedFactory(User,
                                           photos_factory=photos_factory)

# or using Delegate(Factory(...))

photos_factory = providers.Factory(Photo)
users_factory = providers.Factory(User,
                                  photos_factory=providers.Delegate(
                                      photos_factory))


# or using Factory(...).delegate()

photos_factory = providers.Factory(Photo)
users_factory = providers.Factory(User,
                                  photos_factory=photos_factory.delegate())


# Creating several User objects:
user1 = users_factory()  # Same as: user1 = User(photos_factory=photos_factory)
user2 = users_factory()  # Same as: user2 = User(photos_factory=photos_factory)

# Making some asserts:
assert isinstance(user1.main_photo, Photo)
assert isinstance(user2.main_photo, Photo)

# or using Factory(...).provider

photos_factory = providers.Factory(Photo)
users_factory = providers.Factory(User,
                                  photos_factory=photos_factory.provider)


# Creating several User objects:
user1 = users_factory()  # Same as: user1 = User(photos_factory=photos_factory)
user2 = users_factory()  # Same as: user2 = User(photos_factory=photos_factory)

# Making some asserts:
assert isinstance(user1.main_photo, Photo)
assert isinstance(user2.main_photo, Photo)

Factory providers specialization

Factory provider could be specialized for any kind of needs via creating its subclasses.

One of such specialization features is a limitation to Factory provided type:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
"""`Factory` specialization with limitation to provided type example."""

import dependency_injector.providers as providers
import dependency_injector.errors as errors


class BaseService(object):
    """Base service class."""


class SomeService(BaseService):
    """Some service."""


class ServiceProvider(providers.Factory):
    """Service provider."""

    provided_type = BaseService


# Creating service provider with correct provided type:
some_service_provider = ServiceProvider(SomeService)

# Trying to create service provider incorrect provided type:
try:
    some_service_provider = ServiceProvider(object)
except errors.Error as exception:
    print(exception)
    # <class '__main__.ServiceProvider'> can provide only
    # <class '__main__.BaseService'> instances

Abstract factory providers

AbstractFactory provider is a Factory provider that must be explicitly overridden before calling.

Note

Overriding of AbstractFactory provider is possible only by another Factory provider.

AbstractFactory provider is useful when it is needed to specify explicitly that it only provides abstraction, but not an implementation. Client code must override such factories with factories that provide particular implementations. Otherwise, AbstractFactory will raise an error on attempt of calling it. At the same time, AbstractFactory is regular provider that could be injected into other providers (or used for any other kind of bindings) without being overridden. After AbstractFactory provider has been overridden, its behaviour is identical to regular Factory provider.

Example:

../_images/abstract_factory.png

Listing of cache.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
"""Example hierarchy of cache clients with abstract base class."""


class AbstractCacheClient(object):
    """Abstract cache client."""


class RedisCacheClient(AbstractCacheClient):
    """Cache client implementation based on Redis."""

    def __init__(self, host, port, db):
        """Initializer."""
        self.host = host
        self.port = port
        self.db = db


class MemcacheCacheClient(AbstractCacheClient):
    """Cache client implementation based on Memcached."""

    def __init__(self, hosts, port, prefix):
        """Initializer."""
        self.hosts = hosts
        self.port = port
        self.prefix = prefix

Listing of example.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
"""`AbstractFactory` providers example."""

import cache

import dependency_injector.providers as providers


# Define abstract cache client factory:
cache_client_factory = providers.AbstractFactory(cache.AbstractCacheClient)

if __name__ == '__main__':
    # Override abstract factory with redis client factory:
    cache_client_factory.override(providers.Factory(cache.RedisCacheClient,
                                                    host='localhost',
                                                    port=6379,
                                                    db=0))
    redis_cache = cache_client_factory()
    print(redis_cache)  # <cache.RedisCacheClient object at 0x10975bc50>

    # Override abstract factory with memcache client factory:
    cache_client_factory.override(providers.Factory(cache.MemcacheCacheClient,
                                                    hosts=['10.0.1.1',
                                                           '10.0.1.2',
                                                           '10.0.1.3'],
                                                    port=11211,
                                                    prefix='my_app'))
    memcache_cache = cache_client_factory()
    print(memcache_cache)  # <cache.MemcacheCacheClient object at 0x10975bc90>

Factory aggregate providers

FactoryAggregate provider is a special type of provider that aggregates other Factory providers.

Note

FactoryAggregate is not overridable. Calling of FactoryAggregate.override() will result in raising of an expection.

Next prototype might be the best demonstration of FactoryAggregate features:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
"""FactoryAggregate provider prototype."""


class FactoryAggregate(object):
    """FactoryAggregate provider prototype."""

    def __init__(self, **factories):
        """Initializer."""
        self.factories = factories

    def __call__(self, factory_name, *args, **kwargs):
        """Create object."""
        return self.factories[factory_name](*args, **kwargs)

    def __getattr__(self, factory_name):
        """Return factory with specified name."""
        return self.factories[factory_name]

Example below shows one of the FactoryAggregate use cases, when concrete implementation (game) must be selected based on dynamic input (CLI).

Listing of games.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
"""Example games module."""


class Game(object):
    """Base game class."""

    def __init__(self, player1, player2):
        """Initializer."""
        self.player1 = player1
        self.player2 = player2

    def play(self):
        """Play game."""
        print('{0} and {1} are playing {2}'.format(
            self.player1, self.player2, self.__class__.__name__.lower()))


class Chess(Game):
    """Chess game."""


class Checkers(Game):
    """Checkers game."""


class Ludo(Game):
    """Ludo game."""

Listing of example.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
"""`FactoryAggregate` providers example."""

import sys

import dependency_injector.providers as providers

from games import Chess, Checkers, Ludo


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()

    selected_game = game_factory(game_type, player1, player2)
    selected_game.play()

    # $ python example.py chess John Jane
    # John and Jane are playing chess
    #
    # $ python example.py checkers John Jane
    # John and Jane are playing checkers
    #
    # $ python example.py ludo John Jane
    # John and Jane are playing ludo