pydantic 数据校验(v1)
安装
基本使用
假定我们有一个基于pydantic
的模型类如下:
Python |
---|
| from pydantic import BaseModel
class MyModel(BaseModel):
x: Type # Type描述详见下面的表格
|
pydantic描述的数据有如下几种类型
数据校验
基本校验方式
下面的代码用来校验月份数值是否在1到12之间,不牵扯其他字段,直接使用validator
来校验即可
Python |
---|
| import pydantic
class A(pydantic.BaseModel):
month: int
@pydantic.validator('month')
def validate_month(cls, v):
if not 1 <= v <= 12:
raise ValueError(f"month value must be in [1, 12]")
return v
a1 = A(month=15)
|
运行后会抛出ValueError
异常,即校验失败
基于A字段校验B字段
在pydantic
中,如果要校验多个字段field
,可以导入模型的全部属性解决,使用root_validator
可以使用自定义规则校验模型的全部字段
Python |
---|
| import typing
import pydantic
class A(pydantic.BaseModel):
month: int
months: typing.List[int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
@pydantic.root_validator
def validate_month(cls, values):
if values['month'] not in values['months']:
raise ValueError(f"month value must be in [1, 12]")
return values
a1 = A(month=15)
|
额外字段校验
对于pydantic中没有定义的字段,默认忽略不解析。但另一方面,也可以使用Extra
类来管理,设置为 Extra.forbid
即可,参考Field Types - Pydantic
Python |
---|
| """
pydantic 1.x
"""
from pydantic import BaseModel, Extra
class Person(BaseModel):
name: str
age: int
class Config:
extra = Extra.forbid # 不允许多余字段
p1 = Person(name='John', age=30) # OK
p2 = Person(name='John', age=30, country='USA') # will raise ValidationError
# pydantic_core._pydantic_core.ValidationError: 1 validation error for Person
# country
# Extra inputs are not permitted [type=extra_forbidden, input_value='USA', input_type=str]
|
模型类使用
序列化与实例化
pydantic.BaseModel
可以非常容易地进行对象的序列化,可以很方便地转成Python的dict类型和标准的json字符串,示例如下
Python |
---|
| """
pydantic 1.x
"""
import json
import typing
from pydantic import BaseModel
class A(BaseModel):
x: int
y: typing.Optional[str]
z: typing.Optional[list] = [1, 2, 3]
a = A(x=1)
ad = a.dict()
aj = a.json()
ja = json.loads(aj)
aa = A.parse_obj(ad)
|
对可选字段的对象反序列化注意
在上面的实例中,对于模型A
的字段y
,如果实例化时没有赋值,那么实例化后的对象中y=None
,完整字典形式为ad={'x': 1, 'y': None, 'z': [1, 2, 3]}
,此时不能将字典ad
直接使用A.__init__
进行反序列化,因为在模型类A
中,只允许str
类型的可选字段y
获取模型类中带有默认属性值的字段
Python |
---|
| from typing import Optional
from pydantic import BaseModel
class MyModel(BaseModel):
a: int = 1
b: Optional[str]
c: list
@classmethod
def default_attrs(cls):
properties = cls.schema()['properties']
return {k: v['default'] for k, v in properties.items() if v.get('default') is not None}
x = MyModel.default_attrs() # {'a': 1}
|
单元测试
在Python标准库unittest
中,无法直接mock
掉基于pydantic
创建的对象方法。

play_pydantic/src.py
Python |
---|
| import pydantic
class A(pydantic.BaseModel):
month: int
@pydantic.validator('month')
def validate_month(cls, v):
if not 1 <= v <= 12:
raise ValueError(f"month value must be in [1, 12]")
return v
def get_next_month(self):
return self.month + 1 if self.month < 12 else 1
|
play_pydantic/test.py
直接mock.Mock()
将会报错
Python |
---|
| import unittest
from unittest import mock
from play_pydantic.src import A
class TestA(unittest.TestCase):
def test_a_cannot_be_mocked(self):
a = A(month=1)
a.get_next_month = mock.Mock()
if __name__ == "__main__":
unittest.main()
|

此时其实可以使用mock.patch()
解决即可
Python |
---|
| import unittest
from unittest import mock
from play_pydantic.src import A
class TestA(unittest.TestCase):
def test_a_can_be_patched(self):
with mock.patch("play_pydantic.src.A.get_next_month") as fake_fun:
fake_fun.return_value = 0
a = A(month=2)
self.assertEqual(0, a.get_next_month())
if __name__ == "__main__":
unittest.main()
|
参考