查询参数和字符串验证¶
FastAPI 允许你为参数声明额外信息和验证。
让我们以这个应用为例
from fastapi import FastAPI
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = None):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
查询参数 q 的类型是 str | None,这意味着它是 str 类型,但也可以是 None。事实上,它的默认值就是 None,所以 FastAPI 会知道它不是必需的。
注意
由于默认值为 = None,FastAPI 会知道 q 的值不是必需的。
使用 str | None 可以让你的编辑器提供更好的支持并检测错误。
额外验证¶
我们将强制要求:尽管 q 是可选的,但一旦提供,其长度不得超过 50 个字符。
导入 Query 和 Annotated¶
要实现这一点,首先导入
- 来自
fastapi的Query - 来自
typing的Annotated
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@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 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
注意
FastAPI 在 0.95.0 版本中添加了对 Annotated 的支持(并开始推荐它)。
如果您的版本较旧,尝试使用 Annotated 时会出错。
在使用 Annotated 之前,请确保你已 将 FastAPI 版本升级 到至少 0.95.1。
在 q 参数的类型中使用 Annotated¶
还记得我在之前的 Python 类型简介 中提到过 Annotated 可以用于为参数添加元数据吗?
现在是时候在 FastAPI 中使用它了。🚀
我们之前的类型注解是这样的
q: str | None = None
我们将用 Annotated 包裹它,变成这样
q: Annotated[str | None] = None
这两个版本意思相同:q 是一个可以是 str 或 None 的参数,默认值为 None。
现在让我们开始有趣的部分吧。🎉
将 Query 添加到 q 参数的 Annotated 中¶
既然我们已经有了 Annotated(可以在其中放入更多信息,本例中为额外验证),我们将 Query 放入 Annotated 中,并将 max_length 参数设置为 50
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@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 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
注意,默认值仍然是 None,因此该参数仍然是可选的。
但是现在,在 Annotated 中使用了 Query(max_length=50),我们是在告诉 FastAPI:我们希望对该值进行额外验证,即最大长度为 50 个字符。😎
提示
这里我们使用 Query(),因为这是一个查询参数。稍后我们将看到其他类似 Path()、Body()、Header() 和 Cookie() 的组件,它们也接受与 Query() 相同的参数。
FastAPI 现在将
- 验证数据,确保最大长度为 50 个字符
- 当数据无效时,向客户端显示明确的错误
- 在 OpenAPI 模式的路径操作中记录该参数(这样它就会显示在自动文档 UI 中)
替代方案(旧版):将 Query 作为默认值¶
FastAPI 的旧版本(0.95.0 之前)要求你将 Query 作为参数的默认值,而不是将其放在 Annotated 中。你很有可能会在其他代码中看到这种写法,所以我解释一下。
提示
对于新代码,只要可能,请使用上述 Annotated 的写法。它有多种优势(下文解释),且没有劣势。🍰
这就是你将 Query() 作为函数参数默认值,并将 max_length 参数设置为 50 的写法
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = Query(default=None, max_length=50)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@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),我们必须将函数中的默认值 None 替换为 Query()。现在我们需要通过 Query(default=None) 来设置默认值,它的作用与定义默认值相同(至少对于 FastAPI 而言)。
所以
q: str | None = Query(default=None)
...使该参数成为可选参数,默认值为 None,这与下述代码相同
q: str | None = None
但 Query 版本显式地将其声明为查询参数。
然后,我们可以向 Query 传递更多参数。在本例中,是适用于字符串的 max_length 参数
q: str | None = Query(default=None, max_length=50)
这将验证数据,在数据无效时显示明确的错误,并在 OpenAPI 模式的路径操作中记录该参数。
将 Query 作为默认值或放在 Annotated 中¶
请记住,当在 Annotated 中使用 Query 时,你不能使用 Query 的 default 参数。
相反,请使用函数参数本身的默认值。否则会不一致。
例如,这是不允许的
q: Annotated[str, Query(default="rick")] = "morty"
...因为不清楚默认值应该是 "rick" 还是 "morty"。
所以,(建议)你应该使用
q: Annotated[str, Query()] = "rick"
...或者在旧的代码库中你会发现
q: str = Query(default="rick")
Annotated 的优势¶
推荐使用 Annotated 而不是在函数参数中使用默认值,这样做有多个优势。🤓
函数参数的默认值即为实际默认值,这在一般 Python 编程中更直观。😌
你可以在其他地方调用同一个函数而不通过 FastAPI,它也会按预期工作。如果有必需参数(没有默认值),你的编辑器会通过报错提醒你,如果你在没传必需参数的情况下运行代码,Python 也会报错。
当你不用 Annotated 而使用(旧的)默认值风格时,如果你在其他地方不通过 FastAPI 调用该函数,你必须记住要给函数传递参数它才能正常工作,否则得到的值将与你预期的不同(例如得到 QueryInfo 而不是 str)。而且你的编辑器不会报错,Python 运行函数时也不会报错,只有当内部的操作出错时才会发现。
因为 Annotated 可以有多个元数据注解,你甚至可以将同一个函数用于其他工具,例如 Typer。🚀
添加更多验证¶
你还可以添加 min_length 参数
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(min_length=3, max_length=50)] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = Query(default=None, min_length=3, max_length=50)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
添加正则表达式¶
你可以定义一个参数应匹配的 正则表达式 pattern
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[
str | None, Query(min_length=3, max_length=50, pattern="^fixedquery$")
] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: str | None = Query(
default=None, min_length=3, max_length=50, pattern="^fixedquery$"
),
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
这个特定的正则表达式模式检查接收到的参数值是否
^:以随后的字符开头,前面没有其他字符。fixedquery:具有确切的值fixedquery。$:到此结束,fixedquery之后没有其他字符。
如果你对所有这些“正则表达式”的概念感到困惑,别担心。这对很多人来说都是个难题。即使不使用正则表达式,你依然可以做很多事情。
现在你知道了,无论何时需要,都可以在 FastAPI 中使用它们。
默认值¶
当然,你可以使用除了 None 以外的默认值。
假设你想要声明查询参数 q 的 min_length 为 3,并且默认值为 "fixedquery"
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)] = "fixedquery"):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str = Query(default="fixedquery", min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
注意
任何类型的默认值(包括 None)都会使参数变为可选(非必需)。
必需参数¶
当我们不需要声明更多验证或元数据时,只需不声明默认值即可使查询参数 q 成为必需参数,如下所示
q: str
而不是
q: str | None = None
但我们现在用 Query 来声明它,例如
q: Annotated[str | None, Query(min_length=3)] = None
所以,当你需要在使用 Query 时声明一个值是必需的,你可以直接不声明默认值
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str, Query(min_length=3)]):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str = Query(min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
必需,但可以是 None¶
你可以声明一个参数可以接受 None,但它仍然是必需的。这将强制客户端发送一个值,即使该值为 None。
要做到这一点,你可以声明 None 是一个有效的类型,但不要声明默认值
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(min_length=3)]):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = Query(min_length=3)):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
查询参数列表 / 多个值¶
当你显式地使用 Query 定义查询参数时,你还可以声明它接收一个值列表,或者换句话说,接收多个值。
例如,要声明一个可以在 URL 中出现多次的查询参数 q,你可以这样写
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[list[str] | None, Query()] = None):
query_items = {"q": q}
return query_items
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: list[str] | None = Query(default=None)):
query_items = {"q": q}
return query_items
然后,使用类似于以下的 URL
https://:8000/items/?q=foo&q=bar
你将在路径操作函数中的函数参数 q 里,以 Python list 的形式接收到多个 q 查询参数的值(foo 和 bar)。
因此,对该 URL 的响应将是
{
"q": [
"foo",
"bar"
]
}
提示
要像上面的例子一样声明类型为 list 的查询参数,你需要显式使用 Query,否则它会被解释为请求体。
交互式 API 文档将相应地更新,以允许传入多个值

带默认值的查询参数列表 / 多个值¶
如果没有提供任何值,你还可以定义一个默认的 list 值
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[list[str], Query()] = ["foo", "bar"]):
query_items = {"q": q}
return query_items
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: list[str] = Query(default=["foo", "bar"])):
query_items = {"q": q}
return query_items
如果你访问
https://:8000/items/
q 的默认值将是:["foo", "bar"],你的响应将是
{
"q": [
"foo",
"bar"
]
}
仅使用 list¶
你也可以直接使用 list 而不是 list[str]
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[list, Query()] = []):
query_items = {"q": q}
return query_items
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: list = Query(default=[])):
query_items = {"q": q}
return query_items
注意
请记住,在这种情况下,FastAPI 不会检查列表的内容。
例如,list[int] 会检查(并记录)列表的内容是整数。但仅用 list 则不会。
声明更多元数据¶
你可以添加关于该参数的更多信息。
这些信息将包含在生成的 OpenAPI 中,并被文档用户界面和外部工具所使用。
注意
请记住,不同的工具对 OpenAPI 的支持程度可能不同。
其中一些可能尚未显示所有声明的额外信息,尽管在大多数情况下,缺失的功能已经在开发计划中。
你可以添加一个 title
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[str | None, Query(title="Query string", min_length=3)] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: str | None = Query(default=None, title="Query string", min_length=3),
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
以及一个 description
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[
str | None,
Query(
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
),
] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: str | None = Query(
default=None,
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
),
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
参数别名¶
假设你希望参数名为 item-query。
就像在
http://127.0.0.1:8000/items/?item-query=foobaritems
中一样,但 item-query 不是一个有效的 Python 变量名。
最接近的是 item_query。
但你仍然需要它正好是 item-query……
那么你可以声明一个 alias(别名),该别名将用于查找参数值
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: Annotated[str | None, Query(alias="item-query")] = None):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(q: str | None = Query(default=None, alias="item-query")):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
弃用参数¶
现在假设你不再喜欢这个参数了。
你必须暂时保留它,因为有客户端正在使用它,但你希望文档清楚地将其显示为 已弃用(deprecated)。
那么将参数 deprecated=True 传递给 Query
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: Annotated[
str | None,
Query(
alias="item-query",
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
max_length=50,
pattern="^fixedquery$",
deprecated=True,
),
] = None,
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
q: str | None = Query(
default=None,
alias="item-query",
title="Query string",
description="Query string for the items to search in the database that have a good match",
min_length=3,
max_length=50,
pattern="^fixedquery$",
deprecated=True,
),
):
results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
if q:
results.update({"q": q})
return results
文档将这样显示它

