查询参数校验
本教程共 50 篇 · 第 9 篇 · 更新于 2026-08-12 · 约 8 分钟阅读
本节目标:学会用
Query()给查询参数加校验和文档说明,掌握字符串的长度限制、正则匹配,以及必填/可选的显式写法。
上一章我们靠「有没有默认值」来控制查询参数是否必填。但有时候要求更细:搜索词最多 50 个字、用户名必须以字母开头、参数要在文档里写清楚是干什么的。这些都要靠 Query() 来实现。
9-1 用 Annotated 加 Query
现代 FastAPI(0.95.0 之后,我们用的 0.141.1 当然支持)推荐把 Query 写进 Annotated 里。先导入它们:
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
假设我们想让可选的 q 参数,一旦传了,长度不能超过 50 个字符:
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(max_length=50)] = None):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
写法拆解:Annotated[str | None, Query(max_length=50)] 这一整段是类型注解,= None 是函数默认值。Query(max_length=50) 就是告诉 FastAPI:这个参数最多 50 字符。默认值依然是 None,所以它还是可选的。
Note用
Annotated时,默认值写在函数参数= None这里,而不要写在Query(default=None)里,否则 FastAPI 会在导入时直接报错,提示你用=设置默认值。新代码一律用Annotated风格。
9-2 字符串长度约束
除了 max_length,还能加 min_length,要求至少多少字:
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3, max_length=50)] = None):
return {"q": q}
现在 q 一旦传入,长度必须在 3 到 50 之间。传 ab(太短)或 51 个字符(太长)都会返回清晰报错,并且这俩约束会自动写进 /docs 文档。
9-3 用正则 pattern 约束格式
pattern 可以写一个正则表达式,要求参数值必须匹配它。下面这个例子强制 q 必须正好是 fixedquery 这几个字:
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$")] = None
):
return {"q": q}
正则 ^fixedquery$ 的含义:^ 表示开头,fixedquery 是固定内容,$ 表示结尾,中间不能有别的字符。传 fixedquery 通过,传 fixedquery123 或 myfixedquery 都会被拒。
Tip正则对新手有点绕,不用急着精通。记住
pattern这个开关在哪,以后真遇到「格式必须怎样」的需求再回来用就行。
9-4 给参数加默认值(非 None)
校验完全可以和有意义的默认值共存。比如让 q 默认就是 fixedquery,同时要求至少 3 个字:
@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
return {"q": q}
这里 = "fixedquery" 是默认值,所以参数可选;不传就返回 fixedquery。任何类型的默认值都会让参数变可选。
9-5 显式声明必填
当你用 Query 又想让它必填,最简单的办法就是不写默认值:
@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
return {"q": q}
注意 q: Annotated[str, Query(min_length=3)] 后面没有 = 某值,所以它是必填的。客户端不传 q 就会收到 Field required 报错。如果既想必填、又想允许值是 None,那就把 None 放进类型但不给默认值:
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3)]):
return {"q": q}
这样客户端「必须传 q」。这里的 str | None 主要是为请求体等场景准备的;在查询参数场景下,URL 里并不存在真正的 JSON null——写 ?q=null 得到的是字符串 "null" 而非 None。所以查询参数一般直接用 str(必填)或 str | None = None(可选)即可。
9-6 给文档加标题和描述
Query 还能塞元数据,让自动文档更易读。title 是参数标题,description 是详细说明:
@app.get("/items/")
async def read_items(
q: Annotated[
str | None,
Query(
min_length=3,
max_length=50,
title="查询关键词",
description="用来在商品列表里搜索,长度 3 到 50 个字符。",
),
] = None
):
return {"q": q}
打开 /docs,这个 q 参数旁边就会显示中文标题和描述,别人一看就懂怎么传。再补一个示例值,文档会更直观:
@app.get("/items/")
async def read_items(
q: Annotated[
str | None,
Query(
min_length=3,
max_length=50,
title="查询关键词",
description="用来在商品列表里搜索,长度 3 到 50 个字符。",
examples={"示例": "phone"},
),
] = None
):
return {"q": q}
Note这些
title、description、examples不影响接口运行,只进入 OpenAPI 文档。但好的文档能大幅减少前后端联调的沟通成本,值得写。
9-7 用 alias 接收不合法变量名
URL 里有时会出现 item-query 这种带横杠的参数,但 item-query 不是合法的 Python 变量名。用 alias 就能把它映射到 item_query:
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(alias="item-query")] = None
):
return {"q": q}
访问 http://127.0.0.1:8000/items/?item-query=foobar 时,FastAPI 用 item-query 从 URL 取值,再交给函数参数 q。函数内部只用 q 这个名字,干净利落。
9-8 让参数在文档里消失
极少数情况,你不想让某个查询参数出现在自动文档里,可以设置 include_in_schema=False:
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(include_in_schema=False)] = None
):
return {"q": q}
这样接口照常工作,但 /docs 和 /redoc 里看不到这个参数。一般用于内部调试开关,谨慎使用。
9-9 标记参数已弃用
如果一个查询参数你打算以后去掉、但暂时还得留着兼容老调用方,可以给 Query 加 deprecated=True。文档里这个参数会被标成「已弃用」,提示别人别再用了,但接口仍然照常工作:
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(deprecated=True)] = None
):
return {"q": q}
这样既不影响线上老用户,又能推动大家慢慢迁移到新参数。配合前面学的 description,文档的「弃用说明」会非常清楚。
需要留意的是,deprecated=True 只是文档层面的标记,不会拦截请求,也不会打印警告。真正下线一个参数要分两步走:先标弃用并在描述里写明替代方案与预计移除时间,观察一段时间确认没人再传,然后才从代码里删掉。跳过第一步直接删除,往往会让还没来得及改造的调用方突然收到 422 校验错误。
9-10 小结
这一章把查询参数的「校验」和「文档」都补齐了:Query() 配合 Annotated 是标准写法;min_length/max_length 限制字符串长度,pattern 做正则匹配;不写默认值就是必填,写默认值(含 None)就是可选;title、description、examples 让文档更友好,alias 解决带横杠的参数名,include_in_schema=False 可隐藏参数。
至此,路径参数和查询参数的声明、校验、文档你都掌握了。后面我们会进入请求体,学习更复杂的数据结构怎么传。