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
Introduction
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.
Code
Project github repo directory for this part of the tutorial
Tutorial Series Contents
Optional Preamble: FastAPI vs. Flask
Beginner Level Difficulty
Part 1: Hello World
Part 2: URL Path Parameters & Type Hints
Part 3: Query Parameters
Part 4: Pydantic Schemas & Data Validation
Part 5: Basic Error Handling
Part 6: Jinja Templates
Part 6b: Basic FastAPI App Deployment on Linode
Intermediate Level Difficulty
Part 7: Setting up a Database with SQLAlchemy and its ORM
Part 8: Production app structure and API versioning
Part 9: Creating High Performance Asynchronous Logic via async def
and await
Part 10: Authentication via JWT
Part 11: Dependency Injection and FastAPI Depends
Part 12: Setting Up A React Frontend
Part 13: Using Docker, Uvicorn and Gunicorn to Deploy Our App to Heroku
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)
Post Contents
Theory Section - What is Dependency Injection
Practical Section 1 - Using DI in FastAPI
Practical Section 2 - Dependency Injection Implications for Testing
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
In 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 app/api/api_v1/endpoints/recipe.py
):
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
Depends
class to define the parameter, and passing your function to theDepends
class.
***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 get_reddit_top
function
used in the recipe/ideas
endpoint has been replaced. Instead, we’ve now defined a reddit client in app/clients/reddit.py
:
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
Client
this is analagous to requestsSession
, i.e. for reusing connections via pooling. - Little bit of an ugly method which uses
getattr
to fish out the appropriate method (get
,post
etc.) on the session, then immediately invokes it with the correct URL. - Like requests,
raise_for_status
raises 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 OAuth2PasswordBearer
wrapping
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_user
function. This will make thecurrent
user instance available for use in the function. - Then we perform an additional check (using the data access utility
CRUDUser
class 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 true
)
Once you’ve created the superuser, you can login via the Authorize
button in the top right of the OpenAPI interactive
UI:
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 conftest.py
:
There is a lot happening here, let’s break it down by the comment numbers:
- We use the
pytest
fixture 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 caseoverride_reddit_dependency
) - 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 - We
yield
the 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