FastAPI Pydantic 模型(手把手讲解)

FastAPI Pydantic 模型:构建类型安全的 API 数据契约

在现代 Web 开发中,接口的稳定性和数据的准确性越来越重要。尤其是在使用 FastAPI 这类基于 Python 的高性能框架时,如何高效、安全地处理请求和响应数据,成为每个开发者必须面对的课题。而 Pydantic 模型,正是解决这一问题的核心工具。它不仅让数据校验变得简单,还为整个 API 提供了清晰的“契约”文档。

FastAPI Pydantic 模型,本质上就是用 Python 类来定义数据结构,它在 FastAPI 中扮演着“数据守门员”的角色。你可以把它想象成一个自动化的安检系统:用户提交的数据,必须通过这个模型的“安检门”才能进入系统。如果数据格式不对、字段缺失、类型错误,系统会立刻拒绝并返回清晰的错误提示。

这不仅提升了代码的健壮性,也让前后端协作更加顺畅。接下来,我们就从基础开始,一步步拆解这个强大的组合。

什么是 Pydantic 模型?它为何如此重要?

Pydantic 是一个用于数据验证和设置管理的 Python 库。它基于 Python 的类型提示(Type Hints),让你用声明式的方式定义数据结构。在 FastAPI 中,它被深度集成,成为处理请求体、查询参数、响应体的默认方式。

简单来说,Pydantic 模型就是“数据模板”。你定义一个类,类中的每个字段都代表一个数据项,并指定它的类型和约束条件。FastAPI 会自动用这个模型去校验传入的数据。

比如,我们要定义一个用户注册的请求数据,可以这样写:

from pydantic import BaseModel
from typing import Optional

class UserCreate(BaseModel):
    username: str
    email: str
    age: int
    is_active: Optional[bool] = True  # 可选字段,缺省值为 True

这里的关键是 BaseModel,它是所有 Pydantic 模型的基类。每个字段都用 : 类型 的方式声明类型,如 strint。而 Optional[bool] 表示该字段可以是 boolNone,且默认值为 True

当你把这个模型用在 FastAPI 路由中时,FastAPI 会自动校验传入的数据是否符合这个结构。如果用户提交的 JSON 中 age 是字符串 "25",而不是整数 25,Pydantic 会立刻抛出错误,告诉你类型不匹配。

这种“类型先行”的设计,让开发过程更安全、更少出错。

创建数组与初始化:处理复杂数据结构

在实际项目中,我们很少只处理单一数据。很多时候需要处理列表、嵌套对象等复杂结构。Pydantic 也完美支持这些场景。

使用 List 类型处理数组

当你需要接收一个用户列表时,可以这样定义:

from pydantic import BaseModel
from typing import List

class UserList(BaseModel):
    users: List[UserCreate]  # users 是 UserCreate 类型的列表

这里的 List[UserCreate] 表示 users 字段必须是一个包含 UserCreate 对象的列表。FastAPI 会自动校验每个元素是否符合 UserCreate 的规则。

例如,以下 JSON 是合法的请求体:

{
  "users": [
    {
      "username": "alice",
      "email": "alice@example.com",
      "age": 28,
      "is_active": true
    },
    {
      "username": "bob",
      "email": "bob@example.com",
      "age": 30
    }
  ]
}

注意,第二个用户没有提供 is_active,但由于我们定义为 Optional[bool],所以是允许的。

嵌套模型:构建多层结构

当数据结构更复杂时,可以使用嵌套模型。例如,定义一个订单模型,其中包含用户信息和商品列表:

class Product(BaseModel):
    name: str
    price: float
    quantity: int

class Order(BaseModel):
    order_id: str
    user: UserCreate  # 嵌套另一个模型
    products: List[Product]
    total_amount: float

这样,当你接收一个订单数据时,FastAPI 会递归校验所有字段,包括 user 是否符合 UserCreate 的规则,products 是否是 Product 的列表等。

这种分层设计,让数据结构清晰可维护,也便于在多个接口中复用相同的模型。

字段验证与约束:让数据更“听话”

Pydantic 不仅能校验类型,还能设置更复杂的约束条件。这使得我们可以在数据进入系统之前,就过滤掉不符合业务规则的数据。

使用 Field 设置字段约束

Field 函数可以为字段添加额外的验证规则。例如,限制字符串长度、设置默认值、添加描述等。

from pydantic import BaseModel, Field
from typing import Optional

