The Ultimate FastAPI Tutorial Part 11 - Dependency Injection via FastAPI Depends
In part 11 of the FastAPI tutorial, we'll look at dependency injection in FastAPI
Welcome to the Ultimate FastAPI tutorial series. This post is part 11. The series is a project-based tutorial where we will build a cooking recipe API. Each post gradually adds more complex functionality, showcasing the capabilities of FastAPI, ending with a realistic, production-ready API. The series is designed to be followed in order, but if you already know FastAPI you can jump to the relevant part.
Tutorial Series Contents
Beginner Level Difficulty
Intermediate Level Difficulty
Part 14: Using Docker and Uvicorn to Deploy Our App to IaaS (Coming soon)
Part 15: Exploring the Open Source Starlette Toolbox - GraphQL (Coming soon)
Part 16: Alternative Backend/Python Framework Comparisons (i.e. Django) (Coming soon)
Theory Section - What is Dependency Injection
Dependency injection (DI) is a way for your code functions and/or classes to declare things they need to work.
If you want to get more technical: Dependency injection relies on composition, and is a method for achieving inversion of control.
FastAPI has an elegant and simple dependency injection system. It is my favorite feature of the framework. You can declare dependencies in your path operation functions, i.e. the decorated functions which define your API endpoints. Typical examples of the sorts of dependencies you might want to inject:
- Database connections
- Auth/security requirements (e.g. getting user credentials, checking the user is active, checking user access level)
- Clients for interacting with other services (e.g. AWS services, email marketing services, a CMS)
- Virtually any shared code logic
These dependencies only need to be declared once and then referenced elsewhere in the codebase, so DI makes your code less repetitive and easier to understand. DI is also extremely useful for testing, because you can inject test doubles into your objects which reduces unwieldy monkey patching. We’ll look at an example of this in the second part of the post. Let’s start looking at some examples.
Practical Section 1 - Using DI in FastAPI
To begin, I’ll flag which files have changed in the accompanying example code repo
app directory in this part compared to the previous part:
./app ├── __init__.py ├── api │ ├── __init__.py │ ├── api_v1 │ │ ├── __init__.py │ │ ├── api.py │ │ └── endpoints │ │ ├── __init__.py │ │ ├── auth.py │ │ └── recipe.py ----> UPDATED │ └── deps.py ----> UPDATED ├── clients ----> ADDED │ ├── __init__.py │ └── reddit.py ----> ADDED ├── core │ ├── __init__.py │ ├── auth.py │ ├── config.py │ └── security.py ├── crud │ ├── __init__.py │ ├── base.py │ ├── crud_recipe.py │ └── crud_user.py ├── db │ ├── __init__.py │ ├── base.py │ ├── base_class.py │ ├── init_db.py │ └── session.py ├── models │ ├── __init__.py │ ├── recipe.py │ └── user.py ----> UPDATED ├── schemas │ ├── __init__.py │ ├── recipe.py │ └── user.py ----> UPDATED ├── templates | └── index.html └── tests ----> ADDED │ ├── conftest.py ----> ADDED │ └── api ----> ADDED │ ├── __init__.py │ └── test_recipe.py ----> ADDED ├── backend_pre_start.py ├── initial_data.py └── main.py
So far in the tutorial series, we’ve already started using FastAPI’s dependency injection system for our database session and auth. Let’s revisit these and then look at a new example.
Injecting The Database Session Dependency
app/api/deps.py we have a central location where our dependencies are defined. In previous sections we’ve
defined this function to inject our database session into the path operation functions:
Then we make use of the DB session like so (example taken from
You can see at note (1) in this code snippet the structure for a dependency injection in FastAPI:
- Define a
callable(usually a function, but it can also be a class in rarer cases) which returns or yields*** the instantiated object (or simple value) you wish to inject
- Add a parameter to your path operation function (i.e. the decorated API endpoint function), using the FastAPI
Dependsclass to define the parameter, and passing your function to the
***Note that the typical use-case for a dependency function uses
yield is where you are doing extra steps after
finishing (such as closing a DB connection, clean up, some state mutation).
The dependency is then available for use, as shown at note (2) in the code snippet above, where the database session is passed to the crud utility method (which in turn performs a database query via the session, see the source code).
There is no “registration” (or similar) process for your functions, FastAPI takes care of that for you under the hood. As per the docs it is up to you whether you want your dependency functions to be async or not.
A New Dependency Injection Example - Reddit Client
In part 11, the example code has changed with the addition of a reddit client dependency. The
used in the
recipe/ideas endpoint has been replaced. Instead, we’ve now defined a reddit client in
Let’s break this down, as there are a couple of subtleties to this code. Feel free to skip as the key thing to understand is that it’s a basic reddit HTTP client:
- Whilst most Pythonistas are familiar with the popular requests HTTP client fewer are familiar
with an alternative called httpx, which has a similar clean interface but also elegantly supports async. When
we use the httpx
Clientthis is analagous to requests
Session, i.e. for reusing connections via pooling.
- Little bit of an ugly method which uses
getattrto fish out the appropriate method (
postetc.) on the session, then immediately invokes it with the correct URL.
- Like requests,
raise_for_statusraises an error on any 4xx or 5xx responses (docs)
- Usage of the method described in point 2.
- The data is now returned instead of just mutating the dictionary as in the previous (sub-optimal) function.
Great, now that we have our client, we need to update the
deps.py module to include it:
Nice and simple, we return an instance of the reddit client. The last step is to inject the dependency in our path operation function:
Now the reddit client is available within the path operation function. Elegant, isn’t it? For an idea of what it would take to build this yourself, you can check out my post on Flask-Injector.
The next thing to do is to run the example repo locally.
Follow the setup steps in the readme to install the dependencies and start the app. Then head over to
http://localhost:8001/docs for the
interactive UI. Find the
/api/v1/recipes/ideas endpoint and click Try it out
, then execute`.
You should see a response of this format:
Play with the reddit client code (e.g. the parameters in the request in
reddit.py) to get a feel for how everything hangs
together. Experimentation will teach far better than reading.
There is another major benefit to the dependency injection refactoring of the
/recipes/ideas endpoint, which is in testing. We’ll look at that in the
second-half of this post. First, let’s look at a more complex sub-dependency example.
Using FastAPI Depends Sub-Dependencies - Auth Example
Your dependencies can also have dependencies. FastAPI takes care of solving the hierarchy of dependencies. This adds
significant additional power to the FastAPI DI system. In the part 10 of the tutorial
we saw the
get_current_user auth dependency:
This makes use of the FastAPI’s
it, so it can be injected in API endpoints.
But what if we want to increase the specificity of our authorization? Let’s look at using a sub-dependency to create a callable which restricts the returned users to superusers:
Two things to breakdown:
- We pass a dependency to this function (thereby creating a sub-dependency). In this case it is the original
get_current_userfunction. This will make the
currentuser instance available for use in the function.
- Then we perform an additional check (using the data access utility
CRUDUserclass methods) for whether the user is a superuser.
Let’s see this in the endpoint code:
We’ve now updated this endpoint so that any attempt to call it from a user that is not a superuser will be rejected.
You can test this out by running the example repo, then creating a new user (making them a superuser) via the
/api/v1/auth/signup endpoint (being sure to update the
superuser field to
Once you’ve created the superuser, you can login via the
Authorize button in the top right of the OpenAPI interactive
Without being a logged in superuser, attempts to use the
/ideas/async endpoint will fail (note, the synchronous endpoint
will still work without auth as we have not added the dependency to that path operation function).
You could use this sub-dependency approach to build out complex auth logic, as shown in this diagram:
And naturally, sub-dependencies are not just limited to auth, you can use them in numerous ways.
Practical Section 2 - Dependency Injection Implications for Testing
In this part we added the first test to our example app. The test runner is pytest.
If you’re not familiar with pytest, checkout this free pytest introduction lecture from my testing and monitoring ML models course.
For the purposes of this tutorial, the key thing to understand is that test fixtures in pytest are defined by convention
in a file called
conftest.py. So in our loan test file
app/tests/api/test_recipe.py we have a relatively
short bit of code hiding a lot more:
The key thing to note at comment (1) where the test takes the
client as its first argument.
client is a test
fixture defined in
There is a lot happening here, let’s break it down by the comment numbers:
- We use the
pytestfixture decorator to define a fixture
- We access the FastAPI built-in test client via a context manager so we can easily perform clean-up (see 7)
- We use the FastAPI app dependency_overrides to replace dependencies. We replace what the selected
dependency (in this case
get_reddit_client) callable is, pointing to a new callable which will be used in testing (in this case
- We make use of the Python standard library unittest mock
MagicMock(docs for those unfamiliar)
- We specify the return value of a particular method in our mocked reddit client (
get_reddit_top), here it will return dummy data
yieldthe modified client
- We perform clean up on the client, reverting the dependencies
This is an illustration of the power of dependency injection in a testing context. Whilst libraries like request-mock would also allow you to replace the return value of a particular HTTP call, we can also apply this approach to any callable, whether it’s:
- Interacting with a database
- Sending emails via SMTP
- Working with other protocols (e.g. ProtoBuf)
- Simply calling complex code we don’t need to worry about for a particular test.
All with fairly minimal setup, no monkey-patching, and fine-grained control. This is part of a broader pattern in software development: composition over inheritance (useful Python overview here).
Hopefully this shows the power and versatility of FastAPI’s dependency injection system. Learning the value of this approach will save you a lot of pain.
Continue Learning FastAPI
Next we’re going to look at adding a more sophisticated React frontend to our Recipe app:
coming in late August