bobthemighty / punq

An IoC container for Python 3.8+
MIT License
311 stars 14 forks source link

Dynamic dependency resolution? #184

Open michael-rubel opened 1 month ago

michael-rubel commented 1 month ago

If the class wasn't registered manually but exists in the project, can we assume it's a regular registration (non-singleton) when trying to resolve it?

For example:

class MyClass:
    pass

app.container.resolve(MyClass)

^ And this should work without that:

app.container.register(MyClass)

This is how Laravel's Service Container works internally, and I would like to see this feature in punq.

bobthemighty commented 1 month ago

I don't see why not.

bobthemighty commented 1 month ago

Actually, you can already do this.

class MyClass:
    pass

container.instantiate(MyClass)

Is there a reason why that's not sufficient?

michael-rubel commented 1 month ago

@bobthemighty I think it doesn't cover a lot of use cases.

For instance: You cannot use this to instantiate unregistered Pydantic objects. I haven't expected Pydantic support from the container itself, but you can work around this by registering the custom creation factory:

self.container.register(
    service=ClientInput,
    factory=lambda: self.client_input_factory.create(data=body),
)

But the main reason is that we use the container to instantiate classes similarly to other OO languages:

def __init__(
    self,
    client_input_factory: ClientInputFactory,
):
    self.client_input_factory = client_input_factory

It always builds using logic from resolve, so it's currently impossible to type-hint an unregistered dependency via the class constructor.

bobthemighty commented 1 month ago

Okay. I've left this project to rot, so I need to do some work to get it back to health wrt dependencies and builds, but then I'm happy to pick this up. Any chance you could provide a failing test that would work if this behaviour were in place that demonstrates your use case?

michael-rubel commented 1 month ago

@bobthemighty Sure. I'd tinker with that myself, but you have much more experience in Python & know the codebase, so your solution should be more harmonical. I've added a draft PR with a failing test for unregistered constructor dependencies.

Out-of-the-box Pydantic support will require a lot more effort I think, so let's start with traditional container DI where there are no registered dependencies for the class.