Skip to main content

pydantic

简介

官方文档

pydantic 库是 python 中用于数据接口定义检查与设置管理的库。

pydantic 在运行时强制执行类型提示,并在数据无效时提供友好的错误。

它具有如下优点:

  1. IDE/linter 完美搭配,不需要学习新的模式,只是使用类型注解定义类的实例
  2. 多用途,BaseSettings 既可以验证请求数据,也可以从环境变量中读取系统设置
  3. 快速
  4. 可以验证复杂结构
  5. 可扩展,可以使用validator装饰器装饰的模型上的方法来扩展验证
  6. 数据类集成,除了BaseModel,pydantic还提供了一个dataclass装饰器,它创建带有输入数据解析和验证的普通 Python 数据类。

基础使用

安装

pip install pydantic

#dotenv文件获取配置,需要安装 [python-dotenv](https://link.zhihu.com/?target=https%3A//pypi.org/project/python-dotenv)
pip install pydantic[dotenv]
  • 验证是否已编译
import pydantic
print('compiled:', pydantic.compiled)

创建一个User模型

from pydantic import BaseModel, Field

class User(BaseModel):
id: Union[UUID, int, str]
name: str

# 创建
user = User(id=1, name='test')

print(user.dict())
print(user)

内置模块

from pydantic import 
BaseModel, # 最常用的复合类型定义父类
Field, # 对复合类型进行字段验证和限制
title # 标题
description # 描述
conint, # 必须大于某个数 conint=42
validator({字段}), # 验证器装饰器,对指定字段进行验证,与
ValidationError, # 内置的一个通用验证器错误类型

常用API

.dict()

将一个pydantic实例转换成字典

User.dict(
exclude_unset:bool, # 是否忽略一些默认值为None的字段
)

.dict() -> dict

返回当前模型的dict格式数据

Model.dict(
include:set[str], # 仅提取某些key作为dict输出
exclude:set[str], # 指定某些key不作为dict输出
) -> dict

.json()->str

字符串形式的JSON

.copy()

模型的副本(默认为浅表副本)

Model.copy(
deep:bool=False, #是否进行深拷贝
)

.parse_obj()

接受dict作为参数,创建数据模型实例

new_user = User.parse_obj({name="ccvb"})
# 或者常用下面的形式
new_user = User(**{name="ccvb"})

其他常用APi

  • parse_raw() 接受strbytes并将其解析为json,然后将结果传递给parse_obj,来创建数据模型实例
  • parse_file() 文件路径,读取文件并将内容传递给parse_raw。如果content_type省略,则从文件的扩展名推断
  • from_orm() 从ORM 对象创建模型
  • schema() 返回模式的字典
  • schema_json() 返回该字典的 JSON 字符串表示
  • construct() 允许在没有验证的情况下创建模型
  • __fields_set__ 初始化模型实例时设置的字段名称集
  • fields 模型字段的字典
  • __config__ 模型的配置类

实例常用功能

  • 直接打印json - schema_json

    print(Item.schema_json(indent=2))

validator

ValidationError

from pydantic import ValidationError
try:
Model(infinite=infinite_strs())
except ValidationError as e:
print(e)
"""
1 validation error for Model
infinite -> first_value
value is not a valid integer (type=type_error.integer)
"""

内置类型

from pydantic import 
DirectoryPath
UUID1~5
HttpUrl, # url类型
AnyUrl, #
datetime.date
datetime.datetime
类型说明
UUID一种标准的 "通用唯一标识符" ,在许多数据库和系统中用作ID。
在请求和响应中将以 str 表示。
datetime.datetime一个 Python datetime.datetime.
在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 2008-09-15T15:53:00+05:00.
datetime.datePython datetime.date.
在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 2008-09-15.
datetime.time一个 Python datetime.time
在请求和响应中将表示为 ISO 8601 格式的 str ,比如: 14:23:55.003.
datetime.timedelta一个 Python datetime.timedelta.
在请求和响应中将表示为 float 代表总秒数。
Pydantic 也允许将其表示为 "ISO 8601 时间差异编码", 查看文档了解更多信息。
frozenset在请求和响应中,作为 set 对待:
在请求中,列表将被读取,消除重复,并将其转换为一个 set。
在响应中 set 将被转换为 list 。
产生的模式将指定那些 set 的值是唯一的 (使用 JSON 模式的 uniqueItems)。
bytes标准的 Python bytes。
在请求和相应中被当作 str 处理。
生成的模式将指定这个 str 是 binary "格式"。
Decimal标准的 Python Decimal
在请求和相应中被当做 float 一样处理。
您可以在这里检查所有有效的pydantic数据类型: Pydantic data types.

注意事项

使用多种类型

在定义Union注释时,首先包含最具体的类型,然后是不太具体的类型。

  • 正面
class User(BaseModel):
id: Union[UUID, int, str]
name: str
  • 错误
class User(BaseModel):
id: Union[int, str, UUID]
name: str

模型配置

官方详细配置文档

完整的创建一个数据类型

from pydantic import BaseModel, Field

class Item(BaseModel):
name: str
description: str = Field(None,
title="The description of the item",
max_length=10)
price: float = Field(...,
gt=0,
description="The price must be greater than zero")
tax: float = None

a = Item(name="yo yo",
price=22.0,
tax=0.9)
# 导出字典
print(a.dict()) # {'name': 'yo yo', 'description': None, 'price': 22.0, 'tax': 0.9}

# 打印json
print(Item.schema_json(indent=2))

常用配置

官方文档

每个模型可以通过在内部定义一个 Config类进行配置

from pydantic import BaseModel, ValidationError

class Model(BaseModel):
title:str = "123"

class Config:
title = "这是一个模型的内部配置,每个模型都可以单独配置,下面是常用到的配置字段"

# 错误处理
try:
Model(x='x' * 20)
except ValidationError as e:
print('模型字段验证相关的错误通过 ValidationError 类来捕获和打印即可')
print(e)
except Exxx as e:
print(e)
title:str                     # 生成的 JSON 时的 Schema 的标题
allow_mutation:bool = False # 设置该模型的属性是否允许外部修改,即是否__setattr__允许
# (默认True:)
orm_mode # 是否允许使用ORM 模式
getter_dict # 在分解任意类以进行验证时使用的自定义类(应继承自GetterDict)
# 需配合 orm_mode 一起使用
underscore_attrs_are_private:bool
validate_assignmen:bool
max_anystr_length:int # str & byte 类型的最大长度(默认值None:)
min_anystr_length:int # str & byte 类型的最小长度(默认值0:)
error_msg_templates:dict
anystr_strip_whitespace:bool # 是否去除 str 和字节类型的前导和尾随空格(默认值False:)
validate_all:bool # 是否验证字段默认值(默认值False:)

extra:str # 在模型初始化期间是否忽略、允许或禁止额外的属性。
# 接受 、 或 的字符串值 'ignore','allow'或枚举'forbid'的值
# (默认值: )。
# 如果包含额外的属性将导致验证失败,将默默地忽略任何额外的属性,
# 并将属性分配给模型。ExtraExtra.ignore'forbid''ignore''allow'

arbitrary_types_allowed:bool # 是否验证自定义类型中的基础类型(带属性的类型是否会被验证)
copy_on_model_validation:bool # 通过copy复制模型时,是否保留原有的验证器

alias_generator:classmethod # 字段过滤器,可以将指定格式的字段转换成需要的
# xxx_xxxx => xxxxXxxx # 小驼峰
# xxx_xxxx => XxxxXxxx # 大驼峰
# 优先等级最高,但不会作用到父级模型上

定义方式

  • 全局定义
from pydantic import BaseModel as PydanticBaseModel


class BaseModel(PydanticBaseModel):
class Config:
arbitrary_types_allowed = True


class MyClass:
"""A random class"""


class Model(BaseModel):
x: MyClass

  • 基础使用
class Module(BaseModel):
class Config:
.../
  • 配合dataclass()
from pydantic.dataclasses import dataclass

class Config:
.../

@dataclass(config=Config)
class Module:
id: int
name: str = 'John Doe'
signup_ts: datetime = None

模型定义

基础模型

pydantic中定义对象都是通过模型的,你可以认为模型就是类型语言中的类型。

from pydantic import BaseModel

class User(BaseModel):
id: int
name = 'Jane Doe'

上面的例子,定义了一个User模型,继承自BaseModel,有2个字段,id是一个整数并且是必需的,name是一个带有默认值的字符串并且不是必需的

实例化

user = User(id='123')

实例化将执行所有解析和验证,如果有错误则会触发 ValidationError 报错。

模型嵌套

可以使用模型本身作为注释中的类型来定义更复杂的数据结构。

from typing import List
from pydantic import BaseModel

class Foo(BaseModel):
count: int
size: float = None

class Bar(BaseModel):
apple = 'x'
banana = 'y'

class Spam(BaseModel):
foo: Foo
bars: List[Bar]

通用模型(泛型)

使用 typing.TypeVar 的实例作为参数,传递给 typing.Generic,然后在继承了pydantic.generics.GenericModel 的模型中使用:

from typing import Generic, TypeVar, Optional, List

from pydantic import BaseModel, validator, ValidationError
from pydantic.generics import GenericModel

DataT = TypeVar('DataT')

class Error(BaseModel):
code: int
message: str

class DataModel(BaseModel):
numbers: List[int]
people: List[str]

class Response(GenericModel, Generic[DataT]):
data: Optional[DataT]
error: Optional[Error]

@validator('error', always=True)
def check_consistency(cls, v, values):
if v is not None and values['data'] is not None:
raise ValueError('must not provide both data and error')
if v is None and values.get('data') is None:
raise ValueError('must provide data or error')
return v


data = DataModel(numbers=[1, 2, 3], people=[])
error = Error(code=404, message='Not found')

print(Response[int](data=1))
#> data=1 error=None
print(Response[str](data='value'))
#> data='value' error=None
print(Response[str](data='value').dict())
#> {'data': 'value', 'error': None}
print(Response[DataModel](data=data).dict())
"""
{
'data': {'numbers': [1, 2, 3], 'people': []},
'error': None,
}
"""
print(Response[DataModel](error=error).dict())
"""
{
'data': None,
'error': {'code': 404, 'message': 'Not found'},
}
"""
try:
Response[int](data='value')
except ValidationError as e:
print(e)
"""
2 validation errors for Response[int]
data
value is not a valid integer (type=type_error.integer)
error
must provide data or error (type=value_error)
"""

ORM 模型

  • 官方示例
from typing import List
from sqlalchemy import Column, Integer, String
from sqlalchemy.dialects.postgresql import ARRAY
from sqlalchemy.ext.declarative import declarative_base
from pydantic import BaseModel, constr

Base = declarative_base()


class CompanyOrm(Base):
__tablename__ = 'companies'
id = Column(Integer, primary_key=True, nullable=False)
public_key = Column(String(20), index=True, nullable=False, unique=True)
name = Column(String(63), unique=True)
domains = Column(ARRAY(String(255)))


class CompanyModel(BaseModel):
id: int
public_key: constr(max_length=20)
name: constr(max_length=63)
domains: List[constr(max_length=255)]

class Config:
orm_mode = True


co_orm = CompanyOrm(
id=123,
public_key='foobar',
name='Testing',
domains=['example.com', 'foobar.com'],
)
print(co_orm)
#> <models_orm_mode.CompanyOrm object at 0x103349ae0>
co_model = CompanyModel.from_orm(co_orm)
print(co_model)
#> id=123 public_key='foobar' name='Testing' domains=['example.com',
#> 'foobar.com']

动态模型 create_model

在某些情况下,直到运行时才知道模型的结构。为此 pydantic 提供了create_model允许动态创建模型的方法。

from pydantic import BaseModel, create_model

DynamicFoobarModel = create_model(
'DynamicFoobarModel', foo=(str, ...), bar=123
)

与抽象基类一起使用

  • 官方示例
import abc
from pydantic import BaseModel


class FooBarModel(BaseModel, abc.ABC):
a: str
b: int

@abc.abstractmethod
def my_abstract_method(self):
pass

枚举类型

枚举的类型主要分两种:

  • Literal类型: 通过from typing import Literal引入
  • eunm.Eunm: 通过from eunm import Eunm引入

Literal

引用官方的示例

from typing import Literal
from pydantic import BaseModel


class Dessert(BaseModel):
kind: str


class Pie(Dessert):
kind: Literal['pie']
flavor: str | None


