fastapi-validation

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

FastAPI Validation

FastAPI 验证

Master FastAPI validation with Pydantic for building type-safe APIs with comprehensive request and response validation.
掌握使用Pydantic进行FastAPI验证的方法,构建具备全面请求与响应验证的类型安全API。

Pydantic BaseModel Fundamentals

Pydantic BaseModel 基础

Core Pydantic patterns with Pydantic v2.
python
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime
使用Pydantic v2的核心Pydantic模式。
python
from pydantic import BaseModel, Field, ConfigDict
from typing import Optional
from datetime import datetime

Basic model

Basic model

class User(BaseModel): id: int name: str email: str created_at: datetime
class User(BaseModel): id: int name: str email: str created_at: datetime

With defaults and optional fields

With defaults and optional fields

class UserCreate(BaseModel): name: str email: str age: Optional[int] = None is_active: bool = True
class UserCreate(BaseModel): name: str email: str age: Optional[int] = None is_active: bool = True

With Field constraints

With Field constraints

class Product(BaseModel): name: str = Field(..., min_length=1, max_length=100) price: float = Field(..., gt=0, le=1000000) quantity: int = Field(default=0, ge=0) description: Optional[str] = Field(None, max_length=500)
model_config = ConfigDict(
    str_strip_whitespace=True,
    validate_assignment=True,
    json_schema_extra={
        'example': {
            'name': 'Widget',
            'price': 29.99,
            'quantity': 100,
            'description': 'A useful widget'
        }
    }
)
undefined
class Product(BaseModel): name: str = Field(..., min_length=1, max_length=100) price: float = Field(..., gt=0, le=1000000) quantity: int = Field(default=0, ge=0) description: Optional[str] = Field(None, max_length=500)
model_config = ConfigDict(
    str_strip_whitespace=True,
    validate_assignment=True,
    json_schema_extra={
        'example': {
            'name': 'Widget',
            'price': 29.99,
            'quantity': 100,
            'description': 'A useful widget'
        }
    }
)
undefined

Request Body Validation

请求体验证

Validating complex request bodies.
python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr, Field
from typing import List

app = FastAPI()
验证复杂的请求体。
python
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, EmailStr, Field
from typing import List

app = FastAPI()

Simple request validation

Simple request validation

class CreateUserRequest(BaseModel): username: str = Field(..., min_length=3, max_length=50) email: EmailStr password: str = Field(..., min_length=8) age: int = Field(..., ge=13, le=120)
@app.post('/users') async def create_user(user: CreateUserRequest): # user is automatically validated return {'username': user.username, 'email': user.email}
class CreateUserRequest(BaseModel): username: str = Field(..., min_length=3, max_length=50) email: EmailStr password: str = Field(..., min_length=8) age: int = Field(..., ge=13, le=120)
@app.post('/users') async def create_user(user: CreateUserRequest): # user is automatically validated return {'username': user.username, 'email': user.email}

Nested models

Nested models

class Address(BaseModel): street: str city: str state: str = Field(..., min_length=2, max_length=2) zip_code: str = Field(..., pattern=r'^\d{5}(-\d{4})?$')
class UserProfile(BaseModel): name: str email: EmailStr address: Address phone: Optional[str] = Field(None, pattern=r'^+?1?\d{9,15}$')
@app.post('/profiles') async def create_profile(profile: UserProfile): return profile
class Address(BaseModel): street: str city: str state: str = Field(..., min_length=2, max_length=2) zip_code: str = Field(..., pattern=r'^\d{5}(-\d{4})?$')
class UserProfile(BaseModel): name: str email: EmailStr address: Address phone: Optional[str] = Field(None, pattern=r'^+?1?\d{9,15}$')
@app.post('/profiles') async def create_profile(profile: UserProfile): return profile

List validation

List validation

class BulkCreateRequest(BaseModel): users: List[CreateUserRequest] = Field(..., min_length=1, max_length=100)
@app.post('/users/bulk') async def bulk_create_users(request: BulkCreateRequest): return {'count': len(request.users)}
class BulkCreateRequest(BaseModel): users: List[CreateUserRequest] = Field(..., min_length=1, max_length=100)
@app.post('/users/bulk') async def bulk_create_users(request: BulkCreateRequest): return {'count': len(request.users)}

Complex nested structures

Complex nested structures

