本系列:00 导读 · 01补 网络基础 · 01 心智模型 · 02 路由与数据模型(本文) · 02补 HTTP 方法对比 · 02补2 状态码共识 · 03 依赖注入与分层 · 04 中间件异常日志 · 05 异步后台与流式 · 06 鉴权与安全 · 07 测试与项目骨架 · 08 实战 HTTP↔MCP
行文:T2 模式篇 | 本篇方法:组块化 + 刻意练习 | 辅助:主动回忆、精细加工(挂 Pydantic)
组块清单
| 组块 | 触发条件 | 核心写法 |
|---|---|---|
| G1 Path | 资源标识在 URL 路径里 | {item_id} + 类型注解 |
| G2 Query | 过滤、分页、可选参数 | 函数参数默认值 + 注解 |
| G3 Body | JSON 请求体 | BaseModel 单对象或列表 |
| G4 响应模型 | 约束输出形状、隐藏字段 | response_model= |
| G5 状态码 | 创建/删除等语义 | status_code= + Response |
| G6 APIRouter | 模块拆分、统一前缀 | APIRouter(prefix=...) |
段末注释:Pydantic 是 Python 数据验证库;FastAPI 用它解析请求并生成 OpenAPI 文档。字段校验细节见 Pydantic 笔记,本篇只讲「在路由里怎么用」。
路由Path
1 | # 根据路径匹配对应的函数调用 |
可以看到通过路由地址,fastapi可以实现不同函数的调用选择,同时也可以简单的实现参数的传递。
- 误用:路径写成
/items/{item_id}/{item_id}却期望两个不同变量——同名占位符会冲突;用不同名字。
参数传递
我们可以直接通过路由路径进行传参,但是在参数多、结构复杂的情况下,使用体验会不友好,fastapi本身也提供多重参数传递方式:
通过查询参数
http网页如果我们关注过请求地址,我们一定看到过类似这样的地址,例如:https://cn.bing.com/search?pc=MOZI&form=MOZLBR&q=antibody(这是一个搜索浏览的搜索页面,可以看到后面 search确定搜索函数,然后通过pc、form、q传递参数),而fastapi也支持这样的查询模式。
1 | # 多参数传递 |
这时候,我们访问 http://127.0.0.1:8000/users/1/items/tem_id?item_id=13 item_id=13就会被解析并赋值给item_id传递到调用的函数中。
- 注意: 如果参数
async def read_user_item(item_id: Annotated[list[str], Query()]):则可以在查询中出现多次item_id将参数构建出一个列表。
通过请求体
请求体是客户端发送给 API 的数据(也可能没有,直接通过)。响应体是 API 发送给客户端的数据。相当于使用自定义的数据结构,使用请求体和响应体都需要进行声明。
如果要使用请求体,需要先进行请求体数据类型的声明(继承 BaseModel )。
1 | from fastapi import FastAPI |
- 误用:Body 模型里塞 20 个可选字段当「万能 DTO」——应拆 Create / Update / Read 模型。
多请求体
1 | from fastapi import FastAPI |
在这种情况下,FastAPI 会注意到函数中有不止一个请求体参数(有两个参数是 Pydantic 模型)。
因此,它会将参数名作为请求体中的键(字段名),并期望请求体格式如下:
1 | { |
混合传参
之前我们已经看到可以自由地混合使用 Path、Query 和请求体参数声明,FastAPI 知道该如何处理。
但是我们之前可以看到 PATH、Query都是通过访问地址传输的,请求体是通过请求体进行传输的,有时候我们需要一起用,也是可以的
1 | from typing import Annotated |
通过importance注释为Body(),虽然没有声明请求体,但是他也会去请求体中获取参数,预测的请求体如下:
1 | { |
参数校验
在我们进行函数开发阶段,会初步定义每个参数多数据类型(Str、Int、Float、Enum,List、Dict、Tuple、Union、Optional、Any等),在请求时,会自动进行类型的校验。但是实际开发中,我们可能会有更详细的校验需求,比如国家、电话号码,邮箱地址,手机号等等。
有限枚举值限制
1 | # 定义一个枚举类, |
Annotated详细校验
Annotated 可以用于为参数添加元数据(Annotated[type, metadata]), 在Fastapi中,我们可以通过 Annotated 进行更精细的数据校验包括通过 Query提供的功能进行一些标准化的校验,也可以通过自定义函数进行自定义校验。
Query进行查询校验
Query除了进行参数的校验,还支持更多类型的元数据的补充(title、description、alias、deprecated、include_in_schema等等)
1 | async def read_items( |
这样数据会校验长度,正则。
PATH进行路由参数校验
起始Query也就基本够用了,PATH和Query基本一样,只是PATH会校验路由参数。
1 | from typing import Annotated |
AfterValidator 自定义校验
1 | #自定义验证 |
请求体创建阶段,制定校验
刚才的校验,更多是在调用阶段进行的,但是针对一个请求体,校验规则一般一样,而且不回随着调用环境产生差别,所以在请求体构造阶段进行校验规则的确定会更有更好的可迁移性。而pydantic也支持这样的方案。
1 | from typing import Annotated, Literal |
在请求体构建阶段,就限制了每个变量的校验规则。从而避免每次调用都要重新写校验规则。
优雅递归处理-DEPEND复用参数处理
有时候有些不同的操作,需要相同的参数处理,这时DEPEND就派上了用场。不同的参数请求可以服用一个相同的参数处理逻辑。重点在于解决相同的参数处理实现可服用。
1 | from typing import Annotated |
结果返回
返回响应模型块
和请求体一样,最好也进行返回体的定义,进行返回数据的筛选(防止内部参数暴露给外部),FastAPI 会看到返回类型,并确保你返回的内容 仅 包含类型中声明的字段。
1 | class ItemRead(BaseModel): |
- 效果:
cost不会出现在 JSON;OpenAPI 也只展示ItemRead字段。 - 误用:
response_model与return类型不一致却不测——用 TestClient 断言响应体字段。
状态码块
作为结果返回的一部分。
1 |
|
- 误用:204 仍
return {"ok": true}——客户端可能收到非空 body,违背语义。
APIRouter 块
1 | from fastapi import APIRouter |
- 误用:
prefix与路由内路径都带/items,拼成/items/items——约定 router 管前缀,子路由写/或/{id}。
刻意练习:只改一处
在空项目 main.py 上改,用 uvicorn main:app --reload + /docs 验证。
| # | 题面 | 只改什么 | 验收 |
|---|---|---|---|
| D1 | GET /users/{user_id},user_id 必须 ≥ 1 |
加 Path(ge=1) |
传 0 得 422 |
| D2 | GET /items?q=...&limit=...,limit 默认 10、最大 50 |
Query 注解 | 超 50 得 422 |
| D3 | POST /items 接收 name+price |
新建 ItemCreate |
body 缺字段得 422 |
| D4 | 响应里隐藏 password_hash |
response_model=UserPublic |
响应无 hash 字段 |
| D5 | 创建资源返回 201 | status_code=201 |
状态码为 201 |
| D6 | 把 items 路由拆到 routers/items.py |
APIRouter + include_router |
/items 仍可访问 |
| D7 | 同一函数既要 Path item_id 又要 Query detail |
两种参数同函数 | OpenAPI 显示两类参数 |
| D8 | PUT /items/{id} body 字段均可选 |
ItemUpdate 全 Optional |
只传 price 也能过 |