Skip to content

provider.Callable not working when being passed to another provider in a Container #704

Closed
@TomaszBorczyk

Description

@TomaszBorczyk

Hi,

I've encountered a problem in our project with Callable providers and nailed the problem down to this scenario.
Namely, when we have a Factory and we want to use it in another Factory, we can do it easily as such:

some_service_factory = providers.Factory(SomeService, a=1)
other_service_factory = providers.Factory(OtherService, service=some_service_factory(b=2))

Doing something similar with Callable provider fails in runtime with TypeError: some_callable() missing 1 required positional argument: 'input_value'. Full code that causes errors is pasted below.

Code is run as python ./main.py and then entering localhost:8888 in the browser.

import functools

import uvicorn
from dependency_injector.wiring import inject, Provide
from fastapi import FastAPI, APIRouter, Depends

from dependency_injector import containers, providers


def some_callable(configurable_parameter, input_value):
    return configurable_parameter + input_value


class Service:
    def __init__(self, callback):
        self.some_callable = callback

    def greet(self):
        return self.some_callable(input_value=" world")


class AppContainer(containers.DeclarativeContainer):
    callable_instance = providers.Callable(some_callable, configurable_parameter="hello")

    service = providers.Singleton(
        Service,
        callback=callable_instance
        # below works
        # callback=functools.partial(some_callable, configurable_parameter="hello")
    )


router = APIRouter()


@router.get("/")
@inject
async def read_root(
        service=Depends(Provide[AppContainer.service])
):
    return {"message": service.greet()}


def create_app():
    container = AppContainer()
    container.wire(modules=[__name__])
    app = FastAPI()
    app.container = container

    app.include_router(router)

    return app


if __name__ == "__main__":
    uvicorn.run(
        f"{__name__}:{create_app.__name__}",
        host="0.0.0.0",
        port=8888,
        log_level="info",
        reload=True,
        factory=True,
    )

Pip freeze output:

anyio==3.6.2
click==8.1.3
dependency-injector==4.41.0
fastapi==0.95.1
h11==0.14.0
idna==3.4
pydantic==1.10.7
six==1.16.0
sniffio==1.3.0
starlette==0.26.1
typing_extensions==4.5.0
uvicorn==0.22.0

As you can see, I want to use below code:

 callable = providers.Callable(some_callable, configurable_parameter="hello")
 service = providers.Singleton(
       Service,
       callback=callabl
        # below work
        # callback=functools.partial(some_callable, configurable_parameter="hello"
    )

It fails with that dependency-injector trace:

File "/Users/tb/home/depenency_injector_fastapi_bug/.venv/lib/python3.10/site-packages/dependency_injector/wiring.py", line 994, in _patched
    return await _async_inject(
  File "src/dependency_injector/_cwiring.pyx", line 53, in _async_inject
  File "src/dependency_injector/providers.pyx", line 225, in dependency_injector.providers.Provider.__call__
  File "src/dependency_injector/providers.pyx", line 3049, in dependency_injector.providers.Singleton._provide
  File "src/dependency_injector/providers.pxd", line 650, in dependency_injector.providers.__factory_call
  File "src/dependency_injector/providers.pxd", line 577, in dependency_injector.providers.__call
  File "src/dependency_injector/providers.pxd", line 445, in dependency_injector.providers.__provide_keyword_args
  File "src/dependency_injector/providers.pxd", line 365, in dependency_injector.providers.__get_value
  File "src/dependency_injector/providers.pyx", line 225, in dependency_injector.providers.Provider.__call__
  File "src/dependency_injector/providers.pyx", line 1339, in dependency_injector.providers.Callable._provide
  File "src/dependency_injector/providers.pxd", line 635, in dependency_injector.providers.__callable_call
  File "src/dependency_injector/providers.pxd", line 608, in dependency_injector.providers.__call
TypeError: some_callable() missing 1 required positional argument: 'input_value'

When I replace providers.Callable with functools.partial it works, but it is definitely not what I want, as this is just a simple example and in real project it can get more complicated, with Callable being provided with different dependencies etc. I use Singleton here, but it might as well be a Factory or other provider.

In the scenario when I need to reuse Callable in the container and provide it with some dependencies, it does not work (as per above error), I cannot use funtools.partial (as it cannot be injected with dependencies), and need to convert the function that I want to be provided to a class that just implements a single method, only for it to possible to use providers.Factory instead of providers.Callable, which I find being a huge antipattern.

I am sure though I am missing something and would love some direction

How can I make it work?

Thanks

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions