请求体字段校验
本教程共 50 篇 · 第 11 篇 · 更新于 2026-08-12 · 约 7 分钟阅读
本节目标:学会用 Pydantic 的 Field 给模型字段加约束条件(如价格必须大于 0、名称长度限制),理解必填与可选的区别,并知道如何在校验失败时给出更友好的提示。
上一章我们只是声明了字段的类型。类型本身已经是一种校验:字符串必须是字符串,数字必须是数字。但真实业务往往还有更细的规则。
比如商品的价格不能是负数,用户名不能太长。这些额外规则,就要靠 Field 来写。
11-1 为什么需要字段级校验
只写类型,只能保证「这是个数字」。可数字可以是 -100、0 或 999999。很多时候这都不合理。
字段级校验让我们把业务规则直接写进模型定义里。规则跟着字段走,代码集中、好维护,而且校验逻辑不用你手写,Pydantic 会自动执行。
它的写法和查询参数里的 Query、路径参数里的 Path 很像,只是 Field 用在模型内部。
11-2 导入 Field
Field 不是从 fastapi 导入的,而是直接从 pydantic 导入。这一点要记牢,很多人会搞混。
from fastapi import FastAPI
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(..., title="商品名称", min_length=1, max_length=50)
description: str | None = Field(
None, title="商品描述", max_length=300
)
price: float = Field(..., gt=0, description="价格必须大于 0")
tax: float | None = Field(None, ge=0)
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, "item": item.model_dump()}
注意 Field 来自 pydantic,而 Query、Path、Body 来自 fastapi。它们底层其实是亲戚,但导入位置不同。
11-3 常用约束参数
Field 支持一堆约束参数,下面是最常用的几个:
gt:大于(greater than)。gt=0表示必须比 0 大。ge:大于等于(greater or equal)。ge=0表示可以为 0。lt:小于(less than)。le:小于等于(less or equal)。min_length:字符串最小长度。max_length:字符串最大长度。
这些参数只对合适类型生效。比如 gt 用在数字上,min_length 用在字符串上。
回到上面的例子:price 用了 gt=0,所以价格不能是负数;name 用了 min_length=1 和 max_length=50,名字既不能空也不能超长。
Tip
gt、ge、lt、le来自英文缩写,记住「g 是大于、l 是小于、e 是等于」就不容易混。比如ge=18就是「大于等于 18」。
11-4 必填与可选怎么写
在 Field 里,第一个位置参数控制「默认值」。它决定了字段是否必填:
Field(...)里的...(三个点,叫 Ellipsis)表示没有默认值,也就是必填。Field(None)表示默认值是None,也就是可选。Field(0)表示默认值是0,同样是可选,且缺省时取0。
所以 name: str = Field(...) 是必填的,而 tax: float | None = Field(None) 是可选的。
类型注解里的 str | None 只是告诉编辑器「这里可能是字符串,也可能是 None」,真正决定必填与否的是那个默认值。两者配合写,既清晰又安全。
11-5 给字段加元数据
除了约束,Field 还能接收纯描述性的元数据,它们会出现在自动生成的文档里:
title:字段标题。description:字段说明。examples:示例值,展示在文档中,方便调用方理解格式。
from fastapi import FastAPI
from pydantic import BaseModel, Field
class Item(BaseModel):
name: str = Field(..., title="商品名称", examples=["机械键盘"])
price: float = Field(..., gt=0, description="价格必须大于 0", examples=[299.0])
tax: float | None = Field(None, ge=0, examples=[19.9])
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
打开 /docs,点开 Item 模型,就能看到这些 title、description 和 examples。它们不影响运行,但能大幅提升接口的可读性。
Note
examples是 Pydantic 和 FastAPI 都支持的参数,会被写进 OpenAPI 文档。它和「默认值」不同:示例值只是展示用,不会在请求缺失时自动填充。
11-6 校验失败时报错长什么样
当客户端发来的数据违反约束,FastAPI 会自动返回 422 状态码和一段错误详情。看一个具体例子。
假设 price 传了 -5,而模型要求 gt=0,你会收到:
{
"detail": [
{
"type": "greater_than",
"loc": ["body", "price"],
"msg": "Input should be greater than 0",
"input": -5,
"ctx": { "gt": 0 }
}
]
}
每个错误条目包含几个字段:
type:错误类型,比如greater_than表示「应大于某值」。loc:出错位置,["body", "price"]说明在请求体的price字段。msg:人类可读的错误信息。input:客户端实际传进来的值。ctx:上下文,这里给出约束的具体数值gt: 0。
这份报错结构清晰,前端能直接拿去提示用户「价格必须大于 0」。
11-7 错误信息本地化的思路
默认情况下,FastAPI 返回的 msg 是英文的。如果你的用户主要是中文群体,可能希望提示也用中文。这里提供两条思路。
第一条,是用自定义校验抛出中文信息。借助 Pydantic v2 的 field_validator,你可以在校验失败时主动抛出带中文说明的异常:
from pydantic import BaseModel, field_validator
class Item(BaseModel):
price: float
@field_validator("price")
@classmethod
def check_price(cls, value: float) -> float:
if value <= 0:
raise ValueError("价格必须大于 0")
return value
这样报错信息就会变成你写的中文。适合个别关键字段的定制。
第二条,是对整批错误做统一翻译。FastAPI 抛出的 RequestValidationError 里,每个错误都是结构化的(有 type、loc、ctx)。你可以注册一个异常处理函数,根据 type 把英文 msg 映射成中文文案,再返回给客户端。这种方式适合需要全站中文化的项目。
Tip全站翻译建议做成「错误类型 → 中文模板」的映射表,而不是逐字机翻。这样既能保证语气统一,也方便后续维护。具体实现会在「错误处理」相关章节展开。
11-8 小结
Field 是给请求体字段加约束的主力工具,记住这几点:
- 它从
pydantic导入,不是从fastapi。 - 常用约束有
gt、ge、lt、le、min_length、max_length。 Field(...)表示必填,Field(None)表示可选。title、description、examples是元数据,会进文档但不参与校验。- 校验失败返回 422,报错里带
loc和msg,定位问题很方便。 - 想要中文提示,可用
field_validator抛中文,或统一翻译RequestValidationError。
字段校验让接口更健壮。下一章我们看更复杂的场景:模型里嵌套模型、列表里装对象。