用 pydantic 輕鬆進行資料驗證
Last updated on Mar 26, 2024 in Python 模組/套件推薦 by Amo Chen ‐ 4 min read
先前 用 pydantic 輕鬆進行設定管理(Settings management) 一文曾提及利用 pydantic 管理設定,但其實也可以驗證資料,本文將介紹如何透過 pydantic 驗證資料,保證有效減少開發驗證各種資料的繁瑣。
本文環境
- Python 3
- pydantic 2
$ pip install pydantic==1.7.3
Hello, BaseModel
pydantic 提供 BaseModel 讓開發者能夠透過繼承該類別並且利用 typing 註記類別屬性的型別,就能夠擁有基本的驗證功能。
例如以下範例定義 EmployeeModel 類別,並分別為 name
, username
, salary
, habits
註記(annotation)類別屬性 str
, str
, int
, List[str]
, 最後實際初始化該類別後指定給 employee
變數:
from typing import List
from pydantic import BaseModel, ValidationError, validator
class EmployeeModel(BaseModel):
name: str
username: str
salary: int
habits: List[str]
employee = EmployeeModel(
name='Bar',
username='bar',
salary=1000,
habits=[]
)
上述範例執行成功的話,並不會有任何錯誤。但如果故意將 salary
從 1000
改為 secret
的話,就會發現 pydantic 驗證 salary
不是合法的整數:
from typing import List
from pydantic import BaseModel, ValidationError, validator
class EmployeeModel(BaseModel):
name: str
username: str
salary: int
habits: List[str]
employee = EmployeeModel(
name='Bar',
username='bar',
salary='secret',
habits=[]
)
上述範例執行後將會出現以下錯誤,可以發現錯誤提示 salary
不是一個合法的整數,運用 pydantic 所提供的驗證功能,會更加方便開發:
Traceback (most recent call last):
File "pydantic_test.py", line 17, in <module>
habits=[]
File "pydantic/main.py", line 338, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for EmployeeModel
salary
value is not a valid integer (type=type_error.integer)
資料轉型
pydantic 在驗證 str
, int
等型別時,將會試圖轉型,如果該值能夠被正常轉型,那麼也就不會出現任何錯誤,例如以下範例, salary
期望的是整數值,但我們故意傳入字串形態的 '1000'
也不會出現任何錯誤:
from typing import List
from pydantic import BaseModel, ValidationError, validator
class EmployeeModel(BaseModel):
name: str
username: str
salary: int
habits: List[str]
EmployeeModel(
name='Bar',
username='bar',
salary='1000',
habits=[]
)
原因在於 '1000'
是能夠被 int()
正確轉成整數 1000 的,因此並不會出現任何驗證錯誤,詳見 Data Conversion 。
但如果我們想得到與上述註記的型別符合的資料,可以呼叫 .dict()
, .json()
等方法(詳見 Exporting models), pydantic 會確保輸出的資料符合註記的型別,例如以下範例:
from typing import List
from pydantic import BaseModel, ValidationError, validator
class EmployeeModel(BaseModel):
name: str
username: str
salary: int
habits: List[str]
employee = EmployeeModel(
name='Bar',
username='bar',
salary='1000',
habits=[]
)
print('output>', employee.dict())
上述範例輸出結果如下,可以看到我們傳入的字串 '1000'
被正確輸出為整數 1000:
output> {'name': 'Bar', 'username': 'bar', 'salary': 1000, 'habits': []}
Strict types
不過也由於資料轉型的特性,使得不合法型態的資料有機會被輸入並輸出,因此 pydantic 也提供Strict Types ,讓開發者能夠解決像前述在需要傳入 int
型別的地方,嚴格限制只有 int
型別才能傳入。
例如以下使用 StrictInt
註記 salary
屬性,如此一來就算是傳入字串 '1000'
也能夠正確驗證:
from typing import List
from pydantic import BaseModel, ValidationError, validator, StrictInt
class EmployeeModel(BaseModel):
name: str
username: str
salary: StrictInt
habits: List[str]
EmployeeModel(
name='Bar',
username='bar',
salary='1000',
habits=[]
)
上述範例故意傳入字串 '1000'
予 salary
,在 StrictInt
. 的作用下成功驗證出該型別不符:
Traceback (most recent call last):
File "pydantic_test.py", line 17, in <module>
habits=[]
File "pydantic/main.py", line 362, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for EmployeeModel
salary
value is not a valid integer (type=type_error.integer)
除了 StrictInt
之外, pydantic 也提供許多較嚴格的型別,如有需求可以翻閱 Strict Types 。
More pydantic types
Pydantic 也提供許多方便的型別,減少開發者一直重複開發驗證相關的程式,提高開發效率,例如以下範例運用 pydantic 的 HttpUrl
用以驗證 blog 屬性是 http
或者 https
開頭的合法 URL:
HttpUrl: schema http or https, TLD required, max length 2083
from typing import List, Optional
from pydantic import BaseModel, ValidationError, validator, StrictInt, HttpUrl
class EmployeeModel(BaseModel):
name: str
username: str
salary: StrictInt
habits: List[str]
blog: Optional[HttpUrl]
EmployeeModel(
name='Bar',
username='bar',
salary=1000,
habits=[],
blog="https://example.com"
)
更多 pydantic 提供的型別可參閱 Pydantic types ,其中包含 Email, Path, Json, IP 等多種實用的型別。
Model 設定
除了設定型別之外, pydantic 也提供一些設定能夠改變 Model 的行為。例如 pydantic BaseModel
預設忽略任意新增的屬性,也就是在下列範例中的 new_attribute
會被自動排除:
from typing import List, Optional
from pydantic import BaseModel, ValidationError, validator, StrictInt, HttpUrl
class EmployeeModel(BaseModel):
name: str
username: str
salary: StrictInt
habits: List[str]
blog: Optional[HttpUrl]
e = EmployeeModel(
name='Bar',
username='bar',
salary=1000,
habits=[],
blog="https://example.com",
new_attribute='Haha',
)
print(e)
上述範例執行結果如下,可以發現 new_attribute
被自動排除:
{'name': 'Bar', 'username': 'bar', 'salary': 1000, 'habits': [], 'blog': HttpUrl('https://example.com', scheme='https', host='example.com', tld='com', host_type='domain')}
如果要改變上述行為,可以在 Model 內定義一個 Config
類別並設定相關屬性,例如以下範例將 extra
設定為 allow
, 如此一來就能夠允許新增任意屬性:
from typing import List, Optional
from pydantic import BaseModel, ValidationError, validator, StrictInt, HttpUrl
class EmployeeModel(BaseModel):
name: str
username: str
salary: StrictInt
habits: List[str]
blog: Optional[HttpUrl]
class Config:
extra = 'allow'
e = EmployeeModel(
name='Bar',
username='bar',
salary=1000,
habits=[],
blog="https://example.com",
new_attribute='Haha',
)
print(e)
上述範例執行結果如下,可以發現 new_attribute
出現了:
{'name': 'Bar', 'username': 'bar', 'salary': 1000, 'habits': [], 'blog': HttpUrl('https://example.com', scheme='https', host='example.com', tld='com', host_type='domain'), 'new_attribute': 'Haha'}
此外,如果不允許任意新增屬性的話,可以將 allow
改為 forbid
。屆時如果遇到額外的屬性,就會出現類似以下的錯誤,提示 extra fields not permitted 告訴我們 model 中含有不被允許的額外屬性:
Traceback (most recent call last):
File "pydantic_test.py", line 23, in <module>
new_attribute='Haha',
File "pydantic/main.py", line 338, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 2 validation errors for EmployeeModel
salary
value is not a valid integer (type=type_error.integer)
new_attribute
extra fields not permitted (type=value_error.extra)
以上僅是如何設定 model 的 1 個範例,關於更多 model 的設定,可以參閱 Model Config 。
自製 Validator
除了透過型別註記(type annotation)驗證資料之外, pydantic 也允許開發者實作 validator 達到更細緻的驗證,例如下列範例利用裝飾子 validator
實作驗證 username
, password
2 個欄位的方法,並在驗證未通過時 raise exception 代表資料未通過驗證,使用起來十分簡單易懂:
from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel):
username: str
password: str
@validator('password')
def password_rules(cls, v, values, **kwargs):
if len(v) < 8:
raise ValueError('the password is too short')
if v.isalnum() or len(set(v)) <= 3:
raise ValueError('the password is too simple')
return v
@validator('username')
def username_alphanumeric(cls, v):
assert v.isalnum(), 'the username must be alphanumeric'
return v
如果,故意將 password
設定小於長度 3 的字串,範例如下:
user = UserModel(username='foo', password='')
執行時,就會出現以下錯誤,告訴你 the password is too short
:
Traceback (most recent call last):
File "test.py", line 22, in <module>
user = UserModel(username='foo', password='')
File "pydantic/main.py", line 341, in pydantic.main.BaseModel.__init__
pydantic.error_wrappers.ValidationError: 1 validation error for UserModel
password
the password is too short (type=value_error)
當然 pydantic 也不只提供 validator
而已,也提供各種不同用途的 validator 可供使用,例如 root_validator
可一起驗證 model 內所有的屬性。
更多的 validator 使用方法請詳見 Validators .
以上就是如何使用 pydantic 驗證資料的簡短介紹。
Happy Coding!
References
https://pydantic-docs.helpmanual.io/