如果你正在做接口开发,或者任何函数的开发,总会遇到需要判断参数是否正确的问题。一个一个验证不是很合理,所幸有pydantic这个包可以帮我们完成这项工作,而且是完美的完成
使用验证器装饰器可以实现对象之间的自定义验证和复杂关系。
from pydantic import BaseModel, ValidationError, validator
class UserModel(BaseModel):
name: str
username: str
password1: str
password2: str
@validator('name')
def name_must_contain_space(cls, name):
if ' ' not in name:
raise ValueError('must contain a space')
return name.title()
@validator('password2')
def passwords_match(cls, v, values, **kwargs):
if 'password1' in values and v != values['password1']:
raise ValueError('passwords do not match')
return v
@validator('username')
def username_alphanumeric(cls, v):
assert v.isalnum(), 'must be alphanumeric'
return v
user = UserModel(
name='samuel colvin',
username='scolvin',
password1='zxcvbn',
password2='zxcvbn',
)
print(user)
#> name='Samuel Colvin' username='scolvin' password1='zxcvbn' password2='zxcvbn'
try:
UserModel(
name='samuel',
username='scolvin',
password1='zxcvbn',
password2='zxcvbn2',
)
except ValidationError as e:
print(e)
"""
2 validation errors for UserModel
name
must contain a space (type=value_error)
password2
passwords do not match (type=value_error)
"""
被validator 包装的函数第二个参数是需要验证的字段的字段值
你也可以将下列参数的任何子集添加到签名中(名称必须匹配):
values:一个字典,包含任何以前验证过的字段的名称到值映射
config:模型配置
field:正在验证的字段。对象类型为pydantic.fields.ModelField。
**kwargs:如果提供,这将包括签名中没有显式列出的上述参数
from typing import List
from pydantic import BaseModel, ValidationError, validator
class DemoModel(BaseModel):
square_numbers: List[int] = []
cube_numbers: List[int] = []
# '*' is the same as 'cube_numbers', 'square_numbers' here:
# 这里的pre=True指的是在做正式验证之前要走的函数
@validator('*', pre=True)
def split_str(cls, v):
if isinstance(v, str):
return v.split('|')
return v
@validator('cube_numbers', 'square_numbers')
def check_sum(cls, v):
if sum(v) > 42:
raise ValueError('sum of numbers greater than 42')
return v
@validator('square_numbers', each_item=True)
def check_squares(cls, v):
assert v ** 0.5 % 1 == 0, f'{v} is not a square number'
return v
@validator('cube_numbers', each_item=True)
def check_cubes(cls, v):
# 64 ** (1 / 3) == 3.9999999999999996 (!)
# this is not a good way of checking cubes
assert v ** (1 / 3) % 1 == 0, f'{v} is not a cubed number'
return v
print(DemoModel(square_numbers=[1, 4, 9]))
#> square_numbers=[1, 4, 9] cube_numbers=[]
print(DemoModel(square_numbers='1|4|16'))
#> square_numbers=[1, 4, 16] cube_numbers=[]
print(DemoModel(square_numbers=[16], cube_numbers=[8, 27]))
#> square_numbers=[16] cube_numbers=[8, 27]
try:
DemoModel(square_numbers=[1, 4, 2])
except ValidationError as e:
print(e)
"""
1 validation error for DemoModel
square_numbers -> 2
2 is not a square number (type=assertion_error)
"""
try:
DemoModel(cube_numbers=[27, 27])
except ValidationError as e:
print(e)
"""
1 validation error for DemoModel
cube_numbers
sum of numbers greater than 42 (type=value_error)
"""
from typing import List
from pydantic import BaseModel, ValidationError, validator
class ParentModel(BaseModel):
names: List[str]
class ChildModel(ParentModel):
@validator('names', each_item=True)
def check_names_not_empty(cls, v):
assert v != '', 'Empty strings are not allowed.'
return v
# This will NOT raise a ValidationError because the validator was not called
try:
child = ChildModel(names=['Alice', 'Bob', 'Eve', ''])
except ValidationError as e:
print(e)
else:
print('No ValidationError caught.')
#> No ValidationError caught.
class ChildModel2(ParentModel):
@validator('names')
def check_names_not_empty(cls, v):
for name in v:
assert name != '', 'Empty strings are not allowed.'
return v
try:
child = ChildModel2(names=['Alice', 'Bob', 'Eve', ''])
except ValidationError as e:
print(e)
"""
1 validation error for ChildModel2
names
Empty strings are not allowed. (type=assertion_error)
出于性能原因,在没有提供值时,默认情况下不对字段调用验证器。然而,在有些情况下,它可能是有用的或需要总是调用验证器,例如设置一个动态默认值。
from datetime import datetime
from pydantic import BaseModel, validator
class DemoModel(BaseModel):
ts: datetime = None
@validator('ts', pre=True, always=True)
def set_ts_now(cls, v):
return v or datetime.now()
print(DemoModel())
#> ts=datetime.datetime(2022, 9, 5, 18, 0, 25, 664356)
print(DemoModel(ts='2017-11-08T14:00'))
#> ts=datetime.datetime(2017, 11, 8, 14, 0)
有时,你会想在多个字段/模型上使用相同的验证器(例如规范化一些输入数据)。“天真”的方法是编写一个单独的函数,然后从多个装饰器调用它。显然,这需要大量的重复和样板代码。为了避免这种情况,在pydantic中添加了allow_reuse参数。
from pydantic import BaseModel, validator
def normalize(name: str) -> str:
return ' '.join((word.capitalize()) for word in name.split(' '))
class Producer(BaseModel):
name: str
# validators
_normalize_name = validator('name', allow_reuse=True)(normalize)
class Consumer(BaseModel):
name: str
# validators
_normalize_name = validator('name', allow_reuse=True)(normalize)
jane_doe = Producer(name='JaNe DOE')
john_doe = Consumer(name='joHN dOe')
assert jane_doe.name == 'Jane Doe'
assert john_doe.name == 'John Doe'