Chapter 3.6: Error Handling and Validation
Time to learn: 50 minutes
1. Error Handling: The Spaceship's "Emergency Shields"
Even on the most advanced ship, unforeseen situations can occur:
- Incorrect command from Mission Control: The client sent invalid data.
- Loss of communication with a module: The resource was not found in the database.
- Reactor failure: An internal server error.
Proper error handling is a system of "emergency shields." It prevents the ship from falling apart and instead sends a clear signal to Mission Control about what went wrong.
💡 Space Analogy:
Instead of just sending a "FAILURE!" signal to Mission Control, a good onboard computer would send a structured report:
This allows engineers on Earth to quickly understand the problem and take action.
2. Pydantic Validation: The Built-in "Onboard Computer"
We have already encountered the magic of Pydantic. If you try to create a ship with an incorrect data type (e.g., launch_year
as a string), FastAPI will automatically return a 422 Unprocessable Entity
error with a detailed description of which field failed validation and why.
Example request to POST /spaceships
:
{
"name": "X-Wing",
"type": "Fighter",
"launch_year": "a long time ago", // <-- Invalid type!
"status": "In service"
}
Automatic response from FastAPI:
{
"detail": [
{
"loc": [
"body",
"launch_year"
],
"msg": "value is not a valid integer",
"type": "type_error.integer"
}
]
}
3. Handling "Resource Not Found": The HTTPException
We have already used this in our CRUD operations. HTTPException
is the standard FastAPI way to interrupt the execution of a request and immediately return an error response to the client.
Let's recall the code from GET /spaceships/{ship_id}
:
# main.py
from fastapi import FastAPI, HTTPException # Make sure HTTPException is imported
# ...
@app.get("/spaceships/{ship_id}", response_model=Spaceship, tags=["Spacecraft"])
def get_spaceship(ship_id: int):
ship = db_spaceships.get(ship_id)
if not ship:
# If the ship is not found, "raise" a 404 exception
raise HTTPException(status_code=404, detail=f"Spacecraft with ID {ship_id} not found")
return ship
raise HTTPException(...)
: This call stops the function's execution.status_code=404
: Sets the HTTP status of the response.detail
: The message that will be sent to the client in the JSON response body.
4. Custom Validators: "Special Checks" Before Launch
What if we want to add our own, more complex business logic? For example, to prohibit launching ships named "Death Star."
For this, Pydantic has a powerful tool: validators.
Step 1: Add a validator to the SpaceshipCreate
model
# main.py
from pydantic import BaseModel, Field, validator
class SpaceshipCreate(BaseModel):
name: str = Field(..., min_length=3, max_length=50)
type: str
launch_year: int = Field(..., gt=1950)
status: str
@validator('name')
def name_must_not_be_forbidden(cls, v):
"""Checks that the ship's name is not on the forbidden list."""
if 'Death Star' in v:
raise ValueError('Names like "Death Star" are forbidden by Imperial decree!')
return v.title() # Also, capitalize the name
@validator('name')
: A decorator that "binds" this function to thename
field.cls, v
: The method receives the class itself (cls
) and the field's value (v
).raise ValueError(...)
: If the check fails, we raise a standard Python exception. FastAPI will catch it and turn it into a nice422
error.return v.title()
: If everything is fine, we must return the value. We can even modify it on the fly (for example, standardize its format).
Step 2: Test it
Restart uvicorn
and try to create a ship with the forbidden name via /docs
. You will get a 422
error with your custom message!
5. Global Error Handling: The Station's "Emergency Protocol"
Sometimes you need to catch unexpected errors (like a connection failure to a real database) and return a single, standardized response format.
For this, the @app.exception_handler
decorator is used.
Example: Catching all ValueError
exceptions
# main.py
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
# ...
@app.exception_handler(ValueError)
async def value_error_exception_handler(request: Request, exc: ValueError):
"""
A global handler for all ValueError exceptions
to return a standardized JSON.
"""
return JSONResponse(
status_code=400,
content={"message": f"Invalid data provided: {str(exc)}"},
)
@app.exception_handler(ValueError)
: Tells FastAPI that this function should handle allValueError
exceptions that were not caught earlier.async def ...
: Exception handlers should be asynchronous (async
).JSONResponse
: Allows you to fully control the response body and status.
Now, when our custom validator is triggered, the response will have the more user-friendly format that we defined.
Review Quiz
🚀 Chapter Summary:
You have installed a powerful defense system and emergency protocols on your API ship. It can now:
- 🛡️ Automatically repel "invalid data" attacks using Pydantic.
- 🚨 Properly report missing resources (
404 Not Found
) viaHTTPException
. - ⚙️ Perform "special checks" with custom validators.
- 🧯 Globally catch unexpected failures and provide standardized responses.
Your "hyperdrive" is not only fast, but also incredibly reliable!
📌 Checkpoint:
- Try to create a ship named "Death Star" and ensure you get a
400
error with your custom message (from the global handler).- Try to request
GET /spaceships/999
and ensure you get a404
error.- Try to send a
POST
request withlaunch_year
as a string and ensure you get a422
error.⚠️ If you have errors:
- Make sure all necessary modules (
HTTPException
,validator
,Request
,JSONResponse
) are imported.- Check that the
@validator
and@app.exception_handler
decorators are written without typos.
Congratulations on completing Chapter 3! You have built, launched, and secured a powerful, well-documented API from scratch using FastAPI. You are ready to take on real space missions