python-dependency-injector icon indicating copy to clipboard operation
python-dependency-injector copied to clipboard

How can I register an interface with multiple interfaces in a container?

Open satodaiki opened this issue 4 years ago • 3 comments

hi.

Sorry for the confusing question. I want to register only the interface in the Container without having an implementation.

Specifically, it is as follows:

import abc

from dependency_injector import containers, providers

# It is an implementation of Repository that executes user registration/update.
# It is implemented separately for Postgres implementation and InMemory implementation.

class InsertRepository(metaclass=abc.ABCMeta):
    def insert(entity):
        pass

class UpdateRepository(metaclass=abc.ABCMeta):
    def update(entity):
        pass

class DeleteRepository(metaclass=abc.ABCMeta):
    def delete(entity):
        pass

class PostgresInsertRepository(InsertRepository):
    def insert(entity):
        # awesome code

class PostgresUpdateRepository(UpdateRepository):
    def update(entity):
        # awesome code

class PostgresDeleteRepository(DeleteRepository):
    def delete(entity):
        # awesome code

class InMemoryInsertRepository(InsertRepository):
    def insert(entity):
        # awesome code

class InMemoryUpdateRepository(UpdateRepository):
    def update(entity):
        # awesome code

class InMemoryDeleteRepository(DeleteRepository):
    def delete(entity):
        # awesome code

class UserRepository(
    InsertRepository,
    UpdateRepository,
    metaclass=abc.ABCMeta
):
    pass

# I want UserRepository to have PostgresInsertRepository and PostgresUpdateRepository implementations 
# and register them in Container.

# I thought about creating a PostgresUpdateRepository, but I want to avoid it because I just call it
# example:
class PostgresUserRepository(
    UserRepository
):
    def __init__(
        self,
        postgres_insert_repository: PostgresInsertRepository,
        postgres_update_repository: PostgresUpdateRepository
    ):
        self.postgres_insert_repository = postgres_insert_repository
        self.postgres_update_repository = postgres_update_repository

    def insert(entity):
        self.postgres_insert_repository.insert(entity)

    def update(entity):
        self.postgres_update_repository.insert(entity)

class Container(containers.DeclarativeContainer):

    insert_repository = providers.Factory(
        PostgresInsertRepository
    )

    update_repository = providers.Factory(
        PostgresUpdateRepository
    )

    user_repository = providers.Factory(
        PostgresUserRepository,
        postgres_insert_repository=insert_repository,
        postgres_update_repository=update_repository,
    )

(Of course, the above code normally receives DB related values, but it is a simplified code because it deviates a little from this question.)

Do you have any good ideas?

satodaiki avatar Sep 13 '21 00:09 satodaiki

Hi @satodaiki , take a look at the Dependency provider. Here is an example of what you can do with it:

class Container(containers.DeclarativeContainer):

    insert_repository = providers.Factory(
        PostgresInsertRepository
    )

    update_repository = providers.Factory(
        PostgresUpdateRepository
    )

    user_repository = providers.Dependency(
        instance_of=UserRepository,
        default=providers.Factory(
            PostgresUserRepository,
            insert_repository=providers.Dependency(InsertRepository, default=insert_repository),
            update_repository=providers.Dependency(UpdateRepository, default=update_repository),
        )
    )


if __name__ == "__main__":
    # OK
    container = Container()
    assert isinstance(container.user_repository(), UserRepository)

    # Improper type of user repository
    container = Container(user_repository=object())
    try:
        assert isinstance(container.user_repository(), UserRepository)
    except errors.Error as exception:
        print(exception)

    # Improper type of insert repository
    container = Container(insert_repository=object())
    try:
        assert isinstance(container.user_repository(), UserRepository)
    except errors.Error as exception:
        print(exception)

    # Improper type of update repository
    container = Container(update_repository=object())
    try:
        assert isinstance(container.user_repository(), UserRepository)
    except errors.Error as exception:
        print(exception)

Does it look like something you were looking for?

PS: Argument default is optional.

rmk135 avatar Sep 13 '21 01:09 rmk135

@rmk135 Thank you!

However, is it necessary to define the following PostgresUserRepository even in the example presented?

class PostgresUserRepository(
    UserRepository
):
    def __init__(
        self,
        postgres_insert_repository: PostgresInsertRepository,
        postgres_update_repository: PostgresUpdateRepository
    ):
        self.postgres_insert_repository = postgres_insert_repository
        self.postgres_update_repository = postgres_update_repository

    def insert(entity):
        self.postgres_insert_repository.insert(entity)

    def update(entity):
        self.postgres_update_repository.insert(entity)

satodaiki avatar Sep 13 '21 02:09 satodaiki

Hi @satodaiki ,

However, is it necessary to define the following PostgresUserRepository even in the example presented?

Yes, I have PostgresUserRepository class defined. Sharing the full sample that I used locally:

import abc

from dependency_injector import containers, providers, errors


class InsertRepository(metaclass=abc.ABCMeta):
    def insert(self, entity):
        pass


class UpdateRepository(metaclass=abc.ABCMeta):
    def update(self, entity):
        pass


class DeleteRepository(metaclass=abc.ABCMeta):
    def delete(self, entity):
        pass


class PostgresInsertRepository(InsertRepository):
    def insert(self, entity):
        pass


class PostgresUpdateRepository(UpdateRepository):
    def update(self, entity):
        pass


class PostgresDeleteRepository(DeleteRepository):
    def delete(self, entity):
        pass


class InMemoryInsertRepository(InsertRepository):
    def insert(self, entity):
        pass


class InMemoryUpdateRepository(UpdateRepository):
    def update(self, entity):
        pass


class UserRepository(
    InsertRepository,
    UpdateRepository,
    metaclass=abc.ABCMeta
):
    pass


class PostgresUserRepository(
    UserRepository
):
    def __init__(
        self,
        insert_repository: PostgresInsertRepository,
        update_repository: PostgresUpdateRepository
    ):
        self.insert_repository = insert_repository
        self.update_repository = update_repository


class Container(containers.DeclarativeContainer):

    insert_repository = providers.Factory(
        PostgresInsertRepository
    )

    update_repository = providers.Factory(
        PostgresUpdateRepository
    )

    user_repository = providers.Dependency(
        instance_of=UserRepository,
        default=providers.Factory(
            PostgresUserRepository,
            insert_repository=providers.Dependency(InsertRepository, default=insert_repository),
            update_repository=providers.Dependency(UpdateRepository, default=update_repository),
        )
    )


if __name__ == "__main__":
    # OK
    container = Container()
    assert isinstance(container.user_repository(), UserRepository)

    # Improper type of user repository
    container = Container(user_repository=object())
    try:
        assert isinstance(container.user_repository(), UserRepository)
    except errors.Error as exception:
        print(exception)

    # Improper type of insert repository
    container = Container(insert_repository=object())
    try:
        assert isinstance(container.user_repository(), UserRepository)
    except errors.Error as exception:
        print(exception)

    # Improper type of update repository
    container = Container(update_repository=object())
    try:
        assert isinstance(container.user_repository(), UserRepository)
    except errors.Error as exception:
        print(exception)

Does it cover your initial goal? Please let me know if you're still looking for any answers or improvements.

Best, Roman

rmk135 avatar Nov 08 '21 01:11 rmk135