FastAPI Body Dependencies
When building APIs with FastAPI, you'll often need to process and validate data from request bodies. FastAPI's powerful dependency injection system can be integrated with request bodies to create cleaner, more maintainable code. This article explores how to leverage body dependencies in FastAPI applications.
Introduction to Body Dependencies
Body dependencies are a way to use FastAPI's dependency injection system with request body parameters. They allow you to:
- Extract and validate specific fields from the request body
- Reuse body validation logic across multiple endpoints
- Organize your code better with separation of concerns
- Implement custom processing for request body data
Basic Request Body Handling
Before diving into body dependencies, let's review how standard request body handling works in FastAPI:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class User(BaseModel):
username: str
email: str
full_name: str = None
@app.post("/users/")
async def create_user(user: User):
return {"user": user.dict()}
This works well for simple cases, but as your API grows in complexity, you might need more sophisticated ways to handle request bodies.
Creating Body Dependencies
Body dependencies in FastAPI allow you to declare dependencies that work with request body data. Here's how to create them:
Method 1: Using Depends with Pydantic Models
from fastapi import FastAPI, Depends, Body
from pydantic import BaseModel, EmailStr, validator
from typing import Optional
app = FastAPI()
class UserCredentials(BaseModel):
username: str
password: str
@validator('username')
def username_must_be_valid(cls, v):
if len(v) < 3:
raise ValueError('Username must be at least 3 characters long')
return v
@validator('password')
def password_must_be_strong(cls, v):
if len(v) < 8:
raise ValueError('Password must be at least 8 characters long')
# Add more validation as needed
return v
def get_validated_user_credentials(credentials: UserCredentials):
# You could add additional validation or processing here
# For example, checking if username already exists in database
return credentials
@app.post("/register/")
async def register_user(credentials: UserCredentials = Depends(get_validated_user_credentials)):
# Use the validated credentials
return {
"username": credentials.username,
"message": "User registered successfully"
}
Method 2: Using Body Parameters with Dependencies
You can also extract specific fields from the request body:
from fastapi import FastAPI, Depends, Body
from typing import Optional
app = FastAPI()
def validate_name(name: str = Body(...)):
if len(name) < 2:
raise ValueError("Name too short")
return name.title() # Return capitalized name
def validate_age(age: int = Body(...)):
if age < 18:
raise ValueError("Must be at least 18 years old")
return age
@app.post("/profile/")
async def create_profile(
name: str = Depends(validate_name),
age: int = Depends(validate_age),
bio: Optional[str] = Body(None)
):
return {
"name": name,
"age": age,
"bio": bio,
"profile": f"{name}, {age} years old"
}
Nested Body Dependencies
For more complex scenarios, you might need to work with nested data. FastAPI handles this elegantly:
from fastapi import FastAPI, Depends, Body
from pydantic import BaseModel
from typing import List
app = FastAPI()
class Address(BaseModel):
street: str
city: str
country: str
postal_code: str
class User(BaseModel):
name: str
addresses: List[Address]
def extract_primary_address(user: User = Body(...)):
if not user.addresses:
raise ValueError("User must have at least one address")
return user.addresses[0]
@app.post("/users/primary-address/")
async def get_primary_address(primary_address: Address = Depends(extract_primary_address)):
return {
"primary_address": primary_address,
"formatted": f"{primary_address.street}, {primary_address.city}, {primary_address.country}"
}
Real-world Example: Form Data Validation and Processing
Let's look at a practical example of using body dependencies for form submission:
from fastapi import FastAPI, Depends, Body, Form, HTTPException
from pydantic import BaseModel, EmailStr, validator
from typing import Optional
import re
app = FastAPI()
# For JSON request body
class ContactForm(BaseModel):
name: str
email: EmailStr
message: str
subject: Optional[str] = None
@validator('name')
def name_must_be_valid(cls, v):
if len(v) < 2:
raise ValueError('Name must be at least 2 characters')
return v
@validator('message')
def message_not_empty(cls, v):
if len(v.strip()) == 0:
raise ValueError('Message cannot be empty')
return v
# Dependency for validating spam content
def validate_no_spam(form: ContactForm):
spam_patterns = [
r"buy now",
r"free money",
r"lottery winner",
# Add more patterns as needed
]
for pattern in spam_patterns:
if re.search(pattern, form.message, re.IGNORECASE):
raise HTTPException(status_code=400, detail="Spam content detected")
return form
# Dependency for sanitizing content
def sanitize_form(form: ContactForm = Depends(validate_no_spam)):
# Sanitize inputs - simple example
form.name = form.name.strip()
form.email = form.email.lower().strip()
form.message = form.message.strip()
if form.subject:
form.subject = form.subject.strip()
else:
form.subject = "General Inquiry" # Set default subject
return form
@app.post("/contact/")
async def submit_contact_form(form: ContactForm = Depends(sanitize_form)):
# Process the sanitized, validated contact form
# In a real app, you might save to a database or send an email
return {
"status": "success",
"message": "Thank you for your submission!",
"data": {
"name": form.name,
"email": form.email,
"subject": form.subject,
"message_preview": f"{form.message[:50]}..."
}
}
This example demonstrates a practical use case where multiple dependencies work together to validate and sanitize a contact form submission.
Benefits of Body Dependencies
Using body dependencies offers several advantages:
- Code Reusability: Define validation once, use it across multiple endpoints
- Separation of Concerns: Keep validation logic separate from endpoint logic
- Testability: Dependencies are easier to test in isolation
- Maintainability: When validation rules change, you only need to update in one place
- Readability: Endpoint functions remain clean and focused on their core purpose
Advanced Techniques
Combining Path, Query and Body Dependencies
You can combine different types of dependencies in a single endpoint:
from fastapi import FastAPI, Depends, Body, Path, Query
from pydantic import BaseModel
from typing import Optional
app = FastAPI()
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
def validate_item_price(item: Item = Body(...)):
if item.price <= 0:
raise ValueError("Price must be positive")
return item
def get_user_by_id(user_id: int = Path(...)):
# In a real app, you'd fetch from a database
return {"user_id": user_id, "name": "Test User"}
def check_admin_access(is_admin: bool = Query(False)):
if not is_admin:
raise HTTPException(status_code=403, detail="Admin access required")
return is_admin
@app.post("/users/{user_id}/items/")
async def create_item_for_user(
user: dict = Depends(get_user_by_id),
item: Item = Depends(validate_item_price),
is_admin: bool = Depends(check_admin_access)
):
return {
"user": user,
"item": item,
"admin_access": is_admin
}
Using Dependencies with Background Tasks
You can use body dependencies to set up background tasks:
from fastapi import FastAPI, Depends, BackgroundTasks
from pydantic import BaseModel, EmailStr
app = FastAPI()
class EmailContent(BaseModel):
email: EmailStr
subject: str
content: str
def validate_email_content(email_content: EmailContent):
# Validate email content here
return email_content
def send_email_background(email: str, subject: str, content: str):
# This would be your actual email sending logic
print(f"Sending email to {email} with subject: {subject}")
# Implementation omitted for brevity
@app.post("/send-email/")
async def send_email(
background_tasks: BackgroundTasks,
email_content: EmailContent = Depends(validate_email_content)
):
# Add the email sending as a background task
background_tasks.add_task(
send_email_background,
email_content.email,
email_content.subject,
email_content.content
)
return {"message": "Email will be sent in the background"}
Summary
FastAPI body dependencies are a powerful tool for creating clean, maintainable APIs. They allow you to:
- Validate request body data with reusable functions
- Extract and process specific fields from request bodies
- Chain multiple validation and processing steps together
- Create more maintainable code by separating validation from business logic
As you build more complex APIs with FastAPI, body dependencies will become an essential part of your toolkit for keeping your code organized and your endpoints focused on their core functionality.
Additional Resources
- FastAPI Official Documentation on Dependencies
- Pydantic Documentation for Data Validation
- FastAPI Body Parameters Documentation
Practice Exercises
- Create a user registration endpoint that uses body dependencies to validate password strength.
- Implement a blog post submission API with dependencies that check for inappropriate content.
- Build a product ordering system with dependencies that validate product availability and pricing.
- Create a multi-step form submission API where dependencies handle validation at each step.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)