python-dependency-injector
python-dependency-injector copied to clipboard
how to inject FastAPI bearer instances into Depends
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
from third_party import auth_factory
@app.get("/foo")
def foo(user = Depends(auth_factory(scope="read", extractor = some_token_extractor ))):
doSomething()
the oauth_factory produces as callable with fastapi know signature
def user_checker(oauth_header:HTTPAuthorizationCredentials = Security(bearer))
...
return user
My problem now is when I want the some_token_extractor be provided by python-dependency-injector
something like this does not work:
user_detail_extractor = Provide[Container.user_detail_extractor]
@app.get("/with_di")
def with_di(user = Depends(auth_factory(scope="read", extractor = user_detail_extractor ))):
return {
"user": user["username"]
}
class Container(DeclarativeContainer):
user_detail_extractor = providers.Callable(some_di_extractor, some_config="bla")
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
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
Dependslooks for an instance ofSecurityat 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?