The Ultimate FastAPI Tutorial Part 4 - Pydantic Schemas
In part 4 of the FastAPI tutorial, we'll look at an API endpoint with Pydantic validation
Introduction
Welcome to the Ultimate FastAPI tutorial series. This post is part 4. 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
Introduction to Pydantic
Practical Section - Using Pydantic with FastAPI
Introduction to Pydantic
Pydantic describes itself as:
Data validation and settings management using python type annotations.
It’s a tool which allows you to be much more precise with your data structures. For example, up until now we have been relying on a dictionary to define a typical recipe in our project. With Pydantic we can define a recipe like this:
In this simple example, the Recipe
class inherits from the Pydantic BaseModel
, and we can define each of its
expected fields and their types using standard Python type hints.
As well as using the typing
module’s standard types,
you can use Pydantic models recursively like so:
When you combine these capabilities, you can define very complex objects. This is just scratching the surface of Pydantic’s capabilities, here is a quick summary of its benefits:
- No new micro-language to learn (which means it plays well with IDEs/linters)
- Great for both “validate this request/response data” and also loading config
- Validate complex data structures - Pydantic offers extremely granular validators
- Extensible - you can create custom data types
- Works with Python data classes
- It’s very fast
Practical Section - Using Pydantic with FastAPI
If you haven’t already, go ahead and clone the example project repo
See the README
file for local setup.
In the app/main.py
file, you will find the following new code:
The Recipe
response model is imported from a new schemas.py
file. Let’s look the relevant part of the
app/schemas.py
file:
Let’s break this down:
- Our path parameter endpoint
/recipe/{recipe_id}
, which we introduced in part 2 has been updated to include aresponse_model
field. Here we define the structure of the JSON response, and we do this via Pydantic. - The new
Recipe
class inherits from the pydanticBaseModel
, and each field is defined with standard type hints… - …except the
url
field, which uses the PydanticHttpUrl
helper. This will enforce expected URL components, such as the presence of a scheme (http or https).
Next, we’ve updated the search endpoint:
Now let’s look at app/schemas.py
1 We’ve added a response_model RecipeSearchResults
to our /search
endpoint
Notice the response format matches the schema (if it did not, we’d get a Pydantic validation error).
2 We bring in the FastAPI Query
class, which allows us add additional validation
and requirements to our query params, such as a minimum length. Notice that because we’ve set the
example
field, this shows up on the docs page when you “Try It”
3 The RecipeSearchResults
class uses Pydantic’s recursive capability to define
a field that refers to another Pydantic class we’ve previously defined, the Recipe
class.
We specify that the results
field will be a Sequence
(which is an iterable with support
for len
and __getitem__
) of Recipes
.
Having done all that (and followed the README
setup instructions), you can run
the code in the example repo with this command: poetry run ./run.sh
Navigate to localhost:8001/docs
Give the endpoint a try:
- Expand the GET endpoint by clicking on it
- Click on the “Try It Out” button
- Enter the value “chicken” for the keyword
- Press the large “Execute” button
- Press the smaller “Execute” button that appears
Creating a POST endpoint
Another addition we’ve made to our API is ability to create new recipes.
This is done via a POST request. Here is the updated code in app/main.py
:
And here is the updated app/schemas.py
code:
A few key points to note:
- To set the function to handle POST requests, we just tweak our
api_router
decorator. Notice that we’re also setting the HTTP status_code to 201, since we are creating resources. - The
recipe_in
field is the POST request body. By specifying a Pydantic schema, we are able to automatically validate incoming requests, ensuring that their bodies adhere to our schema. - To persist the created recipe, we’re doing a primitive list append. Naturally, this is just for a toy example and won’t persist the data when the server is restarted. Later in the series, we will cover databases.
- The
RecipeCreate
schema contains a new field,submitter_id
, so we distinguish it from theRecipe
schema.
Be sure to try creating some new recipes as you run the app locally via the interactive documentation.
Continue Learning FastAPI
In the next part of the tutorial, we’ll cover more basic error handling for our example project endpoints. Go to part 5