下载工作台
FastAPI 开发

Pydantic v2 模型与数据校验

试读上半部分 · 解锁后可读全文

第 3 章 · Pydantic v2 模型与数据校验

本章目标:掌握 Pydantic v2BaseModelField 字段约束与 field_validator / model_validator;理解模型配置(model_config)、序列化与反序列化;实现嵌套模型表达商品-分类-规格等复杂结构;在 svc-demo 中用 Pydantic 替代 ch02 的字典假数据,为 ch04 请求绑定与 ch05 数据库映射打基础;能对照 Django Serializer 与 Flask Marshmallow 说明差异。

学时建议:4~5 小时(含 1.5 小时跟练)

前置:完成 fastapi-web ch02python-dev 中熟悉类型提示(list[str]Optional)更佳。


3.1 场景说明:从 dict 到强类型模型

ch02 的 _FAKE_PRODUCTS 使用裸字典,存在隐患:

问题dict 写法Pydantic 写法
字段拼写错误"nmae" 不报错校验失败,422 响应
价格负数无约束Field(ge=0)
嵌套分类手写嵌套 dictCategoryRead 嵌套模型
API 文档无字段说明自动生成 schema
ORM 转换手动 dict()model_validate / from_attributes

svc-demo 对外契约由 Pydantic 模型驱动,与 api.example.com 前端/SDK 对齐。

数据流(svc-demo)
  JSON 请求体 → Pydantic 解析校验 → 业务逻辑 → Pydantic 响应 → JSON

3.2 Pydantic v2 与 v1 关键差异

特性Pydantic v1Pydantic v2(本章)
校验装饰器@validator@field_validator
模型级校验@root_validator@model_validator
配置class Configmodel_config = ConfigDict(...)
ORM 模式orm_mode = Truefrom_attributes = True
性能较慢Rust 核心 pydantic-core,更快

FastAPI 0.100+ 默认绑定 Pydantic v2,本章全部使用 v2 语法。


3.3 BaseModel 基础

3.3.1 商品读取模型 app/schemas/product.py

from datetime import datetime
from decimal import Decimal

from pydantic import BaseModel, ConfigDict, Field


class ProductBase(BaseModel):
    sku: str = Field(..., min_length=3, max_length=32, examples=["SKU-001"])
    name: str = Field(..., min_length=1, max_length=128)
    price: Decimal = Field(..., ge=0, decimal_places=2, description="售价(元)")
    stock: int = Field(default=0, ge=0)


class ProductCreate(ProductBase):
    """创建商品时的入参(无 id)"""
    category_id: int = Field(..., gt=0)


class ProductRead(ProductBase):
    """对外响应(含 id、时间戳)"""
    model_config = ConfigDict(from_attributes=True)

    id: int
    is_active: bool = True
    created_at: datetime | None = None

3.3.2 独立脚本验证

item = ProductCreate(sku="SKU-100", name="演示商品", price=Decimal("19.99"), category_id=1)
print(item.model_dump_json())  # 合法
# ProductCreate(sku="AB", price=Decimal("-1"), ...)  # 触发 ValidationError

3.4 Field 详解

from pydantic import BaseModel, Field


class SearchQuery(BaseModel):
    q: str = Field(
        ...,
        min_length=1,
        max_length=64,
        description="搜索关键词",
        json_schema_extra={"example": "键盘"},
    )
    page: int = Field(1, ge=1, description="页码,从 1 开始")
    page_size: int = Field(20, ge=1, le=100, alias="pageSize")

    model_config = ConfigDict(populate_by_name=True)  # 同时接受 pageSize 与 page_size
Field 参数含义示例
...必填name: str = Field(...)
default / default_factory默认值tags: list[str] = Field(default_factory=list)
ge / le / gt / lt数值边界price: Decimal = Field(ge=0)
min_length / max_length字符串长度sku: str = Field(min_length=3)
pattern正则sku: str = Field(pattern=r"^SKU-\d{3}$")
descriptionOpenAPI 文档说明出现在 /docs
alias外部字段名JSON 用 pageSize,Python 用 page_size

3.5 field_validator 字段校验器

Pydantic v2 使用 @field_validator,需指定 mode

from pydantic import BaseModel, field_validator, ValidationError


class ProductCreate(BaseModel):
    sku: str
    name: str
    price: float

    @field_validator("sku")
    @classmethod
    def sku_must_uppercase(cls, v: str) -> str:
        return v.strip().upper()

    @field_validator("name")
    @classmethod
    def name_not_blank(cls, v: str) -> str:
        v = v.strip()
        if not v:
            raise ValueError("商品名称不能为空")
        return v

    @field_validator("price")
    @classmethod
    def price_reasonable(cls, v: float) -> float:
        if v > 999_999:
            raise ValueError("价格超出允许范围")
        return round(v, 2)
mode时机典型用途
before(默认)类型转换前清洗字符串、预处理
after类型转换后业务规则校验

多个字段联合校验用 model_validator

from pydantic import BaseModel, model_validator


class PriceTier(BaseModel):
    retail_price: float
    wholesale_price: float

    @model_validator(mode="after")
    def wholesale_cheaper_than_retail(self):
        if self.wholesale_price > self.retail_price:
            raise ValueError("批发价不能高于零售价")
        return self

3.6 嵌套模型

3.6.1 分类与商品嵌套

以下内容需解锁后阅读

试读已结束。解锁本章 ¥5.00,或开通年度会员畅读全部教程。
年度会员 ¥199.00/年; 小紫 AI 工作台有效会员 ¥99.00/年

正文仅在服务端鉴权后下发,未付费无法获取下半部分内容。