BaseModel 基础定义
Pydantic 的核心是 BaseModel:继承它并用类型注解定义字段,Pydantic 自动处理解析、验证和序列化。
Schema(模式)
数据结构的形式化描述,定义了数据应有哪些字段、每个字段的类型、是否必填、合法范围等约束。Pydantic 模型本身就是 Schema 的 Python 代码表示,同时可以生成 JSON Schema(供 OpenAPI 文档使用)。
序列化(Serialization)
将 Python 对象转换为可传输格式(如 JSON 字符串或 dict)的过程。Pydantic v2 提供 model.model_dump()(→ dict)和 model.model_dump_json()(→ JSON 字符串)。
反序列化(Deserialization)
将 JSON 数据解析并转换为 Python 对象的过程,同时进行类型验证和类型转换。例如 Pydantic 会将 JSON 字符串 "2025-01-01" 自动转换为 Python 的 date 对象。
from pydantic import BaseModel
from datetime import datetime, date
from typing import Optional
class User(BaseModel):
# 必填字段:没有默认值
id: int
username: str
email: str
# 可选字段:有默认值
full_name: str | None = None # Python 3.10+ 语法
is_active: bool = True
score: float = 0.0
tags: list[str] = [] # 列表,默认空列表
# 日期时间:自动解析 ISO 8601 字符串
created_at: datetime = datetime.now()
birthday: date | None = None
# 创建实例(自动类型转换)
user = User(id="1", username="alice", email="alice@example.com")
# id 字符串 "1" 自动转换为整数 1
# 序列化
user_dict = user.model_dump() # → Python dict
user_json = user.model_dump_json() # → JSON 字符串
# 从字典/JSON 解析
user2 = User.model_validate({"id": 2, "username": "bob", "email": "bob@example.com"})
user3 = User.model_validate_json('{"id": 3, "username": "charlie", "email": "c@example.com"}')
Field() 进阶配置
Field() 为单个字段提供额外的元数据和验证约束,推荐使用 Annotated 语法(Pydantic v2 推荐方式):
from pydantic import BaseModel, Field
from typing import Annotated
class Product(BaseModel):
# 字符串约束
name: Annotated[str, Field(
min_length=1, max_length=100,
description="商品名称",
examples=["iPhone 16", "MacBook Pro"]
)]
# 正则验证
sku: Annotated[str, Field(
pattern=r"^[A-Z]{2}-\d{6}$",
description="SKU 格式:2大写字母-6数字,如 AB-123456"
)]
# 数字约束
price: Annotated[float, Field(
gt=0, # greater than(严格大于)
le=99999, # less than or equal(小于等于)
description="价格,单位元"
)]
# 整数约束(ge/le: ≥/≤,gt/lt: >/<)
stock: Annotated[int, Field(ge=0, description="库存数量")] = 0
# 别名:JSON 键名与 Python 属性名不同
category_id: Annotated[int, Field(alias="categoryId")]
# 排除字段(不参与序列化)
_internal_code: str = Field(default="", exclude=True)
model_config = {"populate_by_name": True} # 允许用别名或原名创建
# 验证示例
try:
bad = Product(name="", sku="invalid", price="-1", category_id=1)
except ValueError as e:
print(e) # 输出详细的验证错误信息(包含字段名、错误类型、说明)
嵌套模型与复杂类型
from pydantic import BaseModel
from typing import Union
from decimal import Decimal
class Address(BaseModel):
street: str
city: str
country: str = "CN"
postal_code: str | None = None
class OrderItem(BaseModel):
product_id: int
quantity: int
unit_price: Decimal # Decimal 比 float 更精确,适合金额
class Order(BaseModel):
id: int
customer_name: str
# 嵌套单个模型
shipping_address: Address
# 嵌套模型列表
items: list[OrderItem]
# Dict 类型
metadata: dict[str, str] = {}
# Union 类型(多种可能类型)
discount: float | None = None
@property
def total(self) -> Decimal:
return sum(i.unit_price * i.quantity for i in self.items)
# 嵌套模型可以直接传字典(自动转换)
order = Order(
id=1,
customer_name="张三",
shipping_address={"street": "人民路 1 号", "city": "上海"},
items=[
{"product_id": 101, "quantity": 2, "unit_price": "99.99"},
{"product_id": 102, "quantity": 1, "unit_price": "299.00"},
]
)
print(order.total) # Decimal('498.98')
验证器
当字段约束不足以满足需求时,使用验证器编写自定义验证逻辑:
from pydantic import BaseModel, Field, field_validator, model_validator
from typing import Self
class UserRegistration(BaseModel):
username: str
email: str
password: str
confirm_password: str
age: int
# ── @field_validator:单字段验证 ─────────────────────
@field_validator("username")
@classmethod
def username_must_be_alphanumeric(cls, v: str) -> str:
if not v.replace("_", "").isalnum():
raise ValueError("用户名只能包含字母、数字和下划线")
return v.lower() # 转小写(可在验证器中转换值)
@field_validator("email")
@classmethod
def email_must_contain_at(cls, v: str) -> str:
if "@" not in v:
raise ValueError("邮箱格式无效")
return v.lower()
@field_validator("password")
@classmethod
def password_strength(cls, v: str) -> str:
if len(v) < 8:
raise ValueError("密码至少 8 位")
if not any(c.isdigit() for c in v):
raise ValueError("密码必须包含至少一个数字")
return v
# ── @model_validator:跨字段验证 ─────────────────────
# mode="after":在所有字段验证通过后运行
@model_validator(mode="after")
def passwords_match(self) -> Self:
if self.password != self.confirm_password:
raise ValueError("两次输入的密码不一致")
if self.age < 18:
raise ValueError("用户必须年满 18 岁")
return self
model_config 配置项
from pydantic import BaseModel, ConfigDict
class ArticleSchema(BaseModel):
# model_config 替代了 Pydantic v1 的内部 class Config
model_config = ConfigDict(
# 允许从 ORM 对象(SQLAlchemy model)直接创建
from_attributes=True,
# 严格模式:不做自动类型转换("1" 不会转为 1)
strict=False,
# 序列化时字段排序(字母顺序)
str_strip_whitespace=True, # 自动去除字符串首尾空格
# 在文档中展示的示例
json_schema_extra={
"example": {
"title": "FastAPI 实战指南",
"content": "FastAPI 是...",
"author_id": 1
}
}
)
title: str
content: str
author_id: int
# from_attributes=True 的实际用途:从 SQLAlchemy ORM 对象转换
# 假设 article_orm 是 SQLAlchemy 查询结果
# article_schema = ArticleSchema.model_validate(article_orm)
模型继承与组合
from pydantic import BaseModel
from datetime import datetime
# ── 基础模型(创建时的输入)──────────────────────────────
class UserBase(BaseModel):
username: str
email: str
full_name: str | None = None
# ── 创建请求(额外加密码字段)────────────────────────────
class UserCreate(UserBase):
password: str
# ── 更新请求(所有字段可选)──────────────────────────────
class UserUpdate(BaseModel):
username: str | None = None
email: str | None = None
full_name: str | None = None
password: str | None = None
# ── 数据库读取(包含 ID 和时间戳)────────────────────────
class UserInDB(UserBase):
id: int
hashed_password: str
created_at: datetime
is_active: bool = True
model_config = {"from_attributes": True}
# ── API 响应(不含敏感信息)──────────────────────────────
class UserResponse(UserBase):
id: int
created_at: datetime
is_active: bool
模型继承最佳实践
遵循"输入/输出分离"原则:UserCreate(包含密码,用于接收注册请求)、UserResponse(不含密码,用于返回用户信息)、UserInDB(包含 hashed_password,用于数据库操作)。这种模式确保了敏感字段不会意外泄露,是 FastAPI 项目的标准做法。
本章小结
Pydantic v2 是 FastAPI 数据层的基石。核心用法:BaseModel 定义结构、Field() 添加约束与文档、@field_validator 单字段验证、@model_validator 跨字段验证、model_config 控制序列化行为。下一章学习 FastAPI 最强大的功能之一:依赖注入系统。