FastAPI Model Fields
Introduction
When building APIs with FastAPI, Pydantic models serve as the backbone for request and response data validation. Pydantic's Field
function provides powerful capabilities to customize how these models behave, enabling precise control over data validation, documentation, and transformation. In this guide, we'll explore how to effectively use model fields in FastAPI to create robust and well-documented APIs.
Understanding Pydantic Fields
In Pydantic models, each attribute is a "field" that comes with type hints to ensure data validation. The Field
function extends this functionality with additional options that control validation rules, default values, and documentation.
Let's start with a basic example:
from pydantic import BaseModel, Field
class Product(BaseModel):
id: int
name: str = Field(..., description="The product name")
price: float = Field(gt=0, description="Product price must be positive")
is_available: bool = Field(default=True, description="Product availability status")
In this example:
name
is a required field with a description for documentationprice
must be greater than zerois_available
has a default value ofTrue
The ...
(ellipsis) indicates that a field is required and has no default value.
Field Validation Constraints
Pydantic's Field
provides numerous validation constraints for different data types:
Numeric Constraints
from pydantic import BaseModel, Field
class NumericExample(BaseModel):
integer_field: int = Field(ge=1, le=100, description="Value between 1 and 100")
float_field: float = Field(gt=0, lt=1.0, description="Value between 0 and 1.0 exclusive")
multiple_of: int = Field(multiple_of=5, description="Must be a multiple of 5")
Here:
ge
: greater than or equalle
: less than or equalgt
: greater thanlt
: less thanmultiple_of
: must be a multiple of the specified value
String Constraints
from pydantic import BaseModel, Field
class StringExample(BaseModel):
username: str = Field(min_length=3, max_length=50)
code: str = Field(regex="^[A-Z]{2}-[0-9]{4}$") # Pattern like "AB-1234"
long_text: str = Field(default="", title="Long Description", description="Optional extended description")
String validation includes:
min_length
: minimum string lengthmax_length
: maximum string lengthregex
: regular expression pattern matching
Collection Constraints
from pydantic import BaseModel, Field
from typing import List, Set
class CollectionExample(BaseModel):
tags: List[str] = Field(min_items=1, max_items=5)
unique_codes: Set[str] = Field(default_factory=set)
scores: List[int] = Field(min_items=3, max_items=10, gt=0, lt=100)
Collection constraints include:
min_items
: minimum number of itemsmax_items
: maximum number of itemsunique_items
: whether all items must be unique (mostly for lists)
Fields with Default Values
Default values can be provided in several ways:
from pydantic import BaseModel, Field
from datetime import datetime
from typing import List
class DefaultsExample(BaseModel):
created_at: datetime = Field(default_factory=datetime.now)
tags: List[str] = Field(default_factory=list)
status: str = Field(default="active")
priority: int = Field(default=1)
Note that:
default
provides a specific valuedefault_factory
accepts a function that returns a default value- For mutable types like lists or dicts, use
default_factory
to avoid shared references
Documentation Enhancement with Fields
One powerful aspect of Fields is their ability to enhance API documentation in FastAPI:
from pydantic import BaseModel, Field
from typing import Optional
class User(BaseModel):
user_id: int = Field(..., description="Unique user identifier")
username: str = Field(
...,
min_length=3,
max_length=50,
description="User's login name",
examples=["john_doe", "jane_smith"]
)
email: str = Field(
...,
description="User's email address",
examples=["[email protected]"]
)
bio: Optional[str] = Field(
None,
title="Biography",
description="User's optional biography",
max_length=500
)
When used in FastAPI, these fields produce rich API documentation:
description
provides explanations for each fieldexamples
supplies sample valuestitle
provides an alternative display name
Advanced Field Usage
Field Aliases
Aliases let you use different names externally vs. internally:
from pydantic import BaseModel, Field
class UserModel(BaseModel):
user_id: int = Field(..., alias="userId")
is_active: bool = Field(..., alias="isActive")
# When creating from external data:
user = UserModel(userId=123, isActive=True)
print(user.user_id) # 123
print(user.is_active) # True
# When converting to JSON:
print(user.model_dump()) # {"userId": 123, "isActive": true}
Using Extra Field Metadata
You can add custom metadata to fields for your application's specific needs:
from pydantic import BaseModel, Field
class Product(BaseModel):
name: str = Field(..., description="Product name", metadata={"searchable": True})
internal_code: str = Field(..., metadata={"searchable": False, "internal_only": True})
class Config:
# Allow accessing custom metadata
arbitrary_types_allowed = True
Frozen Fields
You can create read-only fields that can't be modified after model creation:
from pydantic import BaseModel, Field
class AuditLog(BaseModel):
log_id: str = Field(..., frozen=True)
timestamp: int = Field(..., frozen=True)
message: str
Real-World Example: Product API
Let's see how we can apply field validation in a real-world FastAPI application:
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, validator
from typing import List, Optional
from datetime import datetime
import uuid
app = FastAPI()
class ProductCreate(BaseModel):
name: str = Field(..., min_length=1, max_length=100, description="Product name")
description: Optional[str] = Field(None, max_length=1000)
price: float = Field(..., gt=0, description="Product price - must be greater than zero")
categories: List[str] = Field(default_factory=list, max_items=5)
sku: Optional[str] = Field(None, regex=r"^[A-Z]{2}\d{6}$", description="Stock Keeping Unit, format: XX000000")
@validator("categories")
def categories_must_be_valid(cls, v):
for category in v:
if not category.strip():
raise ValueError("Categories cannot be empty strings")
return v
class Product(ProductCreate):
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
created_at: datetime = Field(default_factory=datetime.now)
updated_at: Optional[datetime] = None
in_stock: bool = Field(default=True)
# Database mock
products_db = {}
@app.post("/products/", response_model=Product)
def create_product(product: ProductCreate):
product_dict = product.model_dump()
product_id = str(uuid.uuid4())
# Create complete product
new_product = Product(
id=product_id,
**product_dict
)
# Store in mock database
products_db[product_id] = new_product
return new_product
@app.get("/products/{product_id}", response_model=Product)
def get_product(product_id: str):
if product_id not in products_db:
raise HTTPException(status_code=404, detail="Product not found")
return products_db[product_id]
This example shows:
- Separate models for creation and representation
- Comprehensive field validation with custom validators
- Auto-generated fields using default factories
- Clear API documentation through field descriptions
Summary
Pydantic's Field function is a crucial tool for FastAPI developers that allows you to:
- Apply precise validation constraints to your data
- Provide default values in a clean way
- Enhance API documentation
- Control data serialization and deserialization
- Implement custom validation logic
By mastering Field usage, you'll create more robust APIs with better validation, clearer documentation, and improved developer experience.
Additional Resources
Exercises
- Create a
UserRegistration
model with appropriate validation for username, email, and password fields. - Build a
BlogPost
model with title, content, tags, and publication date fields, including proper validation. - Implement a FastAPI endpoint that uses a Pydantic model with field constraints to validate and store a form submission.
- Create a model for financial transactions with proper numeric validation for amount, appropriate date fields, and status.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)