从 OpenAPI 中排除参数¶
要将查询参数从生成的 OpenAPI 模式(从而从自动文档系统中)中排除,请将 Query 的 include_in_schema 参数设置为 False
from typing import Annotated
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
hidden_query: Annotated[str | None, Query(include_in_schema=False)] = None,
):
if hidden_query:
return {"hidden_query": hidden_query}
else:
return {"hidden_query": "Not found"}
🤓 其他版本和变体
提示
如果可能,请优先使用 Annotated 版本。
from fastapi import FastAPI, Query
app = FastAPI()
@app.get("/items/")
async def read_items(
hidden_query: str | None = Query(default=None, include_in_schema=False),
):
if hidden_query:
return {"hidden_query": hidden_query}
else:
return {"hidden_query": "Not found"}
自定义验证¶
有些情况下,你需要进行一些无法通过上述参数完成的自定义验证。
在这些情况下,你可以使用一个自定义验证器函数,它在常规验证之后(例如在验证值确实是 str 之后)执行。
你可以通过在 Annotated 中使用 Pydantic 的 AfterValidator 来实现。
提示
Pydantic 还有 BeforeValidator 等等。🤓
例如,这个自定义验证器检查项目 ID 是否以 isbn- 开头(用于 ISBN 书号),或者以 imdb- 开头(用于 IMDB 电影 URL ID)
import random
from typing import Annotated
from fastapi import FastAPI
from pydantic import AfterValidator
app = FastAPI()
data = {
"isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
"imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
"isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}
def check_valid_id(id: str):
if not id.startswith(("isbn-", "imdb-")):
raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
return id
@app.get("/items/")
async def read_items(
id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
if id:
item = data.get(id)
else:
id, item = random.choice(list(data.items()))
return {"id": id, "name": item}
注意
这适用于 Pydantic 2 及以上版本。😎
提示
如果你需要进行任何需要与外部组件(如数据库或另一个 API)通信的验证,你应该改用 FastAPI 依赖项(Dependencies),稍后你将学到相关内容。
这些自定义验证器适用于仅依靠请求中提供的相同数据即可检查的事项。
理解这段代码¶
重点仅仅是在 Annotated 内部使用带有函数的 AfterValidator。请随意跳过这部分。🤸
但如果你对这个特定的代码示例感到好奇,且依然感兴趣,这里有一些额外细节。
带有 value.startswith() 的字符串¶
你注意到了吗?使用 value.startswith() 的字符串可以接收一个元组,它会检查元组中的每个值
# Code above omitted 👆
def check_valid_id(id: str):
if not id.startswith(("isbn-", "imdb-")):
raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
return id
# Code below omitted 👇
👀 完整文件预览
import random
from typing import Annotated
from fastapi import FastAPI
from pydantic import AfterValidator
app = FastAPI()
data = {
"isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
"imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
"isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}
def check_valid_id(id: str):
if not id.startswith(("isbn-", "imdb-")):
raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
return id
@app.get("/items/")
async def read_items(
id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
if id:
item = data.get(id)
else:
id, item = random.choice(list(data.items()))
return {"id": id, "name": item}
随机项¶
通过 data.items(),我们得到一个包含键值对元组的 可迭代对象。
我们使用 list(data.items()) 将该可迭代对象转换为一个真正的 list。
然后使用 random.choice(),我们可以从列表中获取一个随机值,即得到一个包含 (id, name) 的元组。它看起来会像 ("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")。
然后我们将元组的那两个值赋值给变量 id 和 name。
所以,如果用户没有提供项目 ID,他们仍然会收到一个随机建议。
……我们用一行简单的代码完成了所有这些工作。🤯 你不觉得 Python 很棒吗?🐍
# Code above omitted 👆
@app.get("/items/")
async def read_items(
id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
if id:
item = data.get(id)
else:
id, item = random.choice(list(data.items()))
return {"id": id, "name": item}
👀 完整文件预览
import random
from typing import Annotated
from fastapi import FastAPI
from pydantic import AfterValidator
app = FastAPI()
data = {
"isbn-9781529046137": "The Hitchhiker's Guide to the Galaxy",
"imdb-tt0371724": "The Hitchhiker's Guide to the Galaxy",
"isbn-9781439512982": "Isaac Asimov: The Complete Stories, Vol. 2",
}
def check_valid_id(id: str):
if not id.startswith(("isbn-", "imdb-")):
raise ValueError('Invalid ID format, it must start with "isbn-" or "imdb-"')
return id
@app.get("/items/")
async def read_items(
id: Annotated[str | None, AfterValidator(check_valid_id)] = None,
):
if id:
item = data.get(id)
else:
id, item = random.choice(list(data.items()))
return {"id": id, "name": item}
回顾¶
你可以为你的参数声明额外的验证和元数据。
通用验证和元数据
aliastitledescriptiondeprecated
字符串特有的验证
min_lengthmax_lengthpattern
使用 AfterValidator 的自定义验证。
在这些示例中,你看到了如何声明 str 值的验证。
请参阅后续章节,学习如何为其他类型(如数字)声明验证。