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

Question about how rodi handles __init__ #43

Open
lucas-labs opened this issue Nov 6, 2023 · 2 comments
Open

Question about how rodi handles __init__ #43

lucas-labs opened this issue Nov 6, 2023 · 2 comments

Comments

@lucas-labs
Copy link
Contributor

lucas-labs commented Nov 6, 2023

Hi!

I was working on a project using rodi and I noticed that when a dependency that depends on another dependency has an __init__, it fails to resolve. Ok, I'm not good explaing things with human words, so as Linus said... talk is cheap, show me the code:

This works just fine:

class Foo:
    def bar(self) -> str:
        return 'bar'

class Baz:
    foo: Foo

    def call_foo(self) -> str:
        return self.foo.bar()

container = Container()
container.register(Foo)
container.register(Baz)
baz = container.resolve(Baz)

# this one works
print(baz.call_foo()) # >>> bar 

But if we add a init, it gets angry

class Foo:
    def bar(self) -> str:
        return 'bar'

class Baz:
    foo: Foo

+    def __init__(self) -> None:
+        pass

    def call_foo(self) -> str:
        return self.foo.bar()

container = Container()
container.register(Foo)
container.register(Baz)
baz = container.resolve(Baz)

# 💥
print(baz.call_foo())

# Traceback (most recent call last):
#   File "whatever\test.py", blahblah
#     return self.foo.bar()
#            ^^^^^^^^
# AttributeError: 'Baz' object has no attribute 'foo'

And this works too, but I love the idea of not having to use an __init__ if I don't need it (I know there's a whole activist movement out there claiming that constructor based injection is the new heaven. But I prefer property-based injection if that option is available, especially in python):

...
class Baz:
   def __init__(self, foo: Foo) -> None:
        self.foo = foo
...

So, here's my question. Is this behaviour by design?

@RobertoPrevato
Copy link
Member

RobertoPrevato commented Nov 8, 2023

Hi @lucas-labs
Thank You for opening this issue. rodi currently works this way: if a class defines a constructor, it resolves dependencies inspecting it (the __init__); otherwise it tries to resolve by class annotations. In the second case, it must be possible to create an instance of the class by simple calling it (this particular detail is by design) because dependencies are set right after instantiating the class.

As a side note, all inspections happen once for best performance: rodi creates activator functions that are used to resolve objects.

But now that you are asking about this, rodi might be modified to support both cases: resolving both __init__ and class properties in such cases.

I wouldn't care about the activists you mention: I also prefer to not define an __init__ method when possible and use only class annotations to describe dependencies (the code is less verbose). And I didn't think that supporting both situations at the same time would be useful. By the way, I worked on rodi not because I am a "dependency injection activist" myself, simply because I worked for years with ASP.NET Core and its DI, and I know how comfortable it is. I kept intentionally rodi abstracted away from BlackSheep because I want to be able to use it also in other kinds of projects, especially CLI apps.

@lucas-labs
Copy link
Contributor Author

@RobertoPrevato Yeah! I've worked with groovy on grails (ruby on rails brother but using the groovy lang) for many years and that's heavy based on DI. It's quite useful, especially for web frameworks like grails or blacksheep.

The only useful situation that I can think of supporting both, having an __init__ method and also defining properties to be injected by its type annotations is the case when you need to do some initialization tasks that doesn't depend on the injected stuff, like so:

class Baz:
    foo: Foo

    def __init__(self) -> None:
        self.whatever = ':)'

    def call_foo(self) -> str:
        return self.foo.bar()

In this case self.foo would not be injected by rodi. Not a big issue tho. It might even be ok from a design point of view. I was just doing something similar to the above and it didn't work so I came here to ask haha.

Thanks for the answer!!
Cheers

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

No branches or pull requests

2 participants