class Tag(BaseModel): name: str color: str = Field(..., pattern=r'^#[0-9A-Fa-f]{6}$')
class Post(BaseModel): title: str = Field(..., min_length=1, max_length=200) content: str tags: List[Tag] = [] author: UserProfile published: bool = False
@app.post('/posts') async def create_post(post: Post): return post
undefined
class Tag(BaseModel): name: str color: str = Field(..., pattern=r'^#[0-9A-Fa-f]{6}$')
class Post(BaseModel): title: str = Field(..., min_length=1, max_length=200) content: str tags: List[Tag] = [] author: UserProfile published: bool = False
@app.post('/posts') async def create_post(post: Post): return post
undefined

Query Parameter Validation

查询参数验证

Validating query parameters with Field constraints.
python
from fastapi import FastAPI, Query
from typing import Optional, List
from enum import Enum

app = FastAPI()
使用Field约束验证查询参数。
python
from fastapi import FastAPI, Query
from typing import Optional, List
from enum import Enum

app = FastAPI()

Simple query params

Simple query params

@app.get('/users') async def get_users( skip: int = Query(0, ge=0), limit: int = Query(10, ge=1, le=100), search: Optional[str] = Query(None, min_length=3, max_length=50) ): return {'skip': skip, 'limit': limit, 'search': search}
@app.get('/users') async def get_users( skip: int = Query(0, ge=0), limit: int = Query(10, ge=1, le=100), search: Optional[str] = Query(None, min_length=3, max_length=50) ): return {'skip': skip, 'limit': limit, 'search': search}

Enum validation

Enum validation

class SortOrder(str, Enum): asc = 'asc' desc = 'desc'
class SortField(str, Enum): name = 'name' created_at = 'created_at' updated_at = 'updated_at'
@app.get('/items') async def get_items( sort_by: SortField = Query(SortField.created_at), order: SortOrder = Query(SortOrder.desc) ): return {'sort_by': sort_by, 'order': order}
class SortOrder(str, Enum): asc = 'asc' desc = 'desc'
class SortField(str, Enum): name = 'name' created_at = 'created_at' updated_at = 'updated_at'
@app.get('/items') async def get_items( sort_by: SortField = Query(SortField.created_at), order: SortOrder = Query(SortOrder.desc) ): return {'sort_by': sort_by, 'order': order}

Multiple values

Multiple values

@app.get('/filter') async def filter_items( tags: List[str] = Query([]), categories: List[int] = Query([], max_length=10) ): return {'tags': tags, 'categories': categories}
@app.get('/filter') async def filter_items( tags: List[str] = Query([]), categories: List[int] = Query([], max_length=10) ): return {'tags': tags, 'categories': categories}

Regex pattern

Regex pattern

@app.get('/search') async def search( q: str = Query(..., min_length=1, max_length=100, pattern=r'^[a-zA-Z0-9\s]+$') ): return {'query': q}
undefined
@app.get('/search') async def search( q: str = Query(..., min_length=1, max_length=100, pattern=r'^[a-zA-Z0-9\s]+$') ): return {'query': q}
undefined

Path Parameter Validation

路径参数验证

Validating URL path parameters.
python
from fastapi import FastAPI, Path
from typing import Annotated

app = FastAPI()

@app.get('/users/{user_id}')
async def get_user(
    user_id: int = Path(..., gt=0, description='The user ID')
):
    return {'user_id': user_id}

@app.get('/items/{item_id}/reviews/{review_id}')
async def get_review(
    item_id: Annotated[int, Path(gt=0)],
    review_id: Annotated[int, Path(gt=0)]
):
    return {'item_id': item_id, 'review_id': review_id}
验证URL路径参数。
python
from fastapi import FastAPI, Path
from typing import Annotated

app = FastAPI()

@app.get('/users/{user_id}')
async def get_user(
    user_id: int = Path(..., gt=0, description='The user ID')
):
    return {'user_id': user_id}

@app.get('/items/{item_id}/reviews/{review_id}')
async def get_review(
    item_id: Annotated[int, Path(gt=0)],
    review_id: Annotated[int, Path(gt=0)]
):
    return {'item_id': item_id, 'review_id': review_id}

String path validation

String path validation

@app.get('/categories/{category_name}') async def get_category( category_name: str = Path(..., min_length=1, max_length=50, pattern=r'^[a-z-]+$') ): return {'category': category_name}
undefined
@app.get('/categories/{category_name}') async def get_category( category_name: str = Path(..., min_length=1, max_length=50, pattern=r'^[a-z-]+$') ): return {'category': category_name}
undefined

Custom Validators

自定义验证器

