FastAPI - Dependencies

  • FastAPI has a very powerful but intuitive Dependency Injection system.

    Introduction

    “Dependency Injection” means, in programming, that there is a way for your code (in this case, your path operation functions) to declare things that it requires to work and use: “dependencies”.

    And then, that system will take care of doing whatever is needed to provide your code with those necessary dependencies (“inject” the dependencies).

    This is invaluable when you need to:

    • Have shared logic (the same code logic again and again).
    • Share database connections.
    • Enforce security, authentication, role requirements, etc.
    • And many other things…

    All these, while minimizing code repetition.

    Function Dependency

    from typing import Annotated
    from fastapi import APIRouter, Depends
    router = APIRouter()
    ## Dependencies
    async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}
    CommonsDep = Annotated[dict, Depends(common_parameters)]
    @router.get("/author")
    async def author_get(commons: CommonsDep):
    return [{"author": id} for id in range(commons["skip"], commons["skip"] + commons["limit"])]
    @router.get("/book")
    async def book_get(commons: Annotated[dict, Depends(common_parameters)]):
    return [{"book": id} for id in range(commons["skip"], commons["skip"] + commons["limit"])]

    Create a dependency, or “dependable”

    Our dependency it’s just the function common_parametersthat can take all the same parameters that a path operation function can take:

    async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}

    In this case, this dependency expects:

    • An optional query parameter q that is a str.
    • An optional query parameter skip that is an int, and by default is 0.
    • An optional query parameter limit that is an int, and by default is 100.

    And then it just returns a dict containing those values.

    Declare the dependency, in the “dependant”

    The same way you use Body, Query, etc. with your path operation function parameters, use Depends with a new parameter:

    @router.get("/author")
    async def author_get(commons: Annotated[dict, Depends(common_parameters)]):
    return [{"author": id} for id in range(commons["skip"], commons["skip"] + commons["limit"])]

    Although you use Depends in the parameters of your function the same way you use Body, Query, etc., Depends works a bit differently.

    • You only give Depends a single parameter.
    • This parameter must be something like a function.
    • You don’t call it directly (don’t add the parenthesis at the end), you pass it as a parameter to Depends().
    • And that function takes parameters in the same way that path operation functions do.

    Whenever a new request arrives, FastAPI will take care of:

    • Calling your dependency (“dependable”) function with the correct parameters.
    • Get the result from your function.
    • Assign that result to the parameter in your path operation function.

    common_parameters

    /book

    /author

    This way you write shared code once, and FastAPI takes care of calling it for your path operations.

    Share Annotated dependencies

    When you need to use the common_parameters() dependency, you have to write the whole parameter with the type annotation and Depends():

    commons: Annotated[dict, Depends(common_parameters)]

    But because we are using Annotated, we can store that Annotated value in a variable and use it in multiple places:

    CommonsDep = Annotated[dict, Depends(common_parameters)]
    @router.get("/book")
    async def book_get(commons: CommonsDep):
    return [{"book": id} for id in range(commons["skip"], commons["skip"] + commons["limit"])]
    Nota

    This is just standard Python, it’s called a “type alias”, it’s actually not specific to FastAPI.

    The dependencies will keep working as expected, and the best part is that the type information will be preserved, which means that your editor will be able to keep providing you with autocompletion, inline errors, etc.

    This will be especially useful when you use it in a large code base where you use the same dependencies over and over again in many path operations.

    Integrated with OpenAPI

    All the request declarations, validations and requirements of your dependencies (and sub-dependencies) will be integrated in the same OpenAPI schema.

    So, the interactive docs will have all the information from these dependencies too.

    Classes as Dependencies

    In the previous example, we were returning a dict from our dependency (“dependable”).

    And we know that editors can’t provide a lot of support (like completion) for dicts, because they can’t know their keys and value types.

    Functions are not the only way to declare dependencies (although it would probably be the more common).

    What makes a dependency

    The key factor is that a dependency should be a “callable.”

    A “callable” in Python is anything that Python can “call” like a function.

    So, if you have an object something (that might not be a function) and you can “call” it (execute it) like:

    something()

    or

    something(some_argument, some_keyword_argument="foo")

    then it is a “callable”.

    Classes as dependencies

    You might notice that to create an instance of a Python class, you use that same syntax.

    For example:

    class Cat:
    def __init__(self, name: str):
    self.name = name
    fluffy = Cat(name="Mr Fluffy")

    In this case, fluffy is an instance of the class Cat.

    And to create fluffy, you are “calling” Cat.

    So, a Python class is also a callable.

    Then, in FastAPI, you could use a Python class as a dependency.

    Then, we can change the dependency “dependable” common_parameters from above to the class Query:

    class CommonQuery:
    def __init__(self, q: str | None = None, skip: int = 0, limit: int = 100):
    self.q = q
    self.skip = skip
    self.limit = limit

    Pay attention to the __init__ method used to create the instance of the class.

    It has the same parameters as our previous common_parameters.

    Use It

    Now you can declare your dependency using this class.

    @router.get("/item")
    async def author_get(query: Annotated[CommonQuery, Depends()]):
    return [{"item": id} for id in range(query.skip, query.skip + query.limit)]

    FastAPI calls the Query class to create an “instance” of that class, and the instance will be passed as the parameter query to your function.

    FastAPI provides a shortcut where the dependency is specifically a class that FastAPI will “call” to create an instance of the class itself.

    So you can do the following:

    query: Annotated[CommonQuery, Depends()

    You use Depends() without any parameter, instead of having to write the full class again inside of Depends(Query)

    Sub-dependencies

    Sub-dependencies

    Pending