class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=20, description="用户名长度必须在 3 到 20 之间")
    email: str = Field(..., pattern=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
    age: int = Field(..., ge=18, le=120, description="年龄必须在 18 到 120 之间")
    is_active: Optional[bool] = True
    bio: Optional[str] = Field(None, max_length=500, description="个人简介,最多 500 字")

这里的关键点是:

  • ... 表示该字段为必填项(不提供默认值)。
  • min_length=3max_length=20 限制用户名长度。
  • pattern=r'^...$' 使用正则表达式验证邮箱格式。
  • ge=18 表示“大于等于 18”,le=120 表示“小于等于 120”。

如果用户提交的 username 是 "ab",系统会返回错误:“username 长度不能小于 3”。

自定义校验函数

对于更复杂的逻辑,可以使用 @validator 装饰器定义自定义校验规则。

from pydantic import BaseModel, validator

class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=20)
    email: str = Field(..., pattern=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$')
    age: int = Field(..., ge=18, le=120)
    is_active: Optional[bool] = True

    @validator('username')
    def username_must_be_lowercase(cls, v):
        if v != v.lower():
            raise ValueError('用户名必须全部小写')
        return v

    @validator('age')
    def age_cannot_be_too_young(cls, v):
        if v < 18:
            raise ValueError('用户必须年满 18 岁')
        return v

在这个例子中,我们添加了两个校验规则:

  • username_must_be_lowercase:确保用户名全为小写。
  • age_cannot_be_too_young:防止未成年人注册。

当数据不满足这些条件时,Pydantic 会抛出 ValidationError,FastAPI 会自动将其转换为 HTTP 422 错误响应,返回清晰的错误信息。

实际案例:构建一个完整的用户管理接口

让我们把前面的知识整合起来,创建一个完整的 FastAPI 接口,实现用户创建功能。

from fastapi import FastAPI
from pydantic import BaseModel, Field, validator
from typing import Optional, List

app = FastAPI(title="用户管理系统", version="1.0")

class UserCreate(BaseModel):
    username: str = Field(..., min_length=3, max_length=20, description="用户名,3-20 个字符")
    email: str = Field(..., pattern=r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$', description="邮箱格式必须正确")
    age: int = Field(..., ge=18, le=120, description="年龄必须在 18 到 120 之间")
    is_active: Optional[bool] = True
    bio: Optional[str] = Field(None, max_length=500, description="个人简介,最多 500 字")

    @validator('username')
    def username_must_be_lowercase(cls, v):
        if v != v.lower():
            raise ValueError('用户名必须全部小写')
        return v

    @validator('age')
    def age_cannot_be_too_young(cls, v):
        if v < 18:
            raise ValueError('用户必须年满 18 岁')
        return v

@app.post("/users/", response_model=dict)
def create_user(user: UserCreate):
    # 模型已自动校验通过,可以直接使用
    return {
        "message": f"用户 {user.username} 创建成功",
        "user": user.dict()  # 转为字典返回
    }

运行这个代码后,你可以用 Postman 或 curl 测试:

curl -X POST http://127.0.0.1:8000/users/ \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "email": "alice@example.com",
    "age": 25,
    "bio": "热爱编程的前端工程师"
  }'

如果数据合法,返回:

{
  "message": "用户 alice 创建成功",
  "user": {
    "username": "alice",
    "email": "alice@example.com",
    "age": 25,
    "is_active": true,
    "bio": "热爱编程的前端工程师"
  }
}

如果提交的数据不符合规则,比如 age=16,FastAPI 会返回:

{
  "detail": [
    {
      "loc": ["body", "age"],
      "msg": "用户必须年满 18 岁",
      "type": "value_error"
    }
  ]
}

错误信息清晰明确,便于调试。

总结:为何选择 FastAPI Pydantic 模型?

FastAPI Pydantic 模型,是现代 Python Web 开发中不可或缺的一环。它将数据验证、类型安全、自动文档生成融为一体,极大地提升了开发效率和系统稳定性。

通过本篇文章,你已经掌握了:

  • 如何定义基础模型和嵌套结构;
  • 如何使用 ListOptional 处理复杂数据;
  • 如何用 Field@validator 添加校验规则;
  • 如何在 FastAPI 中实际应用这些模型。

更重要的是,你理解了这种设计背后的哲学:“让代码自己说话”。当你看到一个 UserCreate 模型时,就知道它接受什么数据、有哪些限制。这不仅让团队协作更高效,也减少了因数据格式问题导致的线上事故。

在未来的项目中,不妨从一开始就使用 FastAPI Pydantic 模型来定义接口契约。它不会让你多写代码,反而能帮你少修 bug。