FastAPI by Example: Pydantic Validation Rules
Sometimes built-in types aren't enough. This code example demonstrates how to define custom validation logic using the `@field_validator` decorator to enforce complex business rules.
Code
from fastapi import FastAPI
from pydantic import BaseModel, field_validator
app = FastAPI()
class Order(BaseModel):
item_name: str
quantity: int
price_per_unit: float
@field_validator('item_name')
@classmethod
def name_must_be_capitalized(cls, v: str) -> str:
if not v[0].isupper():
raise ValueError('Item name must start with a capital letter')
return v
@field_validator('quantity')
@classmethod
def quantity_must_be_positive(cls, v: int) -> int:
if v <= 0:
raise ValueError('Quantity must be a positive integer')
return v
@app.post("/orders/")
async def create_order(order: Order):
total = order.quantity * order.price_per_unit
return {"item": order.item_name, "total_cost": total}Explanation
Pydantic allows developers to define custom validation logic using the @field_validator decorator. This is essential when data requirements go beyond simple type checks or standard constraints like string length. By defining a class method decorated with @field_validator, you can execute arbitrary Python code to verify the value of a specific field. If the value is invalid, raising a ValueError or AssertionError will cause Pydantic to reject the request and FastAPI to return a 422 error. Using custom validators provides several advantages:
- Automatic Validation: Eliminates manual checks by validating data against defined models.
- Type Safety: Ensures data integrity and improves developer experience with IDE support.
- Reduced Boilerplate: Minimizes repetitive validation code, allowing focus on business logic.
For more complex scenarios involving multiple fields, Pydantic offers model-level validators. However, field validators are the primary tool for ensuring individual data points meet specific business rules, such as checking if a username is unique or if a quantity is positive. This keeps the validation logic encapsulated within the model, ensuring that any part of the application using this model adheres to the same rules.
Code Breakdown
@field_validator('item_name') marks the method as a validator for the item_name field.if not v[0].isupper(): checks if the first letter of the name is uppercase.raise ValueError(...) triggers a validation error if the condition is not met.return v returns the validated (and potentially modified) value.
