06. FastAPI 请求参数类型验证
从“收到文本”走向“收到可靠的数据”
类型验证解决三个问题:参数从哪里取、能否转成目标类型、是否满足约束。例如 /items/12?enabled=true 中,12 可转成整数,true 可转成布尔值。验证后业务代码不必反复写转换逻辑。
FastAPI 根据路径模板、类型和声明方式判断来源:出现在 {} 中的是路径参数;普通标量一般来自查询串;Pydantic 模型一般来自请求体。Query、Path、Body 可显式标注来源。
同时验证路径、查询与正文
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class ItemIn(BaseModel):
name: str
price: float
note: str | None = None
@app.post("/shops/{shop_id}/items")
def create_item(shop_id: int, item: ItemIn, enabled: bool = True):
return {"shop_id": shop_id, "enabled": enabled,
"item": item.model_dump()}保存为 main.py,启动 uv run uvicorn main:app --reload,在 /docs 调用:路径填 12,查询填 false,正文填 {"name":"键盘","price":199}。响应中的 shop_id 是数字、enabled 是布尔值。
再将路径改成 abc,或把价格改成 "便宜",应得到 422。错误正文的 detail 是一个列表,loc 指出位置,例如 body → price,msg 描述原因。客户端应关注稳定的字段路径与错误分类,不应依赖某一版本的整段英文提示。
必填、可空、默认值是三件事
| 声明 | 能否不传 | 能否传 null |
|---|---|---|
name: str | 否 | 否 |
name: str = "匿名" | 是 | 否 |
| `name: str | None` | 否 |
| `name: str | None = None` | 是 |
以上以 Pydantic 2 模型字段为例。None 表示值可为空;有没有默认值决定字段能否省略。
转换模式与严格模式
默认验证允许部分合理转换,例如把 "12" 转成整数,但不是任意值都能转换。若 JSON 正文中的整数必须真正是数字,可声明 Field(strict=True)。不要把严格整数规则直接照搬到查询串:查询串天然是文本,严格设置可能拒绝原本可接受的请求。
日期用 date,时间用带明确时区约定的 datetime,唯一标识可用 UUID,固定选项可用 Enum 或 Literal。金额通常使用 Decimal 配合精度约束,避免浮点累加误差。
验证不等于业务检查
“价格必须大于零”适合字段约束;“库存足不足”“用户名是否已存在”“当前用户能否修改订单”需要查询数据库或权限系统,通常放业务层。422 处理结构错误,404 处理资源不存在,409 可处理唯一性冲突。
练习:为商品增加必填的 stock: int,分别测试缺字段、字符串数字和完全不能转换的文本;再为备注增加可空默认值,解释两者区别。