Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to handle circular dependency [non-string keys for Dict provider] #327

Closed
ijalalfrz opened this issue Nov 20, 2020 · 4 comments
Closed
Assignees

Comments

@ijalalfrz
Copy link

ijalalfrz commented Nov 20, 2020

so i have handler class to be injected by container to get repositry object

service/handler.py

from ... import Container
from dependency_injector.wiring import inject, Provide
from .rating_repository import RatingRepository
from ..domain.commands import SaveRating
from ..domain.models import RunnerRating


class CommandHandler:
    # Dep Injector
    @inject
    def __init__(self, rating_repo: RatingRepository =
                 Provide[Container.repo.rating]):
        self.rating_repo = rating_repo

this handler is included in messagebus module like this

service/messagebus.py

from .handler import CommandHandler

Message = Union[Command, Event]

logger = logging.getLogger(__name__)
command_handler = CommandHandler()


COMMAND_HANDLERS = {
    SaveRating: command_handler.save_rating,
}

class MessageBus:

    def __init__(self):
        pass

    # rest methods...

but i also add MessageBus class on container that make me include service/messagebus.py to container.py

container.py

from .runner_rating.service import messagebus

'''
    Dependency injector for application
'''


class MessageBusContainer(containers.DeclarativeContainer):

    bus = providers.Container(
        messagebus.MessageBus
    )

when i run the app, it shows ImportError on container.py because i imported messagebus and inside it there is a handler object that import container.py

what should i do to solve this? because message bus will be injected to other module in future

@rmk135 rmk135 self-assigned this Nov 20, 2020
@rmk135
Copy link
Member

rmk135 commented Nov 20, 2020

Hi @ijalalfrz ,

I think that it is a typical circular dependency problem. In your case it seems like the loop is: handler -> container -> messagebus -> handler.

The quick solution is to move CommandHandler in the runtime. Here is some sample code:

File service/messagebus.py:

from .commands import Command, SaveRating, DoSomethingElse


class MessageBus:

    COMMAND_HANDLERS = {}

    def __init__(self):
        from .handler import CommandHandler
        command_handler = CommandHandler()

        self.COMMAND_HANDLERS = {
            SaveRating: command_handler.save_rating,
            DoSomethingElse: command_handler.something_else,
        }

    def handle(self, command: Command):
        self.COMMAND_HANDLERS[command]()

File service/containers.py:

from dependency_injector import containers, providers

from . import repositories, messagebus


class Container(containers.DeclarativeContainer):

    rating_repository = providers.Singleton(repositories.RatingRepository)

    message_bus = providers.Factory(messagebus.MessageBus)

And a sample code to run it:

from .containers import Container
from .commands import SaveRating, DoSomethingElse


if __name__ == '__main__':
    container = Container()
    message_bus = container.message_bus()

    message_bus.handle(SaveRating)
    message_bus.handle(DoSomethingElse)

The other way

The other way is to pull CommandHandler into the container.

File service/handler.py:

from .repositories import RatingRepository


class CommandHandler:
    def __init__(self, rating_repo: RatingRepository):
        self.rating_repo = rating_repo

    def save_rating(self):
        print('Saving rating')

    def something_else(self):
        print('Doing something else')

File service/messagebus.py:

from typing import Dict, Callable, Any

from .commands import Command


class MessageBus:

    def __init__(self, command_handlers: Dict[str, Callable[..., Any]]):
        self.command_handlers = command_handlers

    def handle(self, command: Command):
        self.command_handlers[command]()

File service/containers.py:

from dependency_injector import containers, providers

from . import repositories, handler, messagebus, commands


class Container(containers.DeclarativeContainer):

    rating_repository = providers.Singleton(repositories.RatingRepository)

    command_handler = providers.Singleton(
        handler.CommandHandler,
        rating_repo=rating_repository,
    )

    message_bus = providers.Factory(
        messagebus.MessageBus,
        command_handlers=providers.Dict({
            commands.SaveRating: command_handler.provided.save_rating,
            commands.DoSomethingElse: command_handler.provided.something_else,
        }),
    )

Sample code for running is the same.

Note: this option can not be implemented cause providers.Dict support only string keys in master. I will make a quick release to fix that.

@rmk135
Copy link
Member

rmk135 commented Nov 20, 2020

I have released version 4.5.0. It supports non-string keys for Dict provider.

I've also included this example in the mini application examples: https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/commands-and-handlers

@rmk135 rmk135 added the feature label Nov 20, 2020
@rmk135 rmk135 changed the title How to handle circular dependency How to handle circular dependency [non-string keys for Dict provider] Nov 20, 2020
@ijalalfrz
Copy link
Author

ijalalfrz commented Nov 20, 2020

I have released version 4.5.0. It supports non-string keys for Dict provider.

I've also included this example in the mini application examples: https://github.com/ets-labs/python-dependency-injector/tree/master/examples/miniapps/commands-and-handlers

Wow thank you @rmk135 for your fast reply and solution, i'm glad to contribute for this feature in this project 👏

@rmk135
Copy link
Member

rmk135 commented Nov 20, 2020

Thank you for bringing up the issue @ijalalfrz . Appreciate you contribution.

Let me know if you face other issues or need any other help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants