python-injector / injector

Python dependency injection framework, inspired by Guice
BSD 3-Clause "New" or "Revised" License
1.3k stars 81 forks source link

use specific Injector instance as `inject` decorator #249

Open r2rstep opened 8 months ago

r2rstep commented 8 months ago

This has been in my mind for some time so I decided to finally write it.

Would that be possible to specify the Injector instance that should be used to inject dependencies? I see it the following way

container = Injector([module])

...

@container.inject    # or maybe @inject(container)
def do_something(a: Dependency):
    ...

My motivation is that I have multiple python modules, each one configuring it's own dependencies so each module (in general) expects different instances of dependencies.

I know that the proposed solution requires a variable container to be defined at interpretation time but not always is that undesireable. What do you think?

My workaround currently is calling container.get but I don't really like it.

Or is there something I don't understand about how injector works and inject chooses "the most local" container?

davidparsson commented 8 months ago

I'm not entirely sure I get your use case, but I think the one of the closest thing we have is creating new types with NewType and binding different NewTypes to different instances. I've used that successfully. If the modules don't interact with each other, having parent/child injectors is another way of partially sharing injector configuration. Would any of those work for you?

r2rstep commented 8 months ago

NewType for sure will work but it introduces boilerplate, even though minimal, so it's not optimal.

To demonstrate my use case:

# common.container
class Container(injector.Module):
    def configure(self, binder):
        binder.bind(MessageBus, to=MessageBus)

# module_a.container
class Container(injector.Module):
    def configure(self, binder):
        binder.bind(SomeCommonBase, to=module_a.Concrete)

container = injector.Injector([Container(), common.container.Container()])

# module_b.container
class Container(injector.Module):
    def configure(self, binder):
        binder.bind(SomeCommonBase, to=module_b.Concrete)

container = injector.Injector([Container(), common.container.Container()])

# common.message_bus
class MessageBus:
    @injector.inject
    def __init__(self, module_specific_instance: SomeCommonBase):
        ...
jstasiak commented 8 months ago

Hey @r2rstep, I'm also not entirely surely I understand this.

In the code sample you included above, where would the "pick the Injector instance" logic go?

I feel like this misses some context and possibly a clarification of what problem is being solved here. In general I'd not expect a piece a single application context to define or use multiple Injector instances.

jstasiak commented 8 months ago

After re-reading I think I understand it better now. I believe if you want to dispatch to multiple Injector instances you may need to do it manually for the time being (get access to the instance, call get() on it).

I'd maybe try to put all of that in a single Injector instance so your various modules could coexist there but I understand it may not be feasible.

r2rstep commented 8 months ago

yeah, it's more like a feature request :) The case is for modular monolith where each module should define own dependencies.

Anyway, thanks for taking time to respond :)