参考:pydantic 官方文档
pydantic库是一种常用的用于数据接口schema定义与检查的库。
通过pydantic库,我们可以更为规范地定义和使用数据接口,这对于大型项目的开发将会更为友好。
当然,除了pydantic库之外,像是valideer库、marshmallow库、trafaret库以及cerberus库等都可以完成相似的功能,但是相较之下,pydantic库的执行效率会更加优秀一些。
因此,这里,我们仅针对pydantic库来介绍一下如何规范定义标准schema并使用。
pydantic库的数据定义方式是通过BaseModel 类来进行定义的,所有基于pydantic的数据类型本质上都是一个BaseModel类,它最基本的使用方式如下:
from pydantic import BaseModel class Person(BaseModel): name: str调用时,我们只需要对其进行实例化即可,实例化方法有以下几种:
直接传值 p = Person(name="Tom") print(p.json()) #{"name":"Tom"} 通过字典传入 p = {"name": "Tom"} p = Person(**p) print(p.json()) # {"name":"Tom"} 通过其他实例化对象传入 p2 = Person.copy(p) print(p2.json()) # {"name":"Tom"}当传入值错误时,pydantic 就会抛出报错,如下:
Person(person="Tom") ValidationError: 1 validation errors for Person name field required (type=value_error.missing)另一方面,如果传入值多于定义值时,BaseModel也会自动对其进行过滤。如:
p = Person(name="Tom", gender="man", age=24) print(p.json()) # {"name":"Tom"}可以看到,额外的参数gender与age 被自动过滤了。
通过这种方式,数据的传递将会更为安全,但是同样的,这也要求我们在前期的schema定义中必须要尽可能地定义完全。
此外,pydantic在数据传输时会直接进行数据类型转换,因此,如果数据传输格式错误,但是可以通过转换变为正确的数据类型,数据传输也可以成功,例如:
p=Person(name=123) print(p.json()) # {"name": "123"}下面我们来看下pydantic中的一些常用的基本类型
from pydantic imoort BaseModel from typing import Dict, List, Sequence, Set, Tuple class Demo(BaseModel): a: int b: float c: str d: bool e: List[int] f: Dict[str, int] g: Set[int] h: Tuple[str, int] ...这里我们给出一些较为复杂的数据类型的实现
enum兴数据类型 我们可以通过enum库进行实现,如下:
from enum import Enum class Gender(str, Enum): man = "man" women = "woman"如果一个数据类型不是必须的,可以允许用户在使用中不进行传入,则我们可以使用typing 库中的Optional 方法进行实现
from typing import Optional from pydantic import BaseModel class Person(BaseModel): name: str age: Optional[int] # 可选参数需要注意的是,设置为可选后,数据仍然会有age字段,但是其默认值为None, 不传入age字段时,Person 仍然可以取到age, 只是其值为None.
p = Person(name="Tom") print(p.json()) # {"name":"Tom", "age": None}上述可选数据类型方法事实上是一种较为特殊的给予数据默认值的方法,只是给其的值为None 这里 我们给出一些更加一般性的给出数据默认值得方法。
from pydantic import BaseModel class Person(BaseModel): name: str gender: str = "man" p = Person(name="Tom") print(p.json()) ## {"name": "Tom", "gender": "man"}如果一个数据可以运行多种数据类型,我们可以通过typin库中的Union方法进行实现。
from typing import Union from pydantic import BaseModel class Time(BaseModel): time: Union[int, str] t = Time(time=12345) print(t.json())# {"time": 12345} t = Time(time = "2017-09-09") print(t.json())# {"time": "2020-7-29"}假设我们之前已经定义了一个schema,将其中某一个参量命名为了A,但是在后续的定义中,我们希望这个量被命名为B,要如何完成这两个不同名称参量的相互传递呢? 我们可以通过Field方法来实现这一操作。 给出例子如下:
from pydantic import BaseModel, Field class Password(BaseModel): password: str = Feild(alias="key")则在传入时,我们需要用key关键词来传入password变量。 实例化代码如下:
p=Password(key="123456") print(p.json()) # {"password": "123456"}需要注意的是,我们除了可以一步步地实例化以外,如果我们已经有了一个完整的company的内容字典,我们也可以一步到位地进行实例化
sales_department = { "name": "sales", "lead": {"name": "Sarah", "gender":"women"}, "cast": [ {"name": "Sarah", "gender":"women"}, {"name": "Bob", "gender":"man"}, {"name": "Mary", "gender":"women"} ] } research_department = { "name": "research", "lead": {"name": "Allen", "gender": "man"}, "cast": [ {"name": "Jane", "gender": "women"}, {"name": "Tim", "gender": "man"} ] } company = { "name": "Fantasy", "owner": {"name": "Victor", "gender": "man"}, "regtime": "2020-7-23", "department_list": [ sales_department, research_department ] } company = Company(**company)pydantic本身提供了上述基本类型的数据检查方法, 但是除此以外,我们也可以使用validator 和config 方法来实现更为复杂的数据类型定义及检查。
使用validator 方法,我们可以对数据进行更为复杂的数据检查
import re from pydantic import BaseModel, validator class Password(BaseModel): password: str @validator("password") def password_rule(cls, password): def is_valid(password): if len(password) < 6 or len(password) > 30: return False if not re.search("[a-z]", password): return False if not re.search("[A-Z]", password): return False if not re.search("\d", password): return False return True if not is_valid(password): raise ValeError("password is invalid")通过这种方法可以额外对密码类型进行格式要求,其字符数以及内部字符进行要求。 validator 装饰器通过装饰类中的方法来验证字段,被装饰的方法必须是 类方法。 validator 装饰器有几个可选的参数:
fields 要调用装饰器进行验证的字段,一个验证器可以应用于多个字段,可以通过显式指定字段名称的方式逐一指定字段,也可以通过*的方式指定所有字段,这意味着将为所有字段调用验证器pre 是否应该在标准验证器之前调用此验证器,如果为True 则之前调用 False 之后调用。whole:对于复杂的对象。例如 set 或者 list,是验证对象中的每个元素或者验证整个对象。如果为 True,则验证对象本身,如果为 False,则验证对象中的每个元素。always:是否在值缺失的情况下仍然调这个方法和其他验证器。check_fields:是否检查模型上是否存在字段。 from pydantic import BaseModel, ValidationError, validator class UserModel(BaseModel): name:str password1: str password2: str @validator("name") def name_must_contain_space(cls, v): if " " not in v: raise ValueError("must contain a space") return v.title() @validator("password2") def passwords_math(cls, v, values, **kwargs): if "password1" in values and v!= values['password1']: raise ValueError('password do not math') return v >>> UserModel(name='samuel colvin', password1='zxcvbn', password2='zxcvbn') <UserModel name='Samuel Colvin' password1='zxcvbn' password2='zxcvbn'> >>> try: > UserModel(name='semuel', password1='xabc', password2='xabc2') > except ValidationError as e: > print(e) 2 validation errors name must contain a space (type=value_error) password2 passwords do not match (type=value_error)需要注意的几件事:
validator 装饰的是类方法,它接收的第一个值是UserModel 而不是UserModel的实例。 -它们的签名可以是 (cls, value) 或 (cls, value, values, config, field)。从 v0.20开始,任何 (values, config, field) 的子集都是允许的。例如 (cls, value, field),但是,由于检查 validator 的方式,可变的关键字参数(**kwargs)必须叫做 kwargs。 validator 应该返回新的值或者引发 ValueError 或 TypeError 异常。 当验证器依赖于其他值时,应该知道: 验证是按照字段的定义顺序来完成的,例如。这里 password2 可以访问 password1 (和 name),但是password1 不能访问 password2。应该注意以下关于字段顺序和必填字段的警告。 如果在其另一个字段上验证失败(或者那个字段缺失),则其不会被包含在 values 中,因此,上面的例子中包含 if ‘password1’ in values and … 语句。参考:pydantic