FastAPI heavily relies on Pydantic for request validation. Pydantic is a data validation and settings management library that uses Python type hints to validate data. When a request comes in, FastAPI automatically validates the data against the defined Pydantic models.
FastAPI uses dependency injection to manage dependencies and can also use it for validation. Dependencies can be used to perform additional validation on the request data before it reaches the endpoint function.
Pydantic allows us to define custom validators. These validators can be used to perform complex validation logic that cannot be achieved with simple type hints.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
price: float
is_offer: bool = None
@app.post("/items/")
async def create_item(item: Item):
return item
In this example, the Item
Pydantic model is used to validate the request data. If the data does not match the defined types, FastAPI will automatically return a 422 Unprocessable Entity response.
from fastapi import FastAPI, Depends, Query
app = FastAPI()
def validate_query_param(q: str = Query(..., min_length=3)):
return q
@app.get("/items/")
async def read_items(q: str = Depends(validate_query_param)):
return {"q": q}
Here, the validate_query_param
function is a dependency that validates the q
query parameter. If the length of q
is less than 3, FastAPI will return an error.
from fastapi import FastAPI
from pydantic import BaseModel, validator
app = FastAPI()
class User(BaseModel):
username: str
age: int
@validator('age')
def validate_age(cls, value):
if value < 0:
raise ValueError('Age cannot be negative')
return value
@app.post("/users/")
async def create_user(user: User):
return user
The validate_age
method is a custom validator that checks if the age
is non - negative.
You can combine multiple validators to perform comprehensive validation. For example, you can use Pydantic models along with custom validators and dependency injection.
from fastapi import FastAPI, Depends, Query
from pydantic import BaseModel, validator
app = FastAPI()
def validate_query_length(q: str = Query(..., min_length=2)):
return q
class Item(BaseModel):
name: str
price: float
@validator('price')
def validate_price(cls, value):
if value < 0:
raise ValueError('Price cannot be negative')
return value
@app.post("/items/")
async def create_item(item: Item, q: str = Depends(validate_query_length)):
return {"item": item, "q": q}
Pydantic can easily handle nested data validation. You can define nested Pydantic models to validate complex data structures.
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Address(BaseModel):
street: str
city: str
zip_code: str
class User(BaseModel):
name: str
address: Address
@app.post("/users/")
async def create_user(user: User):
return user
It is a good practice to keep the validation logic separate from the business logic. This makes the code more modular and easier to maintain. For example, use dependencies for validation and keep the endpoint functions focused on the actual business operations.
When defining custom validators, use descriptive error messages. This helps the API consumers understand what went wrong with their requests.
Write unit tests for your validation logic. This ensures that the validation works as expected and helps catch any issues early in the development process.
Advanced request validation techniques in FastAPI provide a powerful way to handle complex validation scenarios. By leveraging Pydantic models, dependency injection, and custom validators, you can ensure that the data received by your API is valid and meets the required criteria. Following common and best practices will help you write clean, maintainable, and robust API code.