数据验证
数据验证是 FastAPI 的核心特性之一。通过 Pydantic,FastAPI 提供了强大的声明式数据验证功能,让你只需定义数据模型,就能自动获得验证、转换和文档生成。
Pydantic 基础
Pydantic 是一个数据验证库,使用 Python 类型注解来定义数据模型。FastAPI 内部使用 Pydantic 处理所有数据验证。
创建模型
from pydantic import BaseModel
class User(BaseModel):
"""用户模型"""
id: int
name: str
email: str
is_active: bool = True # 带默认值的字段
模型实例化
# 从字典创建
user = User(id=1, name="张三", email="[email protected]")
# 从 JSON 字符串创建
user = User.model_validate_json('{"id": 1, "name": "张三", "email": "[email protected]"}')
# 访问属性
print(user.name) # "张三"
print(user.is_active) # True(使用默认值)
模型转换为字典
# 转换为字典
user_dict = user.model_dump()
# {'id': 1, 'name': '张三', 'email': '[email protected]', 'is_active': True}
# 转换为 JSON 字符串
user_json = user.model_dump_json()
# '{"id": 1, "name": "张三", "email": "[email protected]", "is_active": true}'
字段类型
基本类型
from pydantic import BaseModel
from datetime import datetime, date, time, timedelta
from uuid import UUID
from pathlib import Path
class Demo(BaseModel):
# 数值类型
integer: int
floating: float
boolean: bool
# 字符串类型
text: str
# 时间类型
dt: datetime
d: date
t: time
td: timedelta
# 其他类型
uuid: UUID
path: Path
可选类型
from typing import Optional
class User(BaseModel):
# Python 3.10+ 语法
nickname: str | None = None
# 兼容语法
bio: Optional[str] = None
集合类型
from typing import List, Dict, Set
class Collections(BaseModel):
# 列表
tags: list[str]
# 或
items: List[int]
# 字典
metadata: dict[str, str]
# 或
scores: Dict[str, int]
# 集合
unique_ids: set[int]
# 或
ids: Set[int]
嵌套模型
class Address(BaseModel):
street: str
city: str
country: str = "中国"
class User(BaseModel):
name: str
address: Address # 嵌套单个模型
addresses: list[Address] # 嵌套模型列表
使用示例:
user = User(
name="张三",
address={"street": "南京路", "city": "上海"},
addresses=[
{"street": "北京路", "city": "北京"},
{"street": "广州路", "city": "广州"}
]
)
字段验证
Field 函数
使用 Field 函数添加验证规则和元数据:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(
..., # 必需字段
min_length=1, # 最小长度
max_length=100, # 最大长度
description="商品名称"
)
price: float = Field(
...,
gt=0, # 大于 0
le=10000, # 小于等于 10000
description="商品价格"
)
quantity: int = Field(
default=1, # 默认值
ge=0, # 大于等于 0
description="商品数量"
)
description: str | None = Field(
None,
max_length=500,
description="商品描述"
)
数值验证
| 参数 | 含义 | 示例 |
|---|---|---|
gt | 大于 | gt=0 值必须大于 0 |
ge | 大于等于 | ge=1 值必须大于等于 1 |
lt | 小于 | lt=100 值必须小于 100 |
le | 小于等于 | le=1000 值必须小于等于 1000 |
multiple_of | 是某数的倍数 | multiple_of=5 必须是 5 的倍数 |
字符串验证
from pydantic import BaseModel, Field
class User(BaseModel):
username: str = Field(
...,
min_length=3,
max_length=20,
pattern=r'^[a-zA-Z0-9_]+$' # 正则表达式
)
email: str = Field(
...,
pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$'
)
特殊字符串类型
Pydantic 提供了一些特殊的字符串类型:
from pydantic import BaseModel, EmailStr, HttpUrl, AnyUrl, IPvAnyAddress
class Contact(BaseModel):
email: EmailStr # 邮箱格式验证
website: HttpUrl # HTTP URL 验证
api_url: AnyUrl # 任意 URL 验证
server_ip: IPvAnyAddress # IP 地址验证
安装额外依赖:
pip install email-validator
# 或
pip install "pydantic[email]"
列表验证
from pydantic import BaseModel, Field
class Config(BaseModel):
tags: list[str] = Field(
default_factory=list, # 默认空列表(避免可变默认参数问题)
min_length=1, # 列表最小长度
max_length=10 # 列表最大长度
)
scores: list[int] = Field(
...,
min_length=3,
max_length=5
)
自定义验证器
field_validator
使用 @field_validator 装饰器添加自定义验证逻辑:
from pydantic import BaseModel, field_validator
class User(BaseModel):
name: str
username: str
@field_validator('username')
@classmethod
def username_alphanumeric(cls, v: str) -> str:
"""验证用户名只包含字母和数字"""
if not v.isalnum():
raise ValueError('用户名只能包含字母和数字')
return v.lower() # 转换为小写
@field_validator('name')
@classmethod
def name_must_not_empty(cls, v: str) -> str:
"""验证名称非空"""
if not v or not v.strip():
raise ValueError('名称不能为空')
return v.strip()
model_validator
验证整个模型或多个字段之间的关系:
from pydantic import BaseModel, model_validator
class Event(BaseModel):
start_date: date
end_date: date
@model_validator(mode='after')
def check_dates(self) -> 'Event':
"""验证结束日期必须晚于开始日期"""
if self.end_date <= self.start_date:
raise ValueError('结束日期必须晚于开始日期')
return self
验证前后处理
from pydantic import BaseModel, field_validator
class User(BaseModel):
username: str
@field_validator('username', mode='before')
@classmethod
def normalize_username(cls, v):
"""验证前预处理:去除空格并转小写"""
if isinstance(v, str):
return v.strip().lower()
return v
@field_validator('username', mode='after')
@classmethod
def check_username(cls, v):
"""验证后检查:确保不为空"""
if not v:
raise ValueError('用户名不能为空')
return v
模型配置
Config 类
使用内部 Config 类配置模型行为:
from pydantic import BaseModel
class User(BaseModel):
name: str
password: str
class Config:
# 额外字段处理
extra = 'forbid' # 禁止额外字段
# extra = 'ignore' # 忽略额外字段
# 字段别名
populate_by_name = True # 允许通过原字段名或别名访问
# 验证设置
validate_assignment = True # 赋值时也进行验证
validate_default = True # 验证默认值
# JSON Schema 示例
json_schema_extra = {
"example": {
"name": "张三",
"password": "secret"
}
}
model_config
Pydantic v2 推荐使用 model_config:
from pydantic import BaseModel, ConfigDict
class User(BaseModel):
model_config = ConfigDict(
extra='forbid',
populate_by_name=True,
validate_assignment=True,
)
name: str
password: str
字段别名
定义别名
当 API 字段名与 Python 属性名不同时使用别名:
from pydantic import BaseModel, Field
class User(BaseModel):
name: str = Field(alias='userName')
full_name: str = Field(alias='fullName')
class Config:
populate_by_name = True # 允许同时使用别名和原名称
使用示例:
# 使用别名
user = User(userName='张三', fullName='张三丰')
# 如果设置了 populate_by_name=True,也可以使用原名称
user = User(name='张三', full_name='张三丰')
序列化别名
控制输出时的字段名:
class User(BaseModel):
name: str = Field(
alias='input_name', # 输入时的别名
serialization_alias='output_name' # 输出时的别名
)
默认值与默认工厂
默认值
class Item(BaseModel):
name: str
quantity: int = 0 # 简单默认值
is_active: bool = True
默认工厂
对于可变类型(如列表、字典),使用 default_factory:
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str
tags: list[str] = Field(default_factory=list) # 正确
metadata: dict = Field(default_factory=dict) # 正确
# 错误示范:可变默认值会被共享!
# tags: list[str] = [] # 不要这样做
工厂函数示例
from datetime import datetime
from uuid import uuid4
class Item(BaseModel):
id: str = Field(default_factory=lambda: str(uuid4()))
created_at: datetime = Field(default_factory=datetime.now)
tags: list[str] = Field(default_factory=list)
类型强制转换
Pydantic 会尝试将输入转换为声明的类型:
class Item(BaseModel):
quantity: int
price: float
is_active: bool
# 自动类型转换
item = Item(
quantity="42", # 字符串 "42" 转为整数 42
price="99.9", # 字符串转为浮点数
is_active="yes" # 字符串转为布尔值
)
print(item.quantity) # 42 (int)
print(item.price) # 99.9 (float)
print(item.is_active) # True (bool)
布尔值转换规则
以下值会被转换为 True:
"True","true","1","on","yes"1,True
以下值会被转换为 False:
"False","false","0","off","no"0,False
错误处理
当验证失败时,Pydantic 抛出 ValidationError:
from pydantic import BaseModel, ValidationError
class User(BaseModel):
name: str
age: int
try:
User(name="张三", age="abc")
except ValidationError as e:
print(e.error_count()) # 错误数量
print(e.errors()) # 错误详情列表
print(e.json()) # JSON 格式的错误信息
错误信息示例:
[
{
"type": "int_parsing",
"loc": ["age"],
"msg": "Input should be a valid integer, unable to parse string as an integer",
"input": "abc"
}
]
完整示例:商品模型
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Annotated
from uuid import UUID, uuid4
from pydantic import BaseModel, Field, EmailStr, field_validator, model_validator
class Category(str, Enum):
"""商品分类"""
ELECTRONICS = "electronics"
CLOTHING = "clothing"
FOOD = "food"
BOOKS = "books"
class Review(BaseModel):
"""商品评论"""
user_id: UUID
rating: Annotated[int, Field(ge=1, le=5, description="评分 1-5")]
comment: str | None = Field(None, max_length=500)
created_at: datetime = Field(default_factory=datetime.now)
class Product(BaseModel):
"""商品模型"""
model_config = ConfigDict(
extra='forbid',
json_schema_extra={
"example": {
"name": "智能手机",
"price": "2999.00",
"category": "electronics",
"stock": 100
}
}
)
id: UUID = Field(default_factory=uuid4, description="商品唯一ID")
name: Annotated[str, Field(
min_length=1,
max_length=100,
description="商品名称"
)]
description: str | None = Field(None, max_length=2000)
price: Decimal = Field(
...,
gt=0,
decimal_places=2,
description="商品价格"
)
category: Category
stock: int = Field(default=0, ge=0, description="库存数量")
is_available: bool = True
tags: list[str] = Field(default_factory=list)
reviews: list[Review] = Field(default_factory=list)
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
@field_validator('name')
@classmethod
def name_must_not_be_empty(cls, v: str) -> str:
"""名称不能为空"""
if not v.strip():
raise ValueError('商品名称不能为空')
return v.strip()
@field_validator('tags')
@classmethod
def tags_unique(cls, v: list[str]) -> list[str]:
"""标签去重"""
return list(set(v))
@model_validator(mode='after')
def check_availability(self) -> 'Product':
"""库存为0时自动设置为不可用"""
if self.stock == 0:
self.is_available = False
return self
小结
本章我们学习了:
- Pydantic 基础:创建模型、实例化、序列化
- 字段类型:基本类型、可选类型、集合类型、嵌套模型
- 字段验证:数值验证、字符串验证、列表验证
- 自定义验证器:
@field_validator、@model_validator - 模型配置:Config 类、model_config
- 字段别名:输入别名、序列化别名
- 默认值:默认值、默认工厂
Pydantic 的优势:
- 类型安全的自动转换
- 声明式的验证规则
- 清晰的错误信息
- 与 FastAPI 无缝集成
- 自动生成 OpenAPI 文档
练习
- 创建一个用户注册模型,包含用户名、邮箱、密码(需要确认)
- 实现一个日期范围模型,验证结束日期晚于开始日期
- 创建一个商品模型,价格必须是正数且保留两位小数
- 实现自定义验证器,验证密码强度(至少8位,包含大小写字母和数字)