Factory of Factories pattern

This example demonstrates “Factory of Factories” pattern.

The idea of the pattern is in creating a Factory that creates another Factory and adds additional arguments.

base_factory = providers.Factory(
    providers.Factory
    SomeClass,
    base_argument=1,
)

concrete_factory = providers.Factory(
    OtherClass,
    instance=base_factory(extra_argument=1),
)


if __name__ == "__main__":
    instance = concrete_factory()
    # Same as: # instance = SomeClass(base_argument=1, extra_argument=2)

Sample code

Listing of the pattern example:

"""`Factory of Factories` pattern."""

from dependency_injector import containers, providers


class SqlAlchemyDatabaseService:

    def __init__(self, session, base_class):
        self.session = session
        self.base_class = base_class


class TokensService:

    def __init__(self, id_generator, database):
        self.id_generator = id_generator
        self.database = database


class Token:
    ...


class UsersService:

    def __init__(self, id_generator, database):
        self.id_generator = id_generator
        self.database = database


class User:
    ...


# Sample objects
session = object()
id_generator = object()


class Container(containers.DeclarativeContainer):

    database_factory = providers.Factory(
        providers.Factory,
        SqlAlchemyDatabaseService,
        session=session,
    )

    token_service = providers.Factory(
        TokensService,
        id_generator=id_generator,
        database=database_factory(base_class=Token),
    )

    user_service = providers.Factory(
        UsersService,
        id_generator=id_generator,
        database=database_factory(base_class=User),
    )


if __name__ == "__main__":
    container = Container()

    token_service = container.token_service()
    assert token_service.database.base_class is Token

    user_service = container.user_service()
    assert user_service.database.base_class is User

Arguments priority

Passing of the arguments works the same way like for any other Factory provider.

# 1. Keyword arguments of upper level factory are added to lower level factory
factory_of_dict_factories = providers.Factory(
    providers.Factory,
    dict,
    arg1=1,
)
dict_factory = factory_of_dict_factories(arg2=2)
print(dict_factory())  # prints: {"arg1": 1, "arg2": 2}

# 2. Keyword arguments of upper level factory have priority
factory_of_dict_factories = providers.Factory(
    providers.Factory,
    dict,
    arg1=1,
)
dict_factory = factory_of_dict_factories(arg1=2)
print(dict_factory())  # prints: {"arg1": 2}

# 3. Keyword arguments provided from context have the most priority
factory_of_dict_factories = providers.Factory(
    providers.Factory,
    dict,
    arg1=1,
)
dict_factory = factory_of_dict_factories(arg1=2)
print(dict_factory(arg1=3))  # prints: {"arg1": 3}

Credits

The “Factory of Factories” pattern was suggested by the Dependency Injector users.