重溫舊夢:
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 請求所傳送的多項參數。
我們下次見!
<未完待續>