重溫舊夢:
TL; DR
做為一個 API 框架, FastAPI 需要以多種方式接受外部傳送的變數(例如 GET、POST)。
本篇筆記試著說明 FastAPI 的 Path Parameters、Query Parameters 的區別,以及適用的情境。
Path Parameter
透過預先定義好的「位置」來接受參數
基本操作
使用 Path Parameter 時,使用{}在路徑中設定一個參數,舉例來說:
我們希望建立一個 GET 的 API,可以接受使用者的名字 user_name:
from fastapi import FastAPI
app = FastAPI()
@app.get("/users/{user_name}") # 透過{user_name} 設定參數位置
async def read_user(user_name):
return {"user_name": user_name}
在 read_user(user_name) 中定義了 user_name 參數後, FastAPI 會自動去註冊的路徑中尋找 {user_name} 的位置。
當FastAPI接收到符合條件的 Request(/users/{user_name}) 時,會自動將 {user_name} 位置的值當成read_user() 的 user_name 參數送入。
舉例來說:
當 API 運行後,在瀏覽器輸入: http://127.0.0.1:8000/users/MingLun
由於註冊了
users/{user_name}規則,FastAPI識別到該規則,因此將{user_name}位置的值(MingLun)當作參數送入read_user()根據函式的運行邏輯,將會得到下列的回傳值:
{"user_name": "MingLun"}
預先設定型態
在設定參數時,可以預先定義變數型態,
FastAPI會協助進行轉換及驗證。
了解 Path Variable 的基本使用方式後,可以在函式設定參數時,加入 Python 的 Typing Hint:
重溫舊夢 - Fast API 入門筆記(二) - Typing Hint & Async
from fastapi import FastAPI
app = FastAPI()
@app.get("/get_number/{number}")
async def read_number(number: int):
return {"number": number}
此時如果在瀏覽器輸入: http://127.0.0.1:8000/get_number/1313
FastAPI執行時會以str的格式將"1313"當成參數送入函式,但由於在 read_number() 中定義的 number 參數已經定義了型態 int,FastAPI處理時會嘗試將 "1313" 轉換為 int 格式,因此你將會得到:
{"number": 1313}
而非:
{"number": "1313"}
例外情況: 如果出現無法轉換的情況
接續上述的情境,倘若此時我們在瀏覽器輸入: http://127.0.0.1/get_number/MingLun
此時 FastAPI 同樣會試著將 {number} 位置的參數轉換為 int 格式,也就是將 MingLun 轉換為 int,可預見此時會發生錯誤。
有趣的是,當出現這種意外情境時, FastAPI不會直接噴出 500 Internal Server Error,而是會有固定的 HTTP Error 範本,提供使用者基本的錯誤資訊:
{
"detail": [
{
"loc": [
"path",
"number"
],
"msg": "value is not a valid interger",
"type": "type_error.integer"
}
]
}
順序帶來的影響
在使用
Path Parameter時,多個 EndPoint 的順序是會有影響的
由於 FastAPI 可定義多層路徑 (Ex: /A/B/C),所以有時候會發生多個 EndPoint 路徑重疊的情況,此時 FastAPI 將會依照註冊的順序查找。
舉例來說,我們設計一個 API Endpoint (get_info),可以根據使用者的pID 來取得相關資訊。 同時如果提供了一個保留字 (SUPERUSER),則回傳不一樣的資訊。
(實際上沒有人會這樣設計,這只是個範例)
from fastapi import FastAPI
app = FastAPI()
@app.get("/get_info/SUPERUSER")
async def get_info_super_user():
return {"user": "YOU ARE SUPER USER!"}
@app.get("/get_info/{pID}")
async def get_info(pID: str):
return {"user": pID}
當FastAPI接收到: http://127.0.0.1/get_info/SUPERUSER時,會從上至下檢查各個註冊的路徑。
以上述範例來說,會進入 get_info_super_user()函式,得到特殊的回應。
接著看看錯誤的情境,我們將兩個路徑對調:
from fastapi import FastAPI
app = FastAPI()
@app.get("/get_info/{pID}") # 原本在下方
async def get_info(pID: str):
return {"user": pID}
@app.get("/get_info/SUPERUSER") # 原本在上方
async def get_info_super_user():
return {"user": "YOU ARE SUPER USER!"}
此時再次發送 http://127.0.0.1/get_info/SUPERUSER 會發生什麼事情?
FastAPI 接受到請求後,由上而下判斷規則,當判斷到第一個規則(也就是 get_info())時,會直接進入執行,回傳:
{"user": "SUPERUSER"}
順序擺放錯誤時,永遠無法進入 get_info_super_user() 的 Endpoint.
設計時,條件較為嚴格的EndPoint,要放在較上方
Query Parameter
用來接受URL所附加的參數,與
Path Parameter最大的差別在於沒有預先定義位置
透過 GET 請求傳送多個參數時,常會附加在URL上,例如:
http://xxx.example.com/target?param1=a¶m2=b
接收這些變數並進行處理,就是 Query Parameter 所負責的任務。
基本操作
from fastapi import FastAPI
app = FastAPI()
@app.get("/path_param_example/{pID}") # 路徑中包含參數,屬於 Path Parameter
async def path_param_example(pID: str):
return {"user": pID}
@app.get("/query_param_example") # 路徑中不包含參數,屬於 Query Parameter
async def query_param_example(pID: str):
return {"user": pID}
從上述範例中,我們可以區別 Query Parameter 與 Path Parameter 的最大差異是 : 路徑中是否有預留參數的位置。
以 Query Parameter 來說,由於沒有在路徑中預留參數的位置,參數會掛載在URL後。
以上方案例來說,傳送的方式會如下:
http://127.0.0.1/query_param_example?pID=abc01
預先設定型態
與 Path Parameter 相同,使用 Query Parameter 時也能透過預先定義參數型態,來觸發 FastAPI 自動檢驗、轉換的功能:
from fastapi import FastAPI
app = FastAPI()
@app.get("/query_param_str")
async def query_param_str(pID: str): # 轉換成字串
return {"user": pID}
@app.get("/query_param_int")
async def query_param_int(pID: int): # 轉換成整數
return {"user": pID}
在上述的例子中,我們定義了兩個EndPoint,差別在於設定參數時,分別指定了 str 和 int 格式。
當我們同時傳送:
http://127.0.0.1/query_param_str?pID=1234
由於
pID被預設為str,將會收到:{"user": "1234"} # 字串型態http://127.0.0.1/query_param_int?pID=1234
由於
pID被預設為int,將會收到:{"user": 1234} # 整數型態
FastAPI 會根據參數所指定的型態,自動轉換收到的值,當轉換失敗時,同樣會回傳結構化的錯誤訊息。
透過參數的設置來決定行為
在上述筆記中,透過參數的設置,能夠設定 FastAPI 在接收外部傳送的值時,如何進行型態的轉換。
實際上,透過參數設置,還能做到更多的行為:
1. 設定預設值
在參數上設定預設值時,使用者呼叫 API Endpoint 時,如果沒有夾帶該參數,會自動以預設值帶入:
@app.get("/default_param")
async def query_param_str(param_a: str, param_b: str="example"): # 轉換成字串
return {"param_a": param_a, "param_b": param_b}
當使用者輸入: http://127.0.0.1/default_param¶m_a=abc
將會收到:
{"param_a": "abc", "param_b": "example"}
2. 選擇性參數
在建立 Endpoint 時,有些參數是 Optional ,不見得每次都要攜帶,例如建立一個 Endpoint 有下列條件:
- 必須要提供
user_name - 不一定要提供
gender
此時我們可以透過 Typing Hint 來讓 gender 欄位可以是 str 或是 None(不存在):
from typing import Union
def optional_example(user_name:str, gender: Union[str, None]):
return {"user_name": user_name, "gender": gender}
除了使用 Union 額外指定 None 型態外,還可以使用更簡潔的語法 Optional:
from typing import Optional
def optional_example(user_name:str, gender: Optional[str]):
return {"user_name": user_name, "gender": gender}
Optional[str]與Union[str, None]是完全等價的
結論
本篇筆記介紹了 FastAPI 的 Path Parameter 和 Query Parameter,除了比較兩者的差別,也介紹 FastAPI 如何透過參數的設定來達到「型態轉換」、「結構化錯誤訊息」。
Path Parameter是接受特定位置的參數,而Query Parameter 則是接受 「GET 請求掛載在URL後面的參數」。
在下一篇筆記,我們會更進一步介紹如何使用 FastAPI 的 BaseModel 來定義「參數物件」,接受 POST 請求所傳送的多項參數。
我們下次見!
<未完待續>