pydantic
简介
pydantic 库是 python 中用于数据接口定义检查与设置管理的库。
pydantic 在运行时强制执行类型提示,并在数据无效时提供友好的错误。
它具有如下优点:
- 与
IDE/linter
完美搭配,不需要学习新的模式,只是使用类型注解定义类的实例 - 多用途,BaseSettings 既可以验证请求数据,也可以从环境变量中读取系统设置
- 快速
- 可以验证复杂结构
- 可扩展,可以使用
validator
装饰器装饰的模型上的方法来扩展验证 - 数据类集成,除了
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()
接受str
或bytes
并将其解析为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.date | Python 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
将导致验证器被施加到单独的值(例如List
,Dict
,Set
等),而不是整个对象
未完: 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)将逐字添加到字段的架构中 |