class ApplePie(Pie):
flavor: Literal['apple']


class PumpkinPie(Pie):
flavor: Literal['pumpkin']


class Meal(BaseModel):
dessert: ApplePie | PumpkinPie | Pie | Dessert


print(type(Meal(dessert={'kind': 'pie', 'flavor': 'apple'}).dessert).__name__)
#> ApplePie
print(type(Meal(dessert={'kind': 'pie', 'flavor': 'pumpkin'}).dessert).__name__)
#> PumpkinPie
print(type(Meal(dessert={'kind': 'pie'}).dessert).__name__)
#> Pie
print(type(Meal(dessert={'kind': 'cake'}).dessert).__name__)
#> Dessert

  • 示例2
from pydantic import BaseModel
from typing import Literal


class Info(BaseModel):
change_type: Literal["smart_text", "smart_img", "text", "img"]

c = Info(change_type="img")
print(c.dict())
#> {'change_type': 'img'}

c.change_type = "smart_img"
print(c.dict())
#> {'change_type': 'smart_img'}

类型验证

使用validator装饰器可以实现自定义验证和对象之间的复杂关系。

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, v):
if ' ' not in v:
raise ValueError('must contain a space')
return v.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)
"""

关于验证器的一些注意事项:

  • 验证器是“类方法”,因此它们接收的第一个参数值是UserModel类,而不是UserModel
  • 第二个参数始终是要验证的字段值,可以随意命名
  • 单个验证器可以通过传递多个字段名称来应用于多个字段,也可以通过传递特殊值在所有字段上调用单个验证器'*'
  • 关键字参数pre将导致在其他验证之前调用验证器
  • 通过each_item=True将导致验证器被施加到单独的值(例如ListDictSet等),而不是整个对象

未完: https://zhuanlan.zhihu.com/p/387492501

函数入参校验

@validate_arguments(config=dict(arbitrary_types_allowed=True))
def some_function(params: pd.DataFrame,
var_name: str
) -> dict:
# do something
return my_dict

Field 相关参数

Field可用于提供有关字段和验证的额外信息。

参数名称描述
default(位置参数)字段的默认值。由于Field替换了字段的默认值,因此第一个参数可用于设置默认值。使用省略号 ( …) 表示该字段为必填项。
default_factory当该字段需要默认值时将被调用。除其他目的外,这可用于设置动态默认值。禁止同时设置default和default_factory。
alias字段的别名
description文档字符串
exclude在转储(.dict和.json)实例时排除此字段
include在转储(.dict和.json)实例时(仅)包含此字段
const此参数必须与字段的默认值相同(如果存在)
gt对于数值 ( int, float, ),向 JSON SchemaDecimal添加“大于”的验证和注释exclusiveMinimum
ge对于数值,这将添加“大于或等于”的验证和minimumJSON 模式的注释
lt对于数值,这会为exclusiveMaximumJSON Schema添加“小于”的验证和注释
le对于数值,这将添加“小于或等于”的验证和maximumJSON 模式的注释
multiple_of对于数值,这会multipleOf向 JSON Schema添加“多个”的验证和注释
max_digits对于Decimal值,这将添加验证以在小数点内具有最大位数。它不包括小数点前的零或尾随的小数零。
decimal_places对于Decimal值,这增加了一个验证,最多允许小数位数。它不包括尾随十进制零。
min_itemsminItems对于列表值,这会向 JSON Schema添加相应的验证和注释
max_itemsmaxItems对于列表值,这会向 JSON Schema添加相应的验证和注释
unique_itemsuniqueItems对于列表值,这会向 JSON Schema添加相应的验证和注释
min_lengthminLength对于字符串值,这会向 JSON Schema添加相应的验证和注释
max_lengthmaxLength对于字符串值,这会向 JSON Schema添加相应的验证和注释
allow_mutation一个布尔值,默认为True. TypeError当为 False 时,如果在实例上分配了字段,则该字段引发 a 。模型配置必须设置validate_assignment为True执行此检查。
regex对于字符串值,这会添加从传递的字符串生成的正则表达式验证和patternJSON 模式的注释
repr一个布尔值,默认为True. 当为 False 时,该字段应从对象表示中隐藏。
**任何其他关键字参数(例如examples)将逐字添加到字段的架构中