FastAPI 教程

FastAPI Pydantic 模型

在 FastAPI 中,Pydantic 模型是数据验证、序列化和文档生成的绝对核心。FastAPI 正是通过读取 Pydantic 模型中的 Python 类型提示,来实现自动的数据校验和 Swagger 文档生成的。

目前 FastAPI 默认且强烈推荐使用 Pydantic V2(基于 Rust 的 pydantic-core,速度极快)。下面我将从基础定义字段控制进阶验证最佳实践,为你系统梳理 FastAPI 中的 Pydantic 模型。


一、 基础语法:定义你的第一个模型

只需继承 BaseModel,并使用 Python 的类型提示 (Type Hints) 即可。

纯文本
plaintext
from pydantic import BaseModel

class Item(BaseModel):
    name: str
    price: float
    is_offer: bool | None = None  # 可选字段,默认值为 None

在 FastAPI 中使用

纯文本
plaintext
from fastapi import FastAPI

app = FastAPI()

@app.post("/items/")
def create_item(item: Item):
    # FastAPI 会自动将请求的 JSON 解析并验证为 Item 对象
    # 如果 price 是字符串 "abc",会自动返回 422 错误
    return {"item_name": item.name, "item_price": item.price}

二、 强大的字段控制:Field 🌟

Field 是 Pydantic 的利器,用于为字段添加校验规则默认值文档描述示例(这些都会自动同步到 Swagger UI 中)。

纯文本
plaintext
from pydantic import BaseModel, Field
from typing import Annotated  # Python 3.9+ 推荐用法

class UserCreate(BaseModel):
    # 基础校验:长度限制
    username: str = Field(..., min_length=3, max_length=50, description="用户登录名")

    # 格式校验:内置的 EmailStr 需要安装 pydantic[email]
    email: str = Field(..., pattern=r'^[\w\.-]+@[\w\.-]+\.\w+$', description="电子邮箱")

    # 数值校验:ge (>=), le (<=), gt (>), lt (<)
    age: int = Field(default=18, ge=18, le=120, description="用户年龄")

    # 现代写法:使用 Annotated (更清晰,推荐)
    status: Annotated[str, Field(description="账户状态", examples=["active", "inactive"])] = "active"

💡 提示:... (Ellipsis) 表示该字段是必填项 (Required)。


三、 Pydantic 在 FastAPI 中的“双重角色”

同一个模型,既可以用作请求体验证,也可以用作响应模型过滤

1. 作为 Request Body (输入验证)

确保客户端传来的数据符合规范,不符合则直接拦截,保护业务逻辑。

2. 作为 Response Model (输出过滤与序列化)

这是 Pydantic 最优雅的特性。它可以自动过滤掉模型中未定义的字段(例如密码、内部 ID),并确保返回的数据类型正确。

纯文本
plaintext
class UserIn(BaseModel):
    username: str
    password: str  # 敏感信息

class UserOut(BaseModel):
    username: str
    # 注意:这里没有 password 字段

@app.post("/users/", response_model=UserOut)
def register_user(user: UserIn):
    # 模拟保存到数据库
    db_user = {"username": user.username, "password": "hashed_" + user.password, "internal_id": 999}

    # 即使返回了包含 password 和 internal_id 的字典
    # response_model=UserOut 也会自动将其过滤,只返回 username
    return db_user 
    # 客户端实际收到: {"username": "alice"}

四、 进阶技巧 (Pydantic V2 专属)

1. 嵌套模型 (Nested Models)

轻松处理复杂的 JSON 结构。

纯文本
plaintext
class Address(BaseModel):
    city: str
    zipcode: str

class User(BaseModel):
    name: str
    address: Address  # 嵌套另一个模型

# 接收的 JSON: {"name": "Alice", "address": {"city": "Beijing", "zipcode": "100000"}}

2. 模型继承 (Model Inheritance)

复用字段定义,避免重复代码。

纯文本
plaintext
class BaseUser(BaseModel):
    username: str
    email: str

class UserCreate(BaseUser):
    password: str  # 继承 username, email,并新增 password

class UserUpdate(BaseUser):
    password: str | None = None  # 继承 username, email,password 变为可选

3. 自定义验证器 (@model_validator)

当单个字段的规则不够,需要跨字段校验复杂逻辑时使用。

纯文本
plaintext
from pydantic import BaseModel, model_validator

class Event(BaseModel):
    start_time: int
    end_time: int

    @model_validator(mode='after')
    def check_times(self) -> 'Event':
        if self.end_time <= self.start_time:
            raise ValueError("结束时间必须晚于开始时间")
        return self

4. 计算字段 (@computed_field) (Pydantic V2 新增)

有些字段不需要客户端传入,而是根据其他字段计算得出,且需要包含在响应中。

纯文本
plaintext
from pydantic import BaseModel, computed_field

class Rectangle(BaseModel):
    width: float
    height: float

    @computed_field
    @property
    def area(self) -> float:
        return self.width * self.height

rect = Rectangle(width=10, height=5)
print(rect.model_dump()) 
# 输出: {'width': 10.0, 'height': 5.0, 'area': 50.0} (area 会自动包含在序列化结果中)

五、 ⚠️ 常见陷阱与最佳实践

1. Pydantic V1 vs V2 语法差异 (极易踩坑)

如果你看的是旧教程,请注意以下 V2 的变化:

  • ❌ 旧:item.dict() 👉 ✅ 新:item.model_dump()
  • ❌ 旧:item.json() 👉 ✅ 新:item.model_dump_json()
  • ❌ 旧:@validator / @root_validator 👉 ✅ 新:@field_validator / @model_validator
  • ❌ 旧:Field(default_factory=list) 👉 ✅ 新:依然支持,但也可以直接用 list 类型提示配合默认值。

2. 可变默认值陷阱

虽然 Pydantic V2 对可变默认值处理得比原生 Python 好,但最佳实践仍然是使用 default_factory 来生成新的列表或字典,避免多个实例共享同一个可变对象。

纯文本
plaintext
from pydantic import BaseModel, Field

class Team(BaseModel):
    # ✅ 推荐:每次实例化都会创建一个新的空列表
    members: list[str] = Field(default_factory=list)

3. 区分 Optional 和 赋予默认值

  • age: int | None:表示该字段可以None,但客户端必须显式传递 "age": null
  • age: int | None = None:表示该字段是可选的,客户端可以不传,FastAPI 会自动将其设为 None

4. 保持 Request 和 Response 模型分离

尽量不要用一个模型既做输入又做输出。输入模型(如 UserCreate)可能包含密码,而输出模型(如 UserOut)应包含数据库生成的 idcreated_at。分离它们能让代码意图更清晰。


总结:Pydantic 模型速查表

需求Pydantic 解决方案
定义数据结构继承 BaseModel + 类型提示
必填项field_name: str = Field(...)
可选且带默认值field_name: str | None = None
数值/字符串范围限制Field(ge=0, le=100, min_length=3)
跨字段复杂校验@model_validator(mode='after')
动态计算并返回的字段@computed_field + @property
将模型转为字典 (用于存数据库)model.model_dump()

掌握了 Pydantic,你就掌握了 FastAPI 数据流的“守门员”。你可以尝试用 Pydantic 定义一个你当前项目中的复杂业务对象(比如包含嵌套列表和自定义校验的订单模型),如果遇到问题,随时发给我帮你 Review!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注