1

I have this small MCVE FastAPI application which works fine as expected:

# run.py
import uvicorn
from fastapi import FastAPI, Depends

app = FastAPI()


###########

def dep_with_arg(inp):
    def sub_dep():
        return inp
    return sub_dep


@app.get("/a")
def a(v: str = Depends(dep_with_arg("a"))):
    return v


###########


def dep_without_arg():
    return "b"


@app.get("/b")
def b(v: str = Depends(dep_without_arg)):
    return v


###########


def main():
    uvicorn.run(
        "run:app",
        host="0.0.0.0",
        reload=True,
        port=8000,
        workers=1
    )


if __name__ == "__main__":
    main()

notice the difference between the dependencies of the /a and /b endpoints. in the /a endpoint, I have created a dependency that accepts an argument. both endpoints are working as expected when I call them.

I now try to test the endpoints while overriding the dependencies as below:

from run import app, dep_without_arg, dep_with_arg
from starlette.testclient import TestClient


def test_b():
    def dep_override_for_dep_without_arg():
        return "bbb"

    test_client = TestClient(app=app)
    test_client.app.dependency_overrides[dep_without_arg] = dep_override_for_dep_without_arg
    resp = test_client.get("/b")
    resp_json = resp.json()
    assert resp_json == "bbb"


###########


def test_a_method_1():
    def dep_override_for_dep_with_arg(inp):
        def sub_dep():
            return "aaa"

        return sub_dep

    test_client = TestClient(app=app)
    test_client.app.dependency_overrides[dep_with_arg] = dep_override_for_dep_with_arg
    resp = test_client.get(
        "/a",
    )
    resp_json = resp.json()
    assert resp_json == "aaa"


def test_a_method_2():
    def dep_override_for_dep_with_arg(inp):
        return "aaa"

    test_client = TestClient(app=app)
    test_client.app.dependency_overrides[dep_with_arg] = dep_override_for_dep_with_arg
    resp = test_client.get(
        "/a",
    )
    resp_json = resp.json()
    assert resp_json == "aaa"


def test_a_method_3():
    def dep_override_for_dep_with_arg(inp):
        return "aaa"

    test_client = TestClient(app=app)
    test_client.app.dependency_overrides[dep_with_arg] = dep_override_for_dep_with_arg("aaa")
    resp = test_client.get(
        "/a",
    )
    resp_json = resp.json()
    assert resp_json == "aaa"


def test_a_method_4():
    def dep_override_for_dep_with_arg():
        return "aaa"

    test_client = TestClient(app=app)
    test_client.app.dependency_overrides[dep_with_arg] = dep_override_for_dep_with_arg
    resp = test_client.get(
        "/a",
    )
    resp_json = resp.json()
    assert resp_json == "aaa"

test_b passes as expected but all the other tests fail:

FAILED test.py::test_a_method_1 - AssertionError: assert 'a' == 'aaa'
FAILED test.py::test_a_method_2 - AssertionError: assert 'a' == 'aaa'
FAILED test.py::test_a_method_3 - AssertionError: assert 'a' == 'aaa'
FAILED test.py::test_a_method_4 - AssertionError: assert 'a' == 'aaa'

How should I override dep_with_arg in the example above?


related question: Overriding FastAPI dependencies that have parameters

Amin Ba
  • 1,603
  • 1
  • 13
  • 38
  • Have you tried wrapping the dependency function in a lru cache decorator (`functools.lru_cache`) ? Since you'll get a new function each time now, there is no match between what is being overridden and what you're trying to override - but if the same function gets returned every time for the same parameter, it might work as expected – MatsLindh Jul 30 '23 at 06:53
  • 3
    As I suggested on one of your other posts, why don't you just create the dependency function globally, and then use them? So `dep_a = dep_with_arg("a")` etc, and then using them like `Depends(dep_a)` etc? Then you can easily override: `app.dependency_overrides[dep_a] = dep_override_for_dep_with_arg`. – M.O. Jul 30 '23 at 10:15
  • @M.O. your solution is working and I am going to use it. I don't even need to define it globally. I just import them and they work – Amin Ba Jul 30 '23 at 11:42
  • @M.O. But still I see it as a limitation. If I want to dynamically define dependencies and generate many of these dependencies on the fly, then it is a limitation. – Amin Ba Jul 30 '23 at 23:54
  • In that case, see if the solution @MatsLindh proposes works (decorating your dependency function with `@functools.lru_cache`). That should mean that calling `dep_with_arg("a")` twice returns the exact same object. Ib your test, you'd then do ` test_client.app.dependency_overrides[dep_with_arg("a")] = dep_override_for_dep_with_arg`. – M.O. Jul 31 '23 at 10:34
  • You can also make a helper function that keeps a dictionary of all the resolved functions, and then return those - but that means that you've really just implemented your own cache in the same way as lru_cache does for you (except for the lru part). – MatsLindh Jul 31 '23 at 14:49
  • @M.O. I think you have not understood my concern. I am saying suppose I want to create dep_with_arg("a"), dep_with_arg("b"), ...., dep_with_arg("x"), dep_with_arg("xyza"), dep_with_arg("an-arbitrary-name"). caching will not help me here. caching is helpful if I want to create any of them like dep_with_arg("a") more than once – Amin Ba Jul 31 '23 at 16:10
  • @AminBa I understand what you want. The issue here is that `dep_with_arg` is **not** used as a dependency, its return value is. Therefore, you can't use it in `dependency_overrides`, because that simply maps one function to another. – M.O. Jul 31 '23 at 17:03
  • That's why caching helps, it makes `dep_with_arg` return the same object when called with the same argument, so that `dependency_overrides` can map it correctly. – M.O. Jul 31 '23 at 17:10
  • I understand you want to override all usages of `dep_with_arg` at once, and I'm not sure that's possible. You might be able to do something clever by [subclassing `defaultdict`](https://stackoverflow.com/a/2912455/11612918) and replacing `dependency_overrides`, but you'll have to investigate that. – M.O. Jul 31 '23 at 17:14
  • You might also be able to get it working by refactoring everything inside `sub_dep` to a separate function, and then patching that with `unittest.mock.patch`. – M.O. Jul 31 '23 at 17:23
  • @M.O. but I am not using the build fastapi's dependency override functionality which is a powerful tool and better tha monkey patching – Amin Ba Jul 31 '23 at 17:54

0 Answers0