14
Pydantic 小筆記
故事要從 Python 的語言特性說起,因為 Python 是動態型別的,所以一個在同一個區塊(命名空間)內的變數 x
,可以一下是字串,一下是整數:
x = 4
print(type(4)) # at this moment, x points to an integer
x = "Hello, world"
print(type(x)) # and at this moment, x points to a string
這樣的特性降低了 Python 的學習曲線,但在程式專案變複雜後,對於「值」與「型態」的掌握會越來越難以駕馭。
大約在 Python 3.5 起引入了 type hints,華文叫類型提示或型態提示,它讓我們得以聲明變數的型態,也可以聲明函式回傳值的型態。
有了 type hints,IDE 或是靜態分析工具就可以幫助我們偵測型態上的錯誤,也因為它只是 hints,所以它終究無法讓 Python 變成靜態型別語言,只是一種輔助而已。
下面是最基本的 type hints 範例:
name: str = "world"
def greeting(name: str) -> str:
return "Hello " + name
greeting(name)
上面我們用 : str
聲明 name
為字串,以及在函式我們用 -> str
聲明 greeting()
回傳的也是字串,當然還有更複雜的用法,可以參見〈Python Type Hints 從入門到實踐〉。
以上為前情提要,下面開始談本文的主角 Pydantic 。
Pydantic 是以 type hints 為基礎,幫我們做資料型態驗證的套件,我們可以用它定義「Model」:
from pydantic import BaseModel
class EmployeeModel(BaseModel):
name: str
salary: int
在這個 EmployeeModel
中,我們聲明了 name
應為字串、salary
應為整數,透過繼承自 BaseModel
的特性,pydantic 會自動幫我們驗證型態的正確性。
如果試圖建立一個實例,並提供正確的型態,那不會有任何問題(也不會有任何提示說我們好棒棒):
employee = EmployeeModel(
name='Bar',
salary=1000,
)
但如果給了一個錯誤的型態,那麼 pydantic 會引發錯誤:
employee = EmployeeModel(
name='Bar',
salary='secret',
)
應該要是整數的 salary
,卻被塞了字串,引發 validation error:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/leon/Projects/pydantic/.venv/lib/python3.10/site-packages/pydantic/main.py", line 406, in __init__
raise validation_error
pydantic.error_wrappers.ValidationError: 1 validation error for EmployeeModel
salary
value is not a valid integer (type=type_error.integer)
在應用上,新興的 Python 框架 FastAPI 深度整合了 pydantic 的類型驗證特性,只要在程式碼內事先聲明類型,所有的 API 端點都自動的具備型態驗證機制,我輩開發者再也不用自己刻 e-mail、URL 等等繁瑣的驗證邏輯,香!
下面是個極簡的 FastAPI 範例:
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
在 Item model 中,我們定義了四個屬性,其中的 description
與 tax
的 type hints 是 Optional[str]
,而預設值為 None
,Optional[str]
意味著此屬性是選填的,如果建立實例時未填,則屬性值為 None
。
最後一個區塊定義了一個接受 POST 的 API 端點 /items/,此端點的處理函式 create_item(item: Item)
接受一個參數 item
,並且該參數之型態應為前面定義的 Item model,在這樣的的定義與聲明下,前端送過來的 request body 會自動的被 pydantic 解析與驗證,並且那 FastAPI 自動產生的 OpenAPI 文件也會載明該端點的 JSON schema:
在開發環境(development)與生產環境(production)採用的參數可能不同,例如連接 Twitter API 的帳密、連接 MongoDB 的帳密等等,這類參數習慣上會把它與程式碼分開,另外放在 .env 檔案內,再透過像 python-dotenv 這樣的套件把 .env 讀入成為可調用的變數。Pydantic 也整合了 python-dotenv,可以幫我們做到上述工作,這部份的用法請參閱下面兩篇:
說個題外話,關於 .env 檔案,有一條常見的鐵律是不要把 .env 提交到版控系統,個人是不太認同,只要我的 Git repository 是私有的,自架的,本機的,提交到 Git repository 又何妨?版控≠開源,當我的專案是閉源的,.env 的地位就與任何一份原碼一樣隱私,不具有特殊地位,我們要守護的也不僅是 .env,而是整個專案。
綜觀以上,只要在程式碼內定義好型態,就可以享有:
- IDE 或靜態分析工具幫我們揪錯,以及程式碼的可讀性更高,大型專案更好維護
- Pydantic 幫我們檢查打來 API 的值的型態正確與否,省去自己寫驗證邏輯的功夫,time to market 時間再省一半
- FastAPI 自動根據我們定義的型態產生 OpenAPI 文件與規格,再也不用寫 API 規格書,再也不用回覆前端同事的私訊
14