Chapter 03

Pydantic 数据模型深度

深入掌握 Pydantic v2 的核心能力:字段定义、高级验证器、嵌套模型、序列化配置,构建健壮的数据层。

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 最强大的功能之一:依赖注入系统。