查詢參數與字串驗證¶
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 會知道它不是必填。
注意
FastAPI 會因為預設值是 = None 而知道 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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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 會出錯。
請先至少 升級 FastAPI 版本 到 0.95.1 再使用 Annotated。
在 q 參數的型別中使用 Annotated¶
還記得先前在 Python 型別介紹 提到可以用 Annotated 為參數加入中繼資料嗎?
現在就用在 FastAPI 上吧。🚀
我們原本的型別註記是:
q: str | None = None
接著把它用 Annotated 包起來,變成:
q: Annotated[str | None] = None
這兩種寫法代表的意思相同:q 可以是 str 或 None,且預設是 None。
現在來做有趣的部分。🎉
在 q 參數的 Annotated 中加入 Query¶
既然我們有可以放更多資訊的 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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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
🤓 Other versions and variants
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),我們必須用 Query() 取代函式中的預設值 None,因此需要用 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 中¶
注意,把 Query 放在 Annotated 內時,不能使用 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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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
加入正規表示式¶
你可以定義參數必須符合的 regular expression 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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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 明確定義查詢參數時,也可以讓它接收一個值的清單;換句話說,就是「多個值」。
例如,要宣告查詢參數 q 可以在 URL 中出現多次,你可以這樣寫:
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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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:
http://localhost:8000/items/?q=foo&q=bar
你會在路徑操作函式的參數 q 中,收到多個 q 查詢參數的值(foo 與 bar),以 Python 的 list 形式。
因此,對該 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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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
如果你前往:
http://localhost: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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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 中,供文件 UI 與外部工具使用。
注意
請留意,不同工具對 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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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=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
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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"}
🤓 Other versions and variants
Tip
Prefer to use the Annotated version if possible.
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 依賴」(FastAPI Dependencies),你稍後會學到。
這些自訂驗證器適用於只需使用請求中「同一份資料」即可完成的檢查。
理解這段程式碼¶
重點就是在 Annotated 中使用「AfterValidator 搭配函式」。如果你願意,可以略過這一節。🤸
但如果你對這個範例感到好奇且仍有興致,以下是一些額外細節。
使用 value.startswith() 的字串¶
你注意到了嗎?字串的 value.startswith() 可以接收一個 tuple,並逐一檢查 tuple 中的每個值:
# 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 👇
👀 Full file preview
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() 我們會得到一個包含每個字典項目鍵值對 tuple 的 iterable object。
我們用 list(data.items()) 把這個可疊代物件轉成正式的 list。
接著用 random.choice() 從清單中取得一個「隨機值」,也就是一個 (id, name) 的 tuple。可能像是 ("imdb-tt0371724", "The Hitchhiker's Guide to the Galaxy")。
然後把這個 tuple 的兩個值分別指定給變數 id 和 name。
因此,即使使用者沒有提供 item 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}
👀 Full file preview
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 值宣告驗證。
接下來的章節會示範如何為其他型別(像是數字)宣告驗證。