FastAPI Pydantic Introduction
What is Pydantic?
Pydantic is a data validation and settings management library using Python type annotations. It's one of the core components that makes FastAPI powerful, efficient, and developer-friendly. When working with FastAPI, Pydantic allows you to define how data should be in pure, standard Python; it will then validate your data and convert it to the appropriate type.
Why Use Pydantic with FastAPI?
Before diving into how Pydantic works, let's understand why it's such an essential part of FastAPI:
- Type Safety: Helps catch errors during development instead of at runtime.
- Automatic Validation: Validates incoming request data against your defined schemas.
- Documentation: Automatically generates API documentation from your models.
- Serialization/Deserialization: Converts between Python objects and JSON seamlessly.
Installing Pydantic
Pydantic is installed automatically when you install FastAPI, but you can also install it separately:
pip install pydantic
Basic Pydantic Models
Let's start with a simple example to understand how Pydantic models work:
from pydantic import BaseModel
class User(BaseModel):
id: int
name: str
email: str
is_active: bool = True # Default value
tags: list[str] = [] # Default empty list
In this example:
User
is a Pydantic model that inherits fromBaseModel
- We define fields with their types using Python's type annotations
- We can provide default values for fields
Creating an Instance
# Create a user with data
user = User(id=1, name="John Doe", email="[email protected]")
print(user)
Output:
id=1 name='John Doe' email='[email protected]' is_active=True tags=[]
Data Validation
Pydantic will automatically validate the data and raise clear errors when invalid:
try:
# This will fail because id should be an integer
invalid_user = User(id="not_an_integer", name="Jane Doe", email="[email protected]")
except Exception as e:
print(f"Validation error: {e}")
Output:
Validation error: 1 validation error for User
id
value is not a valid integer (type=type_error.integer)
Working with JSON
Pydantic seamlessly handles conversion between Python objects and JSON:
user = User(id=1, name="John Doe", email="[email protected]", tags=["admin", "user"])
# Convert to a dictionary
user_dict = user.model_dump()
print(user_dict)
# Convert to JSON string
user_json = user.model_dump_json()
print(user_json)
Output:
{'id': 1, 'name': 'John Doe', 'email': '[email protected]', 'is_active': True, 'tags': ['admin', 'user']}
{"id":1,"name":"John Doe","email":"[email protected]","is_active":true,"tags":["admin","user"]}
Parsing JSON
json_data = '{"id": 2, "name": "Jane Doe", "email": "[email protected]", "tags": ["editor"]}'
user = User.model_validate_json(json_data)
print(user)
Output:
id=2 name='Jane Doe' email='[email protected]' is_active=True tags=['editor']
Advanced Validation
Pydantic offers advanced validation features like:
Field Constraints
from pydantic import BaseModel, Field, EmailStr
class AdvancedUser(BaseModel):
id: int = Field(..., gt=0) # Required field, must be greater than 0
name: str = Field(..., min_length=2, max_length=50) # Length constraints
email: EmailStr # Special email validation type
age: int = Field(None, ge=18, description="Age must be 18 or greater")
Note: You'll need to install the email validator package:
pip install "pydantic[email]"
Custom Validators
You can define custom validation logic using validators:
from pydantic import BaseModel, validator
class Product(BaseModel):
name: str
price: float
discount: float = 0.0
@validator('price')
def price_must_be_positive(cls, value):
if value <= 0:
raise ValueError('Price must be positive')
return value
@validator('discount')
def discount_must_be_valid(cls, value, values):
if value < 0:
raise ValueError('Discount cannot be negative')
if 'price' in values and value > values['price']:
raise ValueError('Discount cannot be greater than price')
return value
Using Pydantic with FastAPI
Now let's see how Pydantic integrates with FastAPI:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.post("/items/")
def create_item(item: Item):
# FastAPI automatically validates the request body against the Item model
# If validation fails, it returns a 422 Unprocessable Entity error
total_price = item.price
if item.tax:
total_price += item.tax
return {
"item": item,
"total_price": total_price
}
When you create an API endpoint with FastAPI:
- FastAPI will read the request body as JSON
- Convert the JSON to a Python object using Pydantic
- Validate the data
- Create an instance of your model
- Pass it to your function
If any validation errors occur, FastAPI will automatically return a detailed error message to the client.
Real-World Example: User Registration API
Let's build a more complete example of a user registration API using Pydantic and FastAPI:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, EmailStr, validator
from typing import Optional
import re
app = FastAPI()
# Define our Pydantic models
class UserRegistration(BaseModel):
username: str = Field(..., min_length=3, max_length=20)
email: EmailStr
password: str = Field(..., min_length=8)
full_name: Optional[str] = None
# Custom validator for username format
@validator('username')
def username_alphanumeric(cls, v):
if not re.match(r'^[a-zA-Z0-9_]+$', v):
raise ValueError('Username must be alphanumeric')
return v
# Custom validator for password strength
@validator('password')
def password_strength(cls, v):
if not any(char.isdigit() for char in v):
raise ValueError('Password must contain at least one number')
if not any(char.isupper() for char in v):
raise ValueError('Password must contain at least one uppercase letter')
return v
class UserResponse(BaseModel):
username: str
email: str
full_name: Optional[str] = None
# Mock database
users_db = {}
@app.post("/register/", response_model=UserResponse)
def register_user(user: UserRegistration):
# Check if username exists
if user.username in users_db:
raise HTTPException(status_code=400, detail="Username already registered")
# In a real application, you would hash the password here
# Save user to our mock database
users_db[user.username] = user.model_dump()
# Return the user without the password
return UserResponse(
username=user.username,
email=user.email,
full_name=user.full_name
)
In this example:
- We use Pydantic to define a schema for user registration with validations
- We create a separate schema for the response (without the password)
- FastAPI uses these schemas to validate input and output data
- Our custom validators ensure data quality (username format, password strength)
Common Pydantic Features
Optional Fields and Default Values
from typing import Optional
from pydantic import BaseModel
class Product(BaseModel):
name: str # Required field
description: Optional[str] = None # Optional field with default None
price: float # Required field
available: bool = True # Optional field with default True
Nested Models
from pydantic import BaseModel
from typing import List
class Address(BaseModel):
street: str
city: str
postal_code: str
class User(BaseModel):
name: str
addresses: List[Address] # A list of Address objects
Type Aliases
from pydantic import BaseModel, Field
from typing import List
# Define a type alias for readability
Tags = List[str]
class Item(BaseModel):
name: str
tags: Tags = Field(default_factory=list)
Summary
Pydantic is a powerful data validation library that works seamlessly with FastAPI to:
- Define data schemas using standard Python type hints
- Validate incoming data automatically
- Convert between JSON and Python objects
- Generate OpenAPI documentation
- Provide clear error messages for invalid data
By leveraging Pydantic with FastAPI, you can build robust, type-safe, and well-documented APIs with less code and fewer bugs.
Additional Resources
Practice Exercises
- Create a Pydantic model for a blog post with fields like title, content, author, publication date, and tags.
- Add validation to ensure the title is between 5 and 100 characters.
- Create a FastAPI endpoint to create a new blog post using your model.
- Add custom validation to check that the publication date is not in the future.
- Create a separate model for the response that includes an auto-generated ID field.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)