Open mxab opened 3 years ago
Hi @mxab , thanks for providing an example. I'll take a look.
Does this in general not work or what is it I'm missing
Dependency Injector can not introspect arguments of Depends(auth_factory(...))
. It expects Depends
to contain Provide
marker. Something you could try is to make injection directly to the auth_factory
here https://github.com/mxab/fastapi-di-depends-issue/blob/main/fastapi_di_depends_issue/third_party.py#L20
I think for now I was able to realise to do the DI part via a class that implments __call__
and using an instance of this in the Depends call.
Thanks!
Hi @rmk135, I think I'm still facing the problem on how to marry this. I have a simpler show case now just with FastAPI's Bearers classes
Some container:
class Container(DeclarativeContainer):
config = providers.Configuration()
bearer = providers.Singleton(HTTPBasic, auto_error=config.secured.as_(bool))
The application:
app = FastAPI()
#### working
fixed_bearer = HTTPBasic()
@app.get("/fixed")
def fixed(user: HTTPBasicCredentials = Depends(fixed_bearer)):
return {"username" : user.username}
#### not working
di_bearer = Provide[Container.bearer]
@app.get("/with_di")
@inject
def with_di(user: HTTPBasicCredentials = Depends(di_bearer)):
return {"username" : user.username if user else None}
the second handler does inject the bearer itself and not the the bearer's __call__
method provides
This makes partially sense for me, but I'm still wondering if there is a way to get this to work
This is the failing test https://github.com/mxab/fastapi-di-depends-issue/blob/main/tests/test_fastapi_di_depends_issue.py#L31
As a workarround I can use it like this:
@inject
async def workarround(
request: Request, di_bearer=Provide[Container.bearer]
) -> HTTPBasicCredentials:
return await di_bearer(request)
@app.get("/with_di_workarround")
def with_di_workarround(user: HTTPBasicCredentials = Depends(workarround)):
return {"username": user.username if user else None}
But it's not pretty :)
As long as I don't type the di_bearer in the signature, otherwise FastAPI goes mad :)
async def workarround(
request: Request, di_bearer:HttpBasic=Provide[Container.bearer]): ... # di_bearer:HttpBasic -> boom
ok the problem with the workarround is also that it breaks the open api endpoin :/
def test_openapi(client: TestClient):
resp = client.get("/openapi.json")
resp.raise_for_status()
assert resp.status_code == 200
results in error:
TypeError: Object of type 'Provide' is not JSON serializable
ok forgot the Depends
@inject
async def workarround(
request: Request, di_bearer: HTTPBasic = Depends(Provide[Container.bearer])
) -> HTTPBasicCredentials:
return await di_bearer(request)
So the only question left is if there is a nice way as the workarround
function ?
I think part of the trouble stems from the fact that Depends
looks for an instance of Security
at compile time, before anything is injected. I believe it would get an instance of whatever Provide[Container.bearer]
returns, which is not going to be an instance of fastapi.security.base.SecurityBase
.
Yeah also assumed that this is kind of the case. Thank you for pointing out where this check is happening
I have yet to work out how to inject a callable dependency
class MyDep:
def __call__(self, request: Request) -> str:
return "Hello"
@app.get()
@inject
def my_route(dep_val: str = Depends(Provide[container.my_dep])):
return dep_val # should return "Hello"
I believe this is a similar issue. Using the workaround method will work, I'm sure but it is rather messy. FastAPI leaves much to be desired when you want something more than a very rudamentary CRUD app. Unfortunately I don't think this can be fixed without significant rework of FastAPI's "dependency" system. Maybe I'll just go back to .net 😝
Some ideas... If you override the signature of the method to be dep_val: str = Depends(resolved_instance)
i.e. after dependency-injectors resolution but before FastAPI's dependency resolution it may work? Super hacky... And may have its own problems if e.g. you don't want the call method to be invoked automatically...
If FastAPI had a dependency resolve hook that would be neat.
Maybe it is possible, I'm still learning the internals of both libraries.
How does dependency_overrides_provider
https://github.com/tiangolo/fastapi/blob/b8c4149e89d1c97d204ac3f965e6144e3dc126a9/fastapi/routing.py#L442 work? Seems undocumented
FastAPI leaves much to be desired when you want something more than a very rudamentary CRUD app. Unfortunately I don't think this can be fixed without significant rework of FastAPI's "dependency" system.
I agree 100%. So much so that I created this discussion in FastAPI project. If there was a better story with starlette+pydantic I would probably just use that and try to figure out the apidocs. My ideal combo would be starlette+pydantic+python-dependency-injector+openapi docs.
Edit: Removed mention of my experiment which doesn't actually hook in any deeper than already outlined in the docs.
I ended up with something like this:
class ApplicationContainer(containers.DeclarativeContainer):
partial_service = providers.Factory(Service, kw="foo")
@inject
def service(fastapi_dep=Depends(FastAPI_dependency), service=Depends(Provider[ApplicationContainer.partial_service])):
return service(fastapi_dep)
ApplicationContainer.service = service
Later can be used as:
@router.post("/route")
@inject
async def handler(service=Depends(ApplicationContainer.service)):
print(service)
I think part of the trouble stems from the fact that
Depends
looks for an instance ofSecurity
at compile time, before anything is injected. I believe it would get an instance of whateverProvide[Container.bearer]
returns, which is not going to be an instance offastapi.security.base.SecurityBase
.
@adriangb You are absolutely right. Provide[Container.bearer]
returns an instance of dependency_injector.wiring.Provide
so FastAPI doesn't consider this dependency as a "Security" dependency and doesn't do its magic of creating the relevant OpenAPI definitions, etc.
@rmk135 It seems Dependency Injector is mostly geared towards injecting at runtime, while FastAPI requires security dependencies to be present at compile/startup time in order for it to work properly. Or is there a feature/workaround I'm missing?
Hi, I'm currently struggeling on how to get this to work.
I have a third party "factory method" that provides me with a method I can use in FastAPI's Depends resolver
the
oauth_factory
produces as callable with fastapi know signatureMy problem now is when I want the
some_token_extractor
be provided bypython-dependency-injector
something like this does not work:
I have a full demo setup here https://github.com/mxab/fastapi-di-depends-issue/tree/main/fastapi_di_depends_issue
but I cannot get this test work: https://github.com/mxab/fastapi-di-depends-issue/blob/main/tests/test_fastapi_di_depends_issue.py#L27
Does this in general not work or what is it I'm missing
Thanks very much in advance