Pydantic用type hint实现数据验证, 2023-0

2023-05-06  本文已影响0人  Mc杰夫

(2023.05.07 Sun @KLN HK)
几个基本概念:

type hint优势

  1. 提供了对代码的文档注释,包括函数输入的类型和返回类型,类方法的相关信息
  2. 提高代码可读性、可编辑、可修改性
  3. 构建和保持一个清晰干净的架构

当然type hint也有劣势

  1. 占用更多开发时间
  2. 启动时间变长,特别是如果使用typing包,在短脚本中启动导入的时间显著提升
  3. 对早期没有引入Python annotations版本的并无显著的type hint效果

Pydantic

pydantic是使用python type hints的数据验证工具。一个典型的使用流程是预先定义data model中的字段和类型、是否required等信息,并将输入的数据,如JSON,转化为该data model。如果输入数据与data model中的定义有偏差,则转化时将报错,这样就实现了类静态类型检测的特性。Pydantic常见于FastAPI的应用。

案例

# python 3.9
from datetime import datetime
from typing import Optional
from pydantic import BaseModel

class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: Optional[datetime] = None
    friends: list[int] = []

external_data = {
    'id': '123',
    'signup_ts': '2019-06-01 12:22',
    'friends': [1, 2, '3'],
}

>> user = User(**external_data)
>> user
User(id=123, signup_ts=datetime.datetime(2019, 6, 1, 12, 22), friends=[1, 2, 3], name='John Doe')
>> print(user.id)
123
>> print(repr(user.signup_ts))
datetime.datetime(2019, 6, 1, 12, 22)
>> print(user.friends)
[1, 2, 3]
>> print(user.dict())
"""
{
    'id': 123,
    'signup_ts': datetime.datetime(2019, 6, 1, 12, 22),
    'friends': [1, 2, 3],
    'name': 'John Doe',
}
"""

定义data model,继承自pydantic的BaseModel,并在子类中定义变量和类型、默认值。使用typing.Optional方法,表明该变量并非required变量,当用户定义时不提供该变量值则默认为None。另外也可以从typing中引入Union方法,该方法中标识出的类型是数据可能定义的类型,形式如

a: Union[<data_type1>, <data_type2>, ...]

from typing import Union
from pydantic import BaseModel

class test(BaseMode):
    ua: Union[int, str]  # this variable could be int or str

对比OptionalUnion,可以看到Optional[<data_type>]相当于Union[<data_type>, None]

注意到在Python 3.10 and beyond,Optional可以用|代替。前面的代码在3.10 and beyond可以写成

from datetime import datetime
from pydantic import BaseModel


class User(BaseModel):
    id: int
    name = 'John Doe'
    signup_ts: datetime | None = None
    friends: list[int] = []

Model的built-in method

(2023.06.04 Sun KLN HK)
实例化一个model之后,找到内置方法大致如下

'construct', 'copy', 'dict', 'from_orm', 'json', 'parse_file', 'parse_obj', 
'parse_raw', 'schema', 'schema_json', 'update_forward_refs'
>> class User(BaseModel):
...     id: int
...     name="J D"
>> u1 = User(id='123')
>> u1.dict()
{'id': 123, 'name': 'J D'}
>> u1.json()
'{"id": 123, "name": "J D"}'
>> u1.schema()
{'title': 'User', 'type': 'object', 'properties': {'id': {'title': 'Id', 'type': 'integer'}, 
'name': {'title': 'Name', 'default': 'J D', 'type': 'string'}}, 'required': ['id']}

ValidationError

pydantic提供了数据验证返回的错误类型ValidationError,一旦提供的数据与定义的data model不匹配,则返回该错误。

from pydantic import ValidationError

try:
    User(signup_ts='broken', friends=[1, 2, 'not number'])
except ValidationError as e:
    print(e.json())

返回结果如

[
  {
    "loc": [
      "id"
    ],
    "msg": "field required",
    "type": "value_error.missing"
  },
  {
    "loc": [
      "signup_ts"
    ],
    "msg": "invalid datetime format",
    "type": "value_error.datetime"
  },
  {
    "loc": [
      "friends",
      2
    ],
    "msg": "value is not a valid integer",
    "type": "type_error.integer"
  }
]

pydantic和Enum

(2023.06.17 Sat @SZ 颖隆大厦)
pydantic通过在model中指定字段可以实现对数据字段的限制。而python的enum结合pydantic使用可以实现对特定字段中可选值的限制,将指定字段的值限制在预先定义的集合中,数据初始化中一旦赋予该字段的值超出预定集合,则初始化将会失败。同时,通过enum的使用,结合FastAPI等工具,可实现下拉列表功能。

在下面案例中,定义宠物的信息,宠物model Pet包括namesextypes三个字段,其中sex可选值只有malefemaletypes可选值仅包含常见的几种驯化动物。sextypes的枚举类型都有Enum的衍生类来定义。

from enum import Enum
from pydantic import BaseModel

class Sex(Enum):
    MALE = "male"
    FEMALE = "female"

class DomesticAnimal(Enum):
    BIRD = "bird"
    CAT = "cat"
    DOG = "dog"
    TURTLE = "turtle"
    FISH = "fish"

class Pet(BaseModel):
    name: str
    sex: Sex
    types: DomesticAnimal

可以使用两种方式定义一个Pet对象。

>> a1 = {'name': 'Cathy', 'sex': 'female', 'types': 'dog'}
>> ca = Pet(**a1)
# or
>> ca = Pet(name='Cathy', sex='female', types='dog')
>> ca
Pet(name='Cathy', sex=<Sex.FEMALE: 'female'>, types=<DomesticAnimal.DOG: 'dog'>)

看到ca对象的结果正常显示。如果在定义时,将其中的types改成没有在DomesticAnimal中定义的种类,则会返回错误。

>> cheetah = Pet(name="jizhitangjiang", sex="male", types="cheetah")
---------------------------------------------------------------------------
ValidationError                           Traceback (most recent call last)
/var/folders/d1/s0h2kl014l1bkb0_q0_g5vpr0000gn/T/ipykernel_85550/2575694669.py in <module>
----> 1 cheetah = Pet(name="jizhitangjiang", sex="male", types="cheetah")

~/opt/anaconda3/lib/python3.9/site-packages/pydantic/main.cpython-39-darwin.so in pydantic.main.BaseModel.__init__()

ValidationError: 1 validation error for Pet
types
  value is not a valid enumeration member; permitted: 'bird', 'cat', 'dog', 'turtle', 'fish' (type=type_error.enum; enum_values=[<DomesticAnimal.BIRD: 'bird'>, <DomesticAnimal.CAT: 'cat'>, <DomesticAnimal.DOG: 'dog'>, <DomesticAnimal.TURTLE: 'turtle'>, <DomesticAnimal.FISH: 'fish'>])

Error message中显示初始化的赋值超出可选范围。

pydantic的BaseModel结合python Enum可实现对指定字段的可选值的选择。

Reference

1 pydantic 点 dev

上一篇 下一篇

猜你喜欢

热点阅读