Fast API 入門筆記 (三) - Query Parameter & Path Parameter

Posted by MingLun Allen Wu on Friday, January 7, 2022

重溫舊夢:

TL; DR

做為一個 API 框架, FastAPI 需要以多種方式接受外部傳送的變數(例如 GETPOST)。

本篇筆記試著說明 FastAPIPath ParametersQuery 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 參數送入。

  1. 當 API 運行後,在瀏覽器輸入: http://127.0.0.1:8000/users/MingLun

  2. 由於註冊了 users/{user_name} 規則,FastAPI 識別到該規則,因此將 {user_name} 位置的值(MingLun)當作參數送入 read_user()

  3. 根據函式的運行邏輯,將會得到下列的回傳值:

{"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 參數已經定義了型態 intFastAPI處理時會嘗試將 "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 ParameterPath 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,差別在於設定參數時,分別指定了 strint 格式。

當我們同時傳送:

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] 是完全等價的

結論

本篇筆記介紹了 FastAPIPath ParameterQuery Parameter,除了比較兩者的差別,也介紹 FastAPI 如何透過參數的設定來達到「型態轉換」、「結構化錯誤訊息」。

Path Parameter是接受特定位置的參數,而Query Parameter 則是接受 「GET 請求掛載在URL後面的參數」。

在下一篇筆記,我們會更進一步介紹如何使用 FastAPIBaseModel 來定義「參數物件」,接受 POST 請求所傳送的多項參數。

我們下次見!

<未完待續>



See Also

監控資料品質的旅程