Field validators and model validators with Pydantic v2.
python
from pydantic import BaseModel, field_validator, model_validator
from typing import Any
import re

class UserRegistration(BaseModel):
    username: str
    email: str
    password: str
    password_confirm: str

    @field_validator('username')
    @classmethod
    def username_alphanumeric(cls, v: str) -> str:
        if not re.match(r'^[a-zA-Z0-9_]+$', v):
            raise ValueError('Username must be alphanumeric')
        if len(v) < 3:
            raise ValueError('Username must be at least 3 characters')
        return v.lower()

    @field_validator('email')
    @classmethod
    def validate_email_domain(cls, v: str) -> str:
        if not v.endswith(('@example.com', '@example.org')):
            raise ValueError('Email must be from example.com or example.org')
        return v.lower()

    @field_validator('password')
    @classmethod
    def password_strength(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError('Password must be at least 8 characters')
        if not re.search(r'[A-Z]', v):
            raise ValueError('Password must contain uppercase letter')
        if not re.search(r'[a-z]', v):
            raise ValueError('Password must contain lowercase letter')
        if not re.search(r'[0-9]', v):
            raise ValueError('Password must contain digit')
        return v

    @model_validator(mode='after')
    def check_passwords_match(self) -> 'UserRegistration':
        if self.password != self.password_confirm:
            raise ValueError('Passwords do not match')
        return self
Pydantic v2的字段验证器和模型验证器。
python
from pydantic import BaseModel, field_validator, model_validator
from typing import Any
import re

class UserRegistration(BaseModel):
    username: str
    email: str
    password: str
    password_confirm: str

    @field_validator('username')
    @classmethod
    def username_alphanumeric(cls, v: str) -> str:
        if not re.match(r'^[a-zA-Z0-9_]+$', v):
            raise ValueError('Username must be alphanumeric')
        if len(v) < 3:
            raise ValueError('Username must be at least 3 characters')
        return v.lower()

    @field_validator('email')
    @classmethod
    def validate_email_domain(cls, v: str) -> str:
        if not v.endswith(('@example.com', '@example.org')):
            raise ValueError('Email must be from example.com or example.org')
        return v.lower()

    @field_validator('password')
    @classmethod
    def password_strength(cls, v: str) -> str:
        if len(v) < 8:
            raise ValueError('Password must be at least 8 characters')
        if not re.search(r'[A-Z]', v):
            raise ValueError('Password must contain uppercase letter')
        if not re.search(r'[a-z]', v):
            raise ValueError('Password must contain lowercase letter')
        if not re.search(r'[0-9]', v):
            raise ValueError('Password must contain digit')
        return v

    @model_validator(mode='after')
    def check_passwords_match(self) -> 'UserRegistration':
        if self.password != self.password_confirm:
            raise ValueError('Passwords do not match')
        return self

Validator with dependencies

Validator with dependencies

class DateRange(BaseModel): start_date: datetime end_date: datetime
@model_validator(mode='after')
def check_dates(self) -> 'DateRange':
    if self.start_date >= self.end_date:
        raise ValueError('start_date must be before end_date')
    return self
class DateRange(BaseModel): start_date: datetime end_date: datetime
@model_validator(mode='after')
def check_dates(self) -> 'DateRange':
    if self.start_date >= self.end_date:
        raise ValueError('start_date must be before end_date')
    return self

Computed fields

Computed fields

from pydantic import computed_field
class Product(BaseModel): name: str price: float tax_rate: float = 0.1
@computed_field
@property
def price_with_tax(self) -> float:
    return round(self.price * (1 + self.tax_rate), 2)
from pydantic import computed_field
class Product(BaseModel): name: str price: float tax_rate: float = 0.1
@computed_field
@property
def price_with_tax(self) -> float:
    return round(self.price * (1 + self.tax_rate), 2)

Before validator

Before validator

class UserInput(BaseModel): name: str email: str
@field_validator('name', 'email', mode='before')
@classmethod
def strip_whitespace(cls, v: Any) -> Any:
    if isinstance(v, str):
        return v.strip()
    return v
undefined
class UserInput(BaseModel): name: str email: str
@field_validator('name', 'email', mode='before')
@classmethod
def strip_whitespace(cls, v: Any) -> Any:
    if isinstance(v, str):
        return v.strip()
    return v
undefined

Field Types

字段类型

Specialized field types for validation.
python
from pydantic import (
    BaseModel,
    EmailStr,
    HttpUrl,
    SecretStr,
    conint,
    constr,
    confloat,
    conlist,
    UUID4,
    IPvAnyAddress,
    FilePath,
    DirectoryPath,
    Json
)
from typing import List
from datetime import date, time

class AdvancedUser(BaseModel):
    # String constraints
    username: constr(min_length=3, max_length=50, pattern=r'^[a-zA-Z0-9_]+$')
    bio: constr(max_length=500) | None = None

    # Email and URL
    email: EmailStr
    website: HttpUrl | None = None

    # Numeric constraints
    age: conint(ge=13, le=120)
    rating: confloat(ge=0.0, le=5.0)

    # Secret fields (won't be logged)
    password: SecretStr
    api_key: SecretStr

    # UUID
    user_id: UUID4

    # Network
    ip_address: IPvAnyAddress | None = None

    # Date and time
    birth_date: date
    preferred_time: time | None = None

    # Lists with constraints
    tags: conlist(str, min_length=1, max_length=10)

    # JSON field
    metadata: Json | None = None
用于验证的专用字段类型。
python
from pydantic import (
    BaseModel,
    EmailStr,
    HttpUrl,
    SecretStr,
    conint,
    constr,
    confloat,
    conlist,
    UUID4,
    IPvAnyAddress,
    FilePath,
    DirectoryPath,
    Json
)
from typing import List
from datetime import date, time

class AdvancedUser(BaseModel):
    # String constraints
    username: constr(min_length=3, max_length=50, pattern=r'^[a-zA-Z0-9_]+$')
    bio: constr(max_length=500) | None = None

    # Email and URL
    email: EmailStr
    website: HttpUrl | None = None

    # Numeric constraints
    age: conint(ge=13, le=120)
    rating: confloat(ge=0.0, le=5.0)

    # Secret fields (won't be logged)
    password: SecretStr
    api_key: SecretStr

    # UUID
    user_id: UUID4

    # Network
    ip_address: IPvAnyAddress | None = None

    # Date and time
    birth_date: date
    preferred_time: time | None = None

    # Lists with constraints
    tags: conlist(str, min_length=1, max_length=10)

    # JSON field
    metadata: Json | None = None

File path validation

File path validation

class FileUploadConfig(BaseModel): upload_dir: DirectoryPath allowed_file: FilePath | None = None
undefined
class FileUploadConfig(BaseModel): upload_dir: DirectoryPath allowed_file: FilePath | None = None
undefined

Nested Models and Composition

嵌套模型与组合

Building complex models from simpler ones.
python
from pydantic import BaseModel
from typing import List, Optional
通过简单模型构建复杂模型。
python
from pydantic import BaseModel
from typing import List, Optional

Composition

Composition

class Coordinates(BaseModel): latitude: float = Field(..., ge=-90, le=90) longitude: float = Field(..., ge=-180, le=180)
class Location(BaseModel): name: str coordinates: Coordinates address: Optional[str] = None
class Event(BaseModel): title: str description: str location: Location attendees: List[str] = []
class Coordinates(BaseModel): latitude: float = Field(..., ge=-90, le=90) longitude: float = Field(..., ge=-180, le=180)
class Location(BaseModel): name: str coordinates: Coordinates address: Optional[str] = None
class Event(BaseModel): title: str description: str location: Location attendees: List[str] = []

Inheritance

Inheritance

class BaseUser(BaseModel): username: str email: EmailStr
class AdminUser(BaseUser): permissions: List[str] is_superuser: bool = False
class RegularUser(BaseUser): subscription_tier: str = 'free'
class BaseUser(BaseModel): username: str email: EmailStr
class AdminUser(BaseUser): permissions: List[str] is_superuser: bool = False
class RegularUser(BaseUser): subscription_tier: str = 'free'

Model reuse

Model reuse

class TimestampMixin(BaseModel): created_at: datetime updated_at: datetime
class Post(TimestampMixin): title: str content: str author_id: int
class Comment(TimestampMixin): content: str post_id: int author_id: int
undefined
class TimestampMixin(BaseModel): created_at: datetime updated_at: datetime
class Post(TimestampMixin): title: str content: str author_id: int
class Comment(TimestampMixin): content: str post_id: int author_id: int
undefined

Model Configuration

模型配置

ConfigDict options for model behavior.
python
from pydantic import BaseModel, ConfigDict, Field
用于控制模型行为的ConfigDict选项。
python
from pydantic import BaseModel, ConfigDict, Field

Strict mode

Strict mode

class StrictModel(BaseModel): model_config = ConfigDict(strict=True)
id: int  # Won't coerce from string
name: str
class StrictModel(BaseModel): model_config = ConfigDict(strict=True)
id: int  # Won't coerce from string
name: str

ORM mode (for database models)

ORM mode (for database models)

class UserORM(BaseModel): model_config = ConfigDict(from_attributes=True)
id: int
name: str
email: str
class UserORM(BaseModel): model_config = ConfigDict(from_attributes=True)
id: int
name: str
email: str

Usage with SQLAlchemy

Usage with SQLAlchemy

from sqlalchemy import Column, Integer, String from sqlalchemy.orm import declarative_base
Base = declarative_base()
class UserModel(Base): tablename = 'users' id = Column(Integer, primary_key=True) name = Column(String) email = Column(String)
@app.get('/users/{user_id}', response_model=UserORM) async def get_user(user_id: int, db = Depends(get_db)): user = db.query(UserModel).filter(UserModel.id == user_id).first() return user # Automatically converted to UserORM
from sqlalchemy import Column, Integer, String from sqlalchemy.orm import declarative_base
Base = declarative_base()
class UserModel(Base): tablename = 'users' id = Column(Integer, primary_key=True) name = Column(String) email = Column(String)
@app.get('/users/{user_id}', response_model=UserORM) async def get_user(user_id: int, db = Depends(get_db)): user = db.query(UserModel).filter(UserModel.id == user_id).first() return user # Automatically converted to UserORM

Populate by name

Populate by name

class FlexibleModel(BaseModel): model_config = ConfigDict(populate_by_name=True)
user_id: int = Field(alias='userId')
user_name: str = Field(alias='userName')
class FlexibleModel(BaseModel): model_config = ConfigDict(populate_by_name=True)
user_id: int = Field(alias='userId')
user_name: str = Field(alias='userName')

Allow extra fields

Allow extra fields

class ExtraFieldsModel(BaseModel): model_config = ConfigDict(extra='allow')
name: str
# Any extra fields will be stored
class ExtraFieldsModel(BaseModel): model_config = ConfigDict(extra='allow')
name: str
# Any extra fields will be stored

Forbid extra fields

Forbid extra fields

class StrictFieldsModel(BaseModel): model_config = ConfigDict(extra='forbid')
name: str
# Extra fields will raise validation error
undefined
class StrictFieldsModel(BaseModel): model_config = ConfigDict(extra='forbid')
name: str
# Extra fields will raise validation error
undefined

Response Models

响应模型

Validating and shaping API responses.
python
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

app = FastAPI()

class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str

class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    # Note: password excluded

    model_config = ConfigDict(from_attributes=True)

@app.post('/users', response_model=UserResponse)
async def create_user(user: UserCreate):
    # Create user in database
    db_user = create_user_in_db(user)
    return db_user  # Password automatically excluded
验证并格式化API响应。
python
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List

app = FastAPI()

class UserCreate(BaseModel):
    username: str
    email: EmailStr
    password: str

class UserResponse(BaseModel):
    id: int
    username: str
    email: str
    # Note: password excluded

    model_config = ConfigDict(from_attributes=True)

@app.post('/users', response_model=UserResponse)
async def create_user(user: UserCreate):
    # Create user in database
    db_user = create_user_in_db(user)
    return db_user  # Password automatically excluded

Response with exclude

Response with exclude

class UserDetail(BaseModel): id: int username: str email: str password_hash: str secret_key: str
@app.get('/users/{user_id}', response_model=UserDetail, response_model_exclude={'password_hash', 'secret_key'}) async def get_user_detail(user_id: int): return get_user_from_db(user_id)
class UserDetail(BaseModel): id: int username: str email: str password_hash: str secret_key: str
@app.get('/users/{user_id}', response_model=UserDetail, response_model_exclude={'password_hash', 'secret_key'}) async def get_user_detail(user_id: int): return get_user_from_db(user_id)

Response with include

Response with include

@app.get('/users/{user_id}/public', response_model=UserDetail, response_model_include={'id', 'username'}) async def get_user_public(user_id: int): return get_user_from_db(user_id)
@app.get('/users/{user_id}/public', response_model=UserDetail, response_model_include={'id', 'username'}) async def get_user_public(user_id: int): return get_user_from_db(user_id)

List response

List response

@app.get('/users', response_model=List[UserResponse]) async def list_users(): return get_all_users()
@app.get('/users', response_model=List[UserResponse]) async def list_users(): return get_all_users()

Optional response

Optional response

from typing import Optional
@app.get('/users/{user_id}/optional', response_model=Optional[UserResponse]) async def get_user_optional(user_id: int): user = get_user_from_db(user_id) return user # Can be None
from typing import Optional
@app.get('/users/{user_id}/optional', response_model=Optional[UserResponse]) async def get_user_optional(user_id: int): user = get_user_from_db(user_id) return user # Can be None

Union response

Union response

from typing import Union
class SuccessResponse(BaseModel): status: str = 'success' data: dict
class ErrorResponse(BaseModel): status: str = 'error' message: str
@app.get('/data', response_model=Union[SuccessResponse, ErrorResponse]) async def get_data(): try: data = fetch_data() return SuccessResponse(data=data) except Exception as e: return ErrorResponse(message=str(e))
undefined
from typing import Union
class SuccessResponse(BaseModel): status: str = 'success' data: dict
class ErrorResponse(BaseModel): status: str = 'error' message: str
@app.get('/data', response_model=Union[SuccessResponse, ErrorResponse]) async def get_data(): try: data = fetch_data() return SuccessResponse(data=data) except Exception as e: return ErrorResponse(message=str(e))
undefined

Error Handling

错误处理

Custom error messages and validation error handling.
python
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, ValidationError

app = FastAPI()
自定义错误消息与验证错误处理。
python
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel, ValidationError

app = FastAPI()

Custom validation error handler

Custom validation error handler

@app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): errors = [] for error in exc.errors(): errors.append({ 'field': '.'.join(str(loc) for loc in error['loc'][1:]), 'message': error['msg'], 'type': error['type'] })
return JSONResponse(
    status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
    content={'errors': errors}
)
@app.exception_handler(RequestValidationError) async def validation_exception_handler(request: Request, exc: RequestValidationError): errors = [] for error in exc.errors(): errors.append({ 'field': '.'.join(str(loc) for loc in error['loc'][1:]), 'message': error['msg'], 'type': error['type'] })
return JSONResponse(
    status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
    content={'errors': errors}
)

