ivankorobkov / python-inject

Python dependency injection
Apache License 2.0
671 stars 77 forks source link

Bind to callable with arguments #86

Open jorgeas80 opened 1 year ago

jorgeas80 commented 1 year ago

Hello,

I have this use case in Django: Want to bind a class to a callable that requires one or more arguments.

Here is a simplified example of what I need:

from django.db import models
from inject import Binder

class A(models.Model):
    type = models.IntegerField()

class Base:
    pass

class B(Base)
    pass

class C(Base):
    pass

class Factory:
    def __init__(self, t: int):
        self.t = t
    def build() -> Base:
        return B() if self.t == 0 else C()

def my_config(binder: Binder):
    binder.bind_to_provider(A, Factory) # --> Factory will resolve the link at runtime but needs an argument (t)

So, Factory requires an argument. When instantiated (call to __init__) or even when build method is called (can change that if needed)

What would be the proper way of achieving this?

Many thanks in advance

jorgeas80 commented 1 year ago

Oh, I think this another issue is related. Going to check it

https://github.com/ivankorobkov/python-inject/issues/51

gabis-cordiguide commented 1 year ago

51 seems to describe an issue when calling the function requiring the dependency. In this case the dependency injected requires the argument. I think it's a different issue, one which i am also encountering. I can solve it by injecting an object which creates the "Factory" class, something like:

class FactoryFactory:
    def create_factory(self, t:int) -> Factory:
        return Factory(t)

and then doing

inject.get_injector().get_instance(FactoryFactory).create_factory(10)

but i think it would be nice to have the ability to write something like:

inject.get_injector().get_instance(Type[Factory])(10)

and then have the injector provide the bound class instead of it's instance.

gabis-cordiguide commented 1 year ago

In the specific case in this issue the problem can also be solved by moving the t:int argument to the build() method, although i don't know if that is possible in the larger scope of that project. in that case the Factory method is already performing the functionality of the "FactoryFactory" i mentioned in the previous comment.

gabis-cordiguide commented 1 year ago

Just noticed that it is possible if Type[Factory] is explicitly bound. would nice to have it be the default as well.

ivankorobkov commented 1 year ago

Hi! I'm sorry for the delay.

You got it all right. If you need to pass any extra arguments to build an object, you can create and inject an intermediate builder.

You see, internally the injector treats the whole application as a static immutable graph of objects. Something like this: graph1

All objects are typed, it means that there exists exactly one instance for each type in the graph.

Positional arguments and values (int, etc), cannot be represented in such a graph in Python. Even though you can bind a value to an int, typically, this is not what one should do.

So, if you need to pass different arguments/values to construct different objects, you need to use a builder/factory, as you stated above.