FastAPI 表单模型实战:用 Pydantic 模型声明与校验表单字段
本篇基于 FastAPI 官方教程文档《Formularmodelle(表单模型)》,讲解如何在 FastAPI 中直接使用 Pydantic 模型来声明 表单字段(form fields):从安装依赖、声明 Form 参数、自动从请求中提取并校验各字段,到通过 extra = "forbid" 禁止客户端提交模型中未声明的额外字段。读完后你将掌握这套自 FastAPI 0.113.0 起支持的表单建模能力,并理解其在 OpenAPI 文档生成与请求校验中的底层实现(0.114.0 起额外支持禁止额外字段)。
前置条件:安装 python-multipart
使用表单功能的第一步是安装 python-multipart 包。将其添加到你的项目中:
$ uv add python-multipart
这个依赖在 FastAPI 内部是被硬性检查的。从源码看,fastapi/dependencies/utils.py 中定义了专门的错误提示与检查函数 ensure_multipart_is_installed():它尝试导入 python_multipart 并断言版本大于 0.0.12,若导入失败或版本过低则抛出 RuntimeError,提示安装 python-multipart;甚至针对误装了名为 multipart(而非 python-multipart)的包的情况也准备了单独的提示 multipart_incorrect_install_error。所以遇到 "Form data requires python-multipart" 报错时,检查包名和版本是第一排查方向。
版本前提说明:
- 使用 Pydantic 模型声明表单字段:自 FastAPI
0.113.0起支持; - 通过
extra: "forbid"禁止额外表单字段:自 FastAPI0.114.0起支持。
用 Pydantic 模型声明表单字段
你只需声明一个包含所有期望接收的表单字段的 Pydantic 模型,然后把路径操作函数中的参数声明为 Form 即可。完整可运行示例(对应 docs_src/request_form_models/tutorial001_an_py310.py):
from typing import Annotated
from fastapi import FastAPI, Form
from pydantic import BaseModel
app = FastAPI()
class FormData(BaseModel):
username: str
password: str
@app.post("/login/")
async def login(data: Annotated[FormData, Form()]):
return data
FastAPI 会从请求的表单数据中提取每个字段的数据,完成 Pydantic 校验后,把定义好的 Pydantic 模型实例传递给你的端点函数。
底层实现:Form 继承自 Body
从源码结构看,Form 在 fastapi/params.py 中定义为 class Form(Body),即表单参数在内部被当作一种特殊的 Body 参数处理。其构造函数签名为:
class Form(Body):
def __init__(
self,
default: Any = Undefined,
*,
default_factory: Callable[[], Any] | None = _Unset,
annotation: Any | None = None,
media_type: str = "application/x-www-form-urlencoded",
alias: str | None = None,
# ... 以及 gt/ge/lt/le、min_length/max_length、pattern、discriminator、strict 等
)
关键点是默认 media_type 为 "application/x-www-form-urlencoded"——这意味着默认的表单模型端点接收的是 URL 编码的表单数据(浏览器 <form> 默认提交格式),而不是 multipart/form-data 文件上传格式。参数解析完成后,FastAPI 按模型定义逐字段提取并校验,最终端点拿到的是类型化的 FormData 实例,而不是原始字典。
在 /docs 界面验证
你可以在 /docs 的文档 UI 中直接测试该端点(见文首截图)。OpenAPI Schema 会正确生成表单请求体。测试用例 tests/test_tutorial/test_request_form_models/test_tutorial001.py 中的 test_openapi_schema 断言了生成的 Schema 结构,核心片段如下:
{
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {"$ref": "#/components/schemas/FormData"}
}
},
"required": true
}
}
可以看到:请求体以 application/x-www-form-urlencoded 为 content type,Schema 通过 $ref 指向组件区中的 FormData 模型定义,且标记为 required: true。组件区中 FormData 的定义为:
{
"FormData": {
"properties": {
"username": {"type": "string", "title": "Username"},
"password": {"type": "string", "title": "Password"}
},
"type": "object",
"required": ["username", "password"],
"title": "FormData"
}
}
这意味着 API 客户端可以基于 OpenAPI 规范自动生成表单请求代码,字段类型与必填约束都来自你的 Pydantic 模型。
校验行为:缺字段与内容类型不符都会得到 422
同一测试文件还验证了多种失败场景,对实际联调很有参考价值:
| 请求方式 | 结果 |
|---|---|
POST /login/,data={"username": "Foo", "password": "secret"} |
200,返回 {"username": "Foo", "password": "secret"} |
缺少 password 字段 |
422,type: "missing",loc: ["body", "password"],msg: "Field required" |
缺少 username 字段 |
422,type: "missing",loc: ["body", "username"] |
| 完全不携带数据 | 422,两个字段均报 missing |
以 JSON 方式发送(json={...} 而非表单数据) |
422,两个字段均报 missing |
最后一条值得特别注意:如果客户端用 JSON 而不是表单编码发送数据,FastAPI 无法从表单数据中提取到任何字段,会以"字段缺失"的方式返回 422 校验错误,而不是成功接收。
禁止额外的表单字段
在某些特殊使用场景(可能并不常见)下,你希望将表单字段限制为 Pydantic 模型中声明的那些字段,并禁止任何额外字段。此能力自 FastAPI 0.114.0 起支持。
方法是通过 Pydantic 的模型配置,将 extra 字段设置为 forbid(对应 docs_src/request_form_models/tutorial002_an_py310.py):
class FormData(BaseModel):
username: str
password: str
model_config = {"extra": "forbid"}
如果客户端尝试提交额外数据,会收到一个错误响应。例如,客户端尝试发送以下表单字段:
username:Rickpassword:Portal Gunextra:Mr. Poopybutthole
它将收到一个提示 extra 字段不被允许的 Error 响应:
{
"detail": [
{
"type": "extra_forbidden",
"loc": ["body", "extra"],
"msg": "Extra inputs are not permitted",
"input": "Mr. Poopybutthole"
}
]
}
源码与测试印证
extra = "forbid" 的影响体现在两个层面,均可在仓库中找到证据:
- 运行时校验:tests/test_tutorial/test_request_form_models/test_tutorial002.py 中的
test_post_body_extra_form用data={"username": "Foo", "password": "secret", "extra": "extra"}发起请求,断言返回422,且detail中为type: "extra_forbidden"、loc: ["body", "extra"]的校验错误——与上文文档给出的错误响应结构完全一致。 - OpenAPI Schema 同步更新:
test_tutorial002.py中的test_openapi_schema断言生成的FormDataSchema 中额外出现了"additionalProperties": false(tutorial001 的对应 Schema 中没有这一项)。也就是说extra = "forbid"不仅影响运行时行为,还会反映到对外发布的 API 契约中,让 API 消费方能明确感知"不允许额外字段"。
从源码结构看,这一行为源自 Pydantic 模型配置与 FastAPI 表单参数解析的结合:Form 参数在内部走 Body 参数流程(见 fastapi/params.py 中 Form(Body) 的定义),而 Pydantic 模型自身的 model_config 会原样参与实例化与 Schema 导出,因此无需 FastAPI 侧做特殊处理即可同时作用于校验与文档生成。
总结
- 你只需要声明一个包含期望表单字段的 Pydantic 模型,并把参数标记为
Form(),FastAPI 就会自动从请求的表单数据中逐字段提取并校验,然后把模型实例交给端点函数(FastAPI0.113.0+); - 表单功能依赖
python-multipart包,FastAPI 内部通过ensure_multipart_is_installed()强制检查该依赖; Form参数默认以application/x-www-form-urlencoded接收数据,OpenAPI 文档会自动生成对应的必填请求体 Schema,便于 API 文档展示与客户端代码生成;- 通过
model_config = {"extra": "forbid"}可以禁止客户端提交模型之外的额外表单字段,触发extra_forbidden校验错误,并同步在 OpenAPI Schema 中标记additionalProperties: false(FastAPI0.114.0+); - 相关示例代码见 docs_src/request_form_models/ 目录,回归测试见 tests/test_tutorial/test_request_form_models/ 目录,可对照源码验证上述全部行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