Custom field error messages

Custom field error messages

class User(BaseModel): username: str = Field(..., min_length=3, description='Username must be at least 3 characters') age: int = Field(..., ge=18, description='Must be 18 or older')
class User(BaseModel): username: str = Field(..., min_length=3, description='Username must be at least 3 characters') age: int = Field(..., ge=18, description='Must be 18 or older')

Programmatic validation

Programmatic validation

async def validate_user_data(data: dict): try: user = User(**data) return user except ValidationError as e: raise HTTPException( status_code=422, detail=e.errors() )
undefined
async def validate_user_data(data: dict): try: user = User(**data) return user except ValidationError as e: raise HTTPException( status_code=422, detail=e.errors() )
undefined

File Upload Validation

文件上传验证

Validating file uploads.
python
from fastapi import FastAPI, File, UploadFile, HTTPException
from typing import List

app = FastAPI()

@app.post('/upload')
async def upload_file(file: UploadFile = File(...)):
    # Validate file type
    allowed_types = ['image/jpeg', 'image/png', 'image/gif']
    if file.content_type not in allowed_types:
        raise HTTPException(
            status_code=400,
            detail=f'File type {file.content_type} not allowed'
        )

    # Validate file size
    contents = await file.read()
    max_size = 5 * 1024 * 1024  # 5MB
    if len(contents) > max_size:
        raise HTTPException(
            status_code=400,
            detail='File too large (max 5MB)'
        )

    # Validate filename
    if not file.filename.endswith(('.jpg', '.jpeg', '.png', '.gif')):
        raise HTTPException(
            status_code=400,
            detail='Invalid file extension'
        )

    return {'filename': file.filename, 'size': len(contents)}
