Chapter 3.3: Data Models with Pydantic
Time to learn: 50 minutes
1. Pydantic: The "Digital Blueprint" of a Spaceship
Imagine you are building a spaceship. You can't just weld pieces of metal together randomly. You need a detailed blueprint that defines:
- The ship's name (type:
string
, max length: 50 characters) - The launch year (type:
integer
) - Whether it has a hyperdrive (type:
yes/no
)
Pydantic is a library that allows you to create such "digital blueprints" for your data in Python. In FastAPI, it performs three key functions:
- Structure Declaration: Clearly describes the fields your data consists of.
- Data Validation: Automatically checks if incoming data conforms to the blueprint.
- Documentation: FastAPI uses these blueprints to create detailed and interactive documentation.
💡 Space Analogy: A Pydantic model is the technical passport of an object. Any "cargo" (data) arriving at the station must match the specifications in the passport. If not, the onboard computer (Pydantic) will reject it.
2. Creating the First Blueprint: The Spaceship
Model
Let's create a model that will describe our spacecraft.
Step 1: Import BaseModel
from Pydantic
Pydantic is already installed with fastapi[all]
. We just need to import the base class for our models.
Add this to the top of main.py
, next to the other imports:
Step 2: Describe the Spaceship
model
Create a class that inherits from BaseModel
. Inside the class, define the fields and their types using standard Python type hints.
Add this code to main.py
(you can place it after the imports):
class Spaceship(BaseModel):
"""
The technical passport (model) of a spaceship.
"""
name: str
type: str
launch_year: int
status: str
Spaceship
must have four fields with the specified types.
3. Applying the Model: Improving Our Endpoints
Now let's use our new model to make the API "smarter."
A. Model as a Response (Response Model)
We can tell FastAPI that our endpoint should return data that conforms to the Spaceship
model. This ensures that the response will always have the correct structure.
Modify the /spaceships/{ship_id}
endpoint as follows:
# main.py
# ... code with db_spaceships and the Spaceship model ...
# Use `response_model` to specify the "blueprint" for the response
@app.get("/spaceships/{ship_id}", response_model=Spaceship)
def get_spaceship(ship_id: int):
"""
Returns data about a ship, conforming to the Spaceship model.
"""
ship = db_spaceships.get(ship_id)
return ship
response_model=Spaceship
: We are telling FastAPI: "The response of this function must conform to the Spaceship
structure. Filter out any extra fields and make sure the types are correct."
What does this give us?
- Data Filtering: If
db_spaceships
had extra fields (e.g.,"secret_code"
), they would not be included in the final JSON. - Structure Guarantee: The API client can be sure that it will always receive a response in the expected format.
- Documentation: The
/docs
page will now show a precise example of the response (Example Value).
B. Models for Collections
What about the /spaceships
endpoint, which returns a list of ships? For this, we need to use List
from the typing
module.
Modify the imports and the /spaceships
endpoint:
# main.py at the top
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List # <-- Import List
# ... code ...
# Specify that the response is a list (List) of objects of type Spaceship
@app.get("/spaceships", response_model=List[Spaceship])
def get_spaceships():
"""
Returns a list of ships. Each item in the list
is validated against the Spaceship model.
"""
# Pydantic cannot work with a dictionary whose keys are IDs.
# We convert our dictionary to a simple list.
return list(db_spaceships.values())
response_model=List[Spaceship]
: We are specifying that the response will be a list where each element is an object conforming to theSpaceship
model.return list(db_spaceships.values())
: An important change! Pydantic expects an iterable object (a list), not a dictionary where the keys are IDs. We convert the values of our "DB simulator" into a list.
4. Checking the Improved API
Make sure the uvicorn
server is running with --reload
.
- Check
http://127.0.0.1:8000/spaceships
: The response is now a JSON array, not an object. This is a more correct and standard structure for collections. - Check
http://127.0.0.1:8000/spaceships/1
: The response has not changed, but it is now guaranteed to conform to the model. - Take a look at
/docs
: In the "Schemas" section at the bottom of the page, yourSpaceship
model has appeared. And the example responses for the endpoints now display a beautiful, structured data schema.
5. Advanced Validation: The "Onboard Computer" in Action
Pydantic can do much more than just check types.
Let's add validation to our Spaceship
model:
from pydantic import BaseModel, Field
class Spaceship(BaseModel):
name: str = Field(..., min_length=3, max_length=50, description="The name of the ship")
type: str
launch_year: int = Field(..., gt=1950, description="Launch year must be after 1950")
status: str
Field(...)
: Used to add additional validation rules....
(Ellipsis): Means that the field is required.min_length
,max_length
: Constraints for a string.gt
: "Greater Than".
Although we are not yet creating new ships (that will be in the next chapter), these rules will already be reflected in the documentation and will work when we implement POST
requests.
Review Quiz
🚀 Chapter Summary:
You have designed the "digital blueprints" for your API's data. Now it not only works, but it works predictably and reliably.
- 📝 A
Spaceship
model has been created using Pydantic. - 🛡️ The API now validates and filters outgoing data using
response_model
. - 📊 The documentation has become much more informative, showing exact data schemas.
The blueprints are ready and approved! In the next chapter, we will move from reading data to creating it—implementing full CRUD operations for our fleet.
📌 Checkpoint:
- Make sure the
Spaceship
model schema has appeared in/docs
.- Check that the
/spaceships
endpoint now returns a JSON array ([...]
) and not an object ({...}
).- Make sure there are no syntax errors in the code after adding the models.
⚠️ If you have errors:
NameError: name 'BaseModel' is not defined
: Check that you have importedBaseModel
frompydantic
.NameError: name 'List' is not defined
: Check that you have importedList
fromtyping
.- The response for
/spaceships
is empty ([]
): Make sure you have changedreturn db_spaceships
toreturn list(db_spaceships.values())
.