跳到主要内容

FastAPI 数据校验入门:用 Pydantic 给接口加上“安检”

在前面完成的 FastAPI 学生接口中,我们已经能正常增删改查,但接口存在一个明显隐患:用户传什么,接口就收什么。

本文先用一个真实的反例暴露问题,然后脱离 FastAPI,单独用一个 Demo 演示 Pydantic 的类型校验、字段约束、自定义校验器和模型转字典能力,为下一步在 FastAPI 中接入数据校验打基础。

一、问题复现:非法数据也能“新增成功”​

打开 Swagger 文档,找到新增学生的 POST 接口,点击 Try it out,故意填入一组明显不合法的数据:

  • name 为空
  • 学号是一个汉字
  • 性别不是“男/女”
  • 生日不是日期格式

点击 Execute 执行后,返回码居然是 201,数据被成功添加。

Swagger 中提交非法学生数据仍返回 201 成功

这说明 FastAPI 当前对请求体“照单全收”。如果被恶意提交非法用户信息,数据质量将完全失控。作为 API 开发者,必须对用户填写的数据做验证——这正是 Pydantic 要解决的问题。

二、认识 Pydantic​

Pydantic 是一个 Python 第三方包。安装 FastAPI 时会作为依赖被自动安装,无需单独安装。在终端执行:

pip list

可以查到当前安装的 Pydantic 版本为 2.12.5,属于 2.0 版本系列。2.0 版本使用 Rust 重构,效率比 1.0 版本提升约 10 倍。

终端 pip list 中查看 Pydantic 版本信息

Pydantic 并不绑定 FastAPI,可以独立使用。本文先单独演示它的用法,下一节再讲如何接入 FastAPI。

三、搭建独立 Demo​

新建一个与项目无关的测试目录 Demo,在其中创建文件 PydanticDemo.py。这个文件只演示 Pydantic 本身,与 FastAPI 无关。

1. 定义基础模型​

from pydantic import BaseModel

class User(BaseModel):
name: str
age: int

继承 BaseModel 后,类中声明的属性就带上了类型约束。使用方式与普通面向对象类类似:

u = User(name="Andy", age=18)
print(u.name)
print(u.age)

运行后正常输出 Andy 和 18。

Pydantic 基础模型实例化并输出 name 与 age

2. 自动类型转换​

把 age 的传参改成字符串 "18":

u = User(name="Andy", age="18")
print(u.age)

虽然编辑器标红提示类型不符,但运行依然正常输出。Pydantic 会把可转换的字符串自动转成整型。

Pydantic 将字符串 18 自动转换为整型输出

3. 无法转换时报错​

把 age 改成 "abc" 再运行,程序报错,提示 age 字段的输入必须是整型。这就是最基础的类型校验。

Pydantic 对无法转换的 age 输入抛出类型校验错误

四、用 Field 增加字段约束​

引入 Field 对字段做更细的约束。常用参数包括:

  • gt:大于
  • ge:大于等于
  • lt:小于
  • le:小于等于
  • min_length / max_length:最小/最大长度
  • pattern:正则表达式

改造模型:

from pydantic import BaseModel, Field

class User(BaseModel):
name: str = Field(min_length=4, max_length=50)
age: int = Field(ge=1, le=99)

测试 1:age 超出上限​

传入 age=100 运行,报错提示 age 的输入需要小于等于 99。

age 传入 100 触发小于等于 99 的校验错误

测试 2:合法数据通过​

把 age 改为 80、name 改为 18 个字符的正常姓名,运行无任何问题。

合法姓名与 age=80 通过 Field 校验

测试 3:name 长度不足​

把 name 改成三个字符运行,报错提示长度至少为 4。输入两个汉字“大雄”同样报错,因为字符数小于 4。

name 字符数小于 4 触发 min_length 校验错误

4. 可选字段与默认值​

如果某个字段允许不传,可以声明为可选并给默认值:

age: int | None = None

不传 age 时运行,输出结果为 None。

age 声明为 Optional 且不传值时输出 None

五、用 field_validator 做自定义校验​

Field 只能做通用约束。如果业务规则更复杂(比如姓名不能全是空格),需要用 field_validator 定义字段级校验方法。

from pydantic import BaseModel, Field, field_validator

class User(BaseModel):
name: str = Field(min_length=4, max_length=50)
age: int = Field(ge=1, le=99)

@field_validator("name")
def clean_name(cls, v: str) -> str:
v = v.strip()
if not v:
raise ValueError("姓名不能全为空格")
if len(v) < 4 or len(v) > 50:
raise ValueError("姓名长度在4到50个字符之间")
return v

要点说明:

  1. 方法名固定为 clean_<字段名> 形式,第一个参数是 cls,第二个参数 v 是待校验的字段值。
  2. 先 strip() 去掉前后空格,再判断是否为空、长度是否合规。
  3. 必须 return v。如果不返回,字段值会变成 None。

测试时传入不合规的姓名,报错信息显示 ValueError: 姓名长度在4到50个字符之间,说明自定义校验生效。

field_validator 抛出的姓名长度校验错误信息

六、模型转字典​

Pydantic 模型可以快速转成字典,方便后续序列化或入库:

u = User(name="大熊课堂", age=18)
print(u.model_dump())

运行后输出 {'name': '大熊课堂', 'age': 18},得到的是一个标准字典。

model_dump 将 User 模型转换为字典并打印

小结​

一句话概括 Pydantic:系统类型 + 数据校验 + 数据解析 + 数据建模。它让 Python 从动态语言变成拥有强类型数据约束的编辑器,这也是 FastAPI 必须引入它的原因。

本文覆盖的能力清单:

能力用法
基础类型校验继承 BaseModel,声明 name: str、age: int
自动类型转换可转换的字符串自动转目标类型,不可转换则报错
字段约束Field(min_length=..., max_length=..., ge=..., le=...)
可选字段Optional[int] = None
自定义校验@field_validator("字段名") + clean_字段名 方法
模型转字典model_dump()

下一节将把 Pydantic 接入 FastAPI,替换掉当前“照单全收”的请求体,让新增学生接口真正具备数据校验能力。

课程推荐

想系统学习这门课程?

你已阅读完本文。需要更完整的知识体系,可以继续了解「FastAPI 项目实战」。

查看课程详情