验证文件上传。
python
from fastapi import FastAPI, File, UploadFile, HTTPException
from typing import List

app = FastAPI()

@app.post('/upload')
async def upload_file(file: UploadFile = File(...)):
    # Validate file type
    allowed_types = ['image/jpeg', 'image/png', 'image/gif']
    if file.content_type not in allowed_types:
        raise HTTPException(
            status_code=400,
            detail=f'File type {file.content_type} not allowed'
        )

    # Validate file size
    contents = await file.read()
    max_size = 5 * 1024 * 1024  # 5MB
    if len(contents) > max_size:
        raise HTTPException(
            status_code=400,
            detail='File too large (max 5MB)'
        )

    # Validate filename
    if not file.filename.endswith(('.jpg', '.jpeg', '.png', '.gif')):
        raise HTTPException(
            status_code=400,
            detail='Invalid file extension'
        )

    return {'filename': file.filename, 'size': len(contents)}

Multiple files

Multiple files

@app.post('/upload-multiple') async def upload_multiple_files(files: List[UploadFile] = File(...)): if len(files) > 10: raise HTTPException( status_code=400, detail='Maximum 10 files allowed' )
results = []
for file in files:
    contents = await file.read()
    results.append({
        'filename': file.filename,
        'size': len(contents)
    })

