Description
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