Dependency Injection is an important design pattern for building applications. Basically, every major framework implements Dependency Injection in some form or the other. However, a configuration-heavy approach adopted by some frameworks can lead to confusion. But FastAPI Dependency Injection system is pretty straightforward. It has a specially strong focus towards flexibility

In this post, we will explore the dependency injection in FastAPI using simple examples. In case you are new to FastAPI, you can start with this introduction to FastAPI.

1 – What is Dependency Injection?

Dependency Injection is a programming approach.

In this approach, the framework takes care of fulfilling the dependencies of your program. The program simply specifies the things or dependencies it needs to work properly. Based on this specification, the framework (such as FastAPI) will inject those dependencies.

Basically, this is vastly different from an approach where a program first collects all the dependencies, creates their instances and then uses them to carry out its function.

But when do we need dependency injection?

Some basic use-cases of dependency injection are as follows:

  • Sharing of database connections
  • Managing shared logic within an application
  • Authentication and authorization
  • Sharing of repositories across different classes

In other words, the main selling point of dependency injection is to avoid writing repetitive code.

2 – Intro to FastAPI Dependency Injection

FastAPI provides a function known as Depends to handle Dependency Injection.

Let us see it in action.

from fastapi import Depends, FastAPI

app = FastAPI()

async def properties(offset: int = 0, limit: int = 100):
    return {"offset": offset, "limit": limit}

@app.get("/books/")
async def get_books(params: dict = Depends(properties)):
    return params

@app.get("/authors/")
async def get_authors(params: dict = Depends(properties)):
    return params

This is a very simple example. However, it demonstrates the concept quite well.

Basically, we have the async function properties() that accepts a couple of optional parameters as input and returns a dict containing those values. In this example, properties() is a dependency.

Now we need to use the dependency somewhere.

This is done by using Depends keyword. Note that we import Depends from fastapi. In other words, it is part of the core library.

As you can see, we use Depends as a parameter in our path operation function. This is similar to how we use Body and Path. However, Depends is different from Body or Path keywords. It takes a function as input. That function in turn takes parameters just like typical path operation functions. In this case, the function is properties and the parameters are offset and limit.

So what happens when the application receives a request to an endpoint such as /books?

  • FastAPI calls the dependency function properties with the parameters (offset and limit).
  • Get the result from the function. In this case, the result is simply a dict.
  • Finally, assign the result to the params variable in our path operation function.

Important thing is that we are using the same function in /books as well as /authors endpoint. In other words, we are reusing the same dependency in two places. Also, we are simply using Depends to establish the relation. FastAPI takes care of calling the dependency function and providing the results. This is the whole point of Dependency Injection.

3 – FastAPI Dependency Injection using Classes

In the previous approach, we use a dict. However, with dict, we cannot get support features like code completion and static checks.

Generally, we would like to use classes as a mechanism for setting up dependencies. However, Depends needs a callable as input. A callable in Python is anything you can call like a function. Incidentally, we can do so with classes and hence, it is perfectly fine to use them in Depends.

See below example:

from fastapi import Depends, FastAPI

app = FastAPI()

class Properties:
    def __init__(self, offset: int = 0, limit: int= 100):
        self.offset = offset
        self.limit = limit

@app.get("/fetch-books")
async def fetch_books(properties: Properties = Depends(Properties)):
    response = {}
    response.update({"offset": properties.offset})
    response.update({"limit": properties.limit})
    return response

As you can see, we create a class Properties. It contains an __init__ method to create an instance. FastAPI analyzes the parameters of the Properties class and processes them in the same way as the parameters for a path operation function.

In the above syntax, we are repeating Properties a couple of times. FastAPI also provides a shortcut to avoid the same. See below:

@app.get("/fetch-books")
async def fetch_books(properties: Properties = Depends()):
    response = {}
    response.update({"offset": properties.offset})
    response.update({"limit": properties.limit})
    return response

In this case, we leave the arguments for Depends blank. FastAPI will know that it needs to use the Properties class.

4 – Handling Sub-dependencies

We can also create dependencies having sub-dependencies. In fact, the dependency-tree can be as deep as you want. FastAPI will resolve it and do the appropriate thing.

See below example:

from fastapi import Depends, FastAPI

app = FastAPI()

async def default_properties(limit: int):
    if limit == 0:
        return 5
    else:
        return limit

async def determine_properties(offset: int, limit: int = Depends(default_properties)):
    return {"offset": offset, "limit": limit}

@app.get("/fetch-authors")
async def fetch_authors(properties: dict = Depends(determine_properties)):
    return properties

Here, the Depends in path operation function declares the function determine_properties as a dependency. However, determine_properties in turn declares default_properties as a sub-dependency.

In this case, FastAPI will know that it has to first solve default_properties to solve determine_properties. Also, if multiple dependencies have the same sub-dependency, FastAPI will call that sub-dependency only once per request. It will save the return value in cache and use that to serve the other dependants.

5 – Add Dependencies to FastAPI Path Operation Decorators

Many times, a particular path operation has multiple dependencies. In FastAPI, we can handle this scenario by using an optional argument known as dependencies.

Basically, dependencies is a list of Depends().

See below example:

from fastapi import Depends, FastAPI, HTTPException

app = FastAPI()

async def verify_offset(offset: int):
    if offset == 0:
        raise HTTPException(status_code=400, detail="Invalid offset value")

async def verify_limit(limit: int):
    if limit == 0:
        raise HTTPException(status_code=400, detail="Invalid limit value")

@app.get("/fetch-books-with-params", dependencies=[Depends(verify_offset), Depends(verify_limit)])
async def fetch_books_with_params():
    return {"offset": 10, "limit": 10}

As you can see, we have two functions verify_offset and verify_limit. FastAPI will solve these dependencies just like normal dependencies. But, they will not return any values to the path operation function. However, they can raise exceptions if needed.

6 – FastAPI Global Dependencies

FastAPI Dependency Injection also provides facility to specify global dependencies.

FastAPI Global Dependencies can be added the same way as we did with path operation decorators. Instead of decorators, we can add them to the FastAPI application itself.

See below:

from fastapi import Depends, FastAPI, HTTPException

async def verify_offset(offset: int):
    if offset == 0:
        raise HTTPException(status_code=400, detail="Invalid offset value")

async def verify_limit(limit: int):
    if limit == 0:
        raise HTTPException(status_code=400, detail="Invalid limit value")

app = FastAPI(dependencies=[Depends(verify_offset), Depends(verify_limit)])

In this case, the dependencies will be applicable to all path operations in the application.

Conclusion

With this, we have looked at FastAPI Dependency Injection using Depends in quite some detail. All in all, the dependency injection system of FastAPI is simple and intuitive. This is exactly what makes it quite powerful in terms of flexibility.

In future posts, we will look at more complex uses of dependency injection in FastAPI.

If you have any comments or queries about this post, please feel free to mention them in the comments section below.

Categories: BlogFastAPI

Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

6 Comments

Christian · October 28, 2022 at 1:36 pm

Thank a lot for this superb article.

It is clear, progressive and help understanding the whole picture

    Saurabh Dashora · October 29, 2022 at 1:29 pm

    Hi Christian,

    Thanks for the wonderful feedback!

Omar · December 23, 2022 at 10:44 pm

Thanks for this article.

I have a question. The dependency injection confuse me a little bit.

async def verify_offset(offset: int):
if offset == 0:
raise HTTPException(status_code=400, detail=”Invalid offset value”)

async def verify_limit(limit: int):
if limit == 0:
raise HTTPException(status_code=400, detail=”Invalid limit value”)

@app.get(“/fetch-books-with-params”, dependencies=[Depends(verify_offset), Depends(verify_limit)])
async def fetch_books_with_params():
return {“offset”: 10, “limit”: 10}

Let’s take a look a this.

How fastapi knows that in incoming request, this data will go in verify_offset and the other one will go in verify_limit ? At first I would say the data type but in this case both are int.

    Saurabh Dashora · January 1, 2023 at 2:18 am

    Hi, FastAPI looks at the order of the input parameters

seun · April 21, 2023 at 11:11 am

Thanks for the simple in depth articles as it helped me solved an issue.

    Saurabh Dashora · April 21, 2023 at 12:27 pm

    Thanks for the great feedback! Happy to help…

Leave a Reply

Your email address will not be published. Required fields are marked *