return results
undefined
@app.post('/upload-multiple') async def upload_multiple_files(files: List[UploadFile] = File(...)): if len(files) > 10: raise HTTPException( status_code=400, detail='Maximum 10 files allowed' )
results = []
for file in files:
    contents = await file.read()
    results.append({
        'filename': file.filename,
        'size': len(contents)
    })

return results
undefined

Form Data Validation

表单数据验证

Validating form data submissions.
python
from fastapi import FastAPI, Form
from pydantic import BaseModel, ValidationError

app = FastAPI()
验证表单数据提交。
python
from fastapi import FastAPI, Form
from pydantic import BaseModel, ValidationError

app = FastAPI()

Simple form

Simple form

@app.post('/login') async def login( username: str = Form(..., min_length=3), password: str = Form(..., min_length=8) ): return {'username': username}
@app.post('/login') async def login( username: str = Form(..., min_length=3), password: str = Form(..., min_length=8) ): return {'username': username}

Form with validation model

Form with validation model

class LoginForm(BaseModel): username: str = Field(..., min_length=3) password: str = Field(..., min_length=8)
@app.post('/login-validated') async def login_validated( username: str = Form(...), password: str = Form(...) ): try: form = LoginForm(username=username, password=password) return {'username': form.username} except ValidationError as e: raise HTTPException(status_code=422, detail=e.errors())
class LoginForm(BaseModel): username: str = Field(..., min_length=3) password: str = Field(..., min_length=8)
@app.post('/login-validated') async def login_validated( username: str = Form(...), password: str = Form(...) ): try: form = LoginForm(username=username, password=password) return {'username': form.username} except ValidationError as e: raise HTTPException(status_code=422, detail=e.errors())

Form with file

Form with file

@app.post('/profile') async def update_profile( name: str = Form(..., min_length=1), bio: str = Form(None, max_length=500), avatar: UploadFile = File(None) ): result = {'name': name, 'bio': bio} if avatar: result['avatar_filename'] = avatar.filename return result
undefined
@app.post('/profile') async def update_profile( name: str = Form(..., min_length=1), bio: str = Form(None, max_length=500), avatar: UploadFile = File(None) ): result = {'name': name, 'bio': bio} if avatar: result['avatar_filename'] = avatar.filename return result
undefined

Advanced Patterns

高级模式

Discriminated unions and recursive models.
python
from pydantic import BaseModel, Field, Discriminator
from typing import Literal, Union, List
可区分联合类型与递归模型。
python
from pydantic import BaseModel, Field, Discriminator
from typing import Literal, Union, List

Discriminated unions

Discriminated unions

class Cat(BaseModel): pet_type: Literal['cat'] meows: int
class Dog(BaseModel): pet_type: Literal['dog'] barks: float
Pet = Union[Cat, Dog]
class PetOwner(BaseModel): name: str pet: Pet
@app.post('/pets') async def create_pet(owner: PetOwner): # Automatically discriminates based on pet_type return owner
class Cat(BaseModel): pet_type: Literal['cat'] meows: int
class Dog(BaseModel): pet_type: Literal['dog'] barks: float
Pet = Union[Cat, Dog]
class PetOwner(BaseModel): name: str pet: Pet
@app.post('/pets') async def create_pet(owner: PetOwner): # Automatically discriminates based on pet_type return owner

Recursive models

Recursive models

class TreeNode(BaseModel): value: int children: List['TreeNode'] = []
TreeNode.model_rebuild() # Required for recursive models
@app.post('/tree') async def create_tree(tree: TreeNode): return tree
class TreeNode(BaseModel): value: int children: List['TreeNode'] = []
TreeNode.model_rebuild() # Required for recursive models
@app.post('/tree') async def create_tree(tree: TreeNode): return tree

Generic models

Generic models

from typing import TypeVar, Generic
T = TypeVar('T')
class Response(BaseModel, Generic[T]): data: T message: str success: bool = True
class UserData(BaseModel): id: int name: str
@app.get('/user/{user_id}', response_model=Response[UserData]) async def get_user(user_id: int): user = UserData(id=user_id, name='John Doe') return Response(data=user, message='User retrieved')
undefined
from typing import TypeVar, Generic
T = TypeVar('T')
class Response(BaseModel, Generic[T]): data: T message: str success: bool = True
class UserData(BaseModel): id: int name: str
@app.get('/user/{user_id}', response_model=Response[UserData]) async def get_user(user_id: int): user = UserData(id=user_id, name='John Doe') return Response(data=user, message='User retrieved')
undefined

When to Use This Skill

何时使用此技能

Use fastapi-validation when:
  • Building APIs that require strict input validation
  • Ensuring type safety across request and response models
  • Implementing complex validation rules and business logic
  • Converting between database models and API schemas
  • Documenting API schemas with OpenAPI
  • Preventing invalid data from entering your system
  • Building forms with server-side validation
  • Handling file uploads with validation
  • Creating reusable validation patterns
在以下场景中使用fastapi-validation:
  • 构建需要严格输入验证的API
  • 确保请求与响应模型的类型安全
  • 实现复杂的验证规则与业务逻辑
  • 在数据库模型与API schema之间进行转换
  • 使用OpenAPI记录API schema
  • 防止无效数据进入系统
  • 构建具备服务端验证的表单
  • 处理带验证的文件上传
  • 创建可复用的验证模式

FastAPI Validation Best Practices

FastAPI验证最佳实践

  1. Use specific types - Use EmailStr, HttpUrl, UUID instead of plain str for better validation
  2. Separate request and response - Create different models for input and output
  3. Leverage computed fields - Use computed fields for derived values instead of manual calculation
  4. Validate early - Validate at API boundary before business logic
  5. Custom validators - Create reusable validators for common patterns
  6. Meaningful error messages - Provide clear, actionable error messages
  7. Use aliases - Handle different naming conventions (camelCase, snake_case) with aliases
  8. Exclude sensitive data - Always exclude passwords and secrets from responses
  9. ORM mode - Enable from_attributes for database model conversion
  10. Document examples - Use json_schema_extra to provide example data
  1. 使用特定类型 - 使用EmailStr、HttpUrl、UUID而非普通str,以获得更好的验证效果
  2. 分离请求与响应 - 为输入和输出创建不同的模型
  3. 利用计算字段 - 使用计算字段处理派生值,而非手动计算
  4. 尽早验证 - 在业务逻辑之前,在API边界处进行验证
  5. 自定义验证器 - 为常见模式创建可复用的验证器
  6. 有意义的错误消息 - 提供清晰、可操作的错误消息
  7. 使用别名 - 通过别名处理不同的命名约定(camelCase、snake_case)
  8. 排除敏感数据 - 始终从响应中排除密码和机密信息
  9. ORM模式 - 启用from_attributes以实现数据库模型转换
  10. 文档示例 - 使用json_schema_extra提供示例数据

FastAPI Validation Common Pitfalls

FastAPI验证常见陷阱

  1. Missing response_model - Not using response_model exposes all fields including secrets
  2. Incorrect Field usage - Using Field without ... for required fields makes them optional
  3. Validator order - Validators run in definition order, dependencies matter
  4. Coercion confusion - Pydantic coerces types by default, use strict mode when needed
  5. Recursive model rebuild - Forgetting model_rebuild() on recursive models causes errors
  6. Form data limitations - Form data doesn't support nested models directly
  7. List validation - Not setting max_length on lists can allow resource exhaustion
  8. Regex complexity - Complex regex patterns can cause performance issues
  9. Timezone handling - datetime fields need explicit timezone handling
  10. Union validation - Union types validate in order, put more specific types first
  1. 缺少response_model - 不使用response_model会暴露所有字段,包括机密信息
  2. Field使用错误 - 必填字段未使用Field(...)会被设为可选
  3. 验证器顺序 - 验证器按定义顺序运行,依赖关系很重要
  4. 类型转换混淆 - Pydantic默认会进行类型转换,必要时使用严格模式
  5. 递归模型重建 - 递归模型忘记调用model_rebuild()会导致错误
  6. 表单数据限制 - 表单数据不直接支持嵌套模型
  7. 列表验证 - 不为列表设置max_length可能导致资源耗尽
  8. 正则表达式复杂度 - 复杂的正则表达式可能导致性能问题
  9. 时区处理 - datetime字段需要显式的时区处理
  10. 联合类型验证 - 联合类型按顺序验证,应将更具体的类型放在前面

Resources

资源