1. 简介
使用Python类型注解进行数据验证和设置管理。
Pydantic 在运行时强制执行类型提示,并在数据无效时提供用户友好的错误信息。
定义数据如何表示为纯粹和规范的 Python ,并使用 pydantic 对其进行验证。
1.1 示例:
1 | from datetime import datetime |
这里发生了什么:
id
是int
类型;注释声明告诉pydantic该字段是必须的。如果可能,字符串、字节或浮点数将强制转换为
int
,否则将引发异常。name
从默认值推断为其为str
类型,该字段不是必须的,因为它有默认值。signup_ts
是datetime
类型,该字段不是必须的,默认值为None
。pydantic会将表示
unix
时间戳(例如1496498400)的int
类型或表示时间和日期的字符串处理成datetime
类型。friends
使用Python的typing
系统,需要一个整数列表,就像id
字段一样,类整数的对象将会被转换为整数。
如果验证失败,pydantic会抛出一个错误,列出错误的原因:
1 | from pydantic import ValidationError |
输出:
1 | [ |
1.2 基本原理
pydantic使用了一些很酷的新语言特性,但我为什么要使用它呢?
与你的IDE/linter/brain配合得很好
不需要学习新的模式定义微语言。如果您知道如何使用 Python 的类型提示,也就知道如何使用 pydantic。
因为pydantic数据结构只是您使用类型注解定义的类的实例,所以自动完成、linting、mypy、IDE(尤其是 PyCharm)和您的直觉都应该能够正确地处理经过验证的数据。
多用途
pydantic的 BaseSettings 类允许在 “验证此请求数据” 上下文和 “加载我的系统设置” 上下文中使用。主要区别在于,系统设置可以从环境变量读取,并且通常需要更复杂的对象,如DSN和Python对象。
快速
pydantic比其他所有测试库都要快。
可以验证复杂结构
使用递归pydantic模型、typing 的标准类型 (如
List
、Tuple
和Dict
等) 和验证器,可以很清晰且容易地定义、验证和解析复杂数据模式。可拓展
pydantic允许定义自定义数据类型,或者您可以使用被 validator 装饰器装饰的模型上的方法来扩展验证。
dataclasses 集成
和 BaseModel 一样,pydantic提供了一个 dataclass 装饰器,它创建带有输入数据解析和验证的(几乎)普通的Python数据类。
2. 安装
1 | pip install pydantic |
Pydantic除了Python3.6、3.7、3.8 或 3.9(和Python3.6中的 dataclasses 包)之外,不需要其他依赖项。
Pydantic 可以可选的使用 Cython 进行编译,将会带来 30%-50%的性能提升。
PyPI可以为Linux、MacOS和64位Windows提供二进制文件。如果您是手动安装,请在安装pydantic之前安装 cython,这样编译就会自动进行。
要测试pydantic 是否已经编译,可以使用如下方法:
1 | import pydantic |
Pydantic 有三个可选依赖:
- 如果需要
email
验证,可以添加email-validator
。 - 要在 Python3.8之前的版本中使用
Literal
,需要安装typing-extensions
。 - 使用
Settings
的dotenv
文件支持需要安装python-dotenv
。
要将这些与 pydantic 一起安装,可以使用如下方式:
1 | pip install pydantic[email] |
当然,你可以使用 pip install email-validator
和/或 pip install typing_extensions
手动安装这些依赖。
如果想从存储库直接安装 pydantic,可以使用:
1 | pip install git+git://github.com/samuelcolvin/pydantic@master#egg=pydantic |
3. 用法详解
3.1 模型
在pydantic中定义对象的主要方法是通过模型(模型只是继承自 BaseModel 的类)。
您可以将模型看作严格类型语言中的类型,或者看作API中单个端点的需求。
不受信任的数据可以传递给模型,在解析和验证之后,pydantic保证结果模型实例的字段将符合模型上定义的字段类型。
pydantic主要是一个解析库,而不是验证库。验证是达到目的的一种手段:构建符合所提供的类型和约束的模型。
换句话说,pydantic保证输出模型的类型和约束,而不是输入数据。
3.1.1 基本模型的使用
1 | from pydantic import BaseModel |
在这里,User 是具有两个字段的模型,其中字段 id 是整数类型,并且是必需的;name 字段是字符串类型,但不是必需的(它有默认值)。name 的类型是从其默认值推断来的,因此,类型注解不是必需的(但是,当某些字段没有类型注解时,请注意关于字段顺序的警告)。
1 | user = User(id='123') |
这里的 user
是 User
的一个实例。对象的初始化会执行所有解析和验证,如果没有引发 ValidationError
异常,则表明结果模型实例是有效的。
1 | assert user.id == 123 |
模型的字段可以作为User
对象的普通属性访问。字符串 ‘123’ 根据字段类型被强制转换为 int
类型:
1 | assert user.name == 'Jane Doe' |
初始化 User
对象时未设置 name
的值,所以它的值是默认值:
1 | assert user.__fields_set__ == {'id'} |
User
对象初始化时提供的字段:
1 | assert user.dict() == dict(user) == {'id': 123, 'name': 'Jane Doe'} |
.dict()
或 dict(user)
都将会提供一个字段的字典,但 .dict()
可以接受许多其他参数:
1 | user.id = 321 |
这个模型是可变模型,所以字段值可以更改。
3.1.1.1 模型属性
上面的例子只展示了模型所能做的事情的冰山一角。模型具有以下方法和属性:
dict()
返回模型的字段和值的字典。参见 导出模型)。
json()
返回表示
dict()
的 JSON 字符串。参见 导出模型)。copy()
返回模型的副本(默认情况下为浅副本)。参见 导出模型)。
parse_obj()
如果一个对象不是字典,可以使用该方法将其加载到具有错误处理的模型中。参见 帮助函数。
parse_raw()
用于加载多种格式字符串的实用程序。参见 帮助函数。
parse_file()
与
parse_raw()
类似,但是作用于文件路径。参见 帮助函数。from_orm()
从任意类加载数据到模型中。参见 ORM 模式。
schema()
返回一个将模型表示为 JSON 模式的字典。参见 模式。
schema_json()
返回表示
schema()
的 JSON 字符串。参见 模式。construct()
用于创建模型而不执行验证的类方法;参见 创建未经验证的模型。
__fields_set__
当模型实例初始化时设置的字段名称集合。
__config__
模型的配置类。参见 模型配置。
3.1.2 递归模型
可以通过在注解中使用模型本身作为类型来定义更复杂的分层数据结构。
1 | from typing import List |
对于自引用模型,参见 延迟注解。
3.1.3 ORM 模式
可以从任意类实例创建Pydantic模型,以支持映射到ORM对象的模型。
要达到这个目的,需要:
模型的内部类
Config
的orm_mode
属性必须设置为True
。必须使用特殊的构造函数
from_orm
来创建模型实例。
这里的示例使用SQLAlchemy,但是相同的方法应该适用于任何ORM。
1 | from typing import List |
3.1.3.1 保留名称
您可能想使用保留的SQLAlchemy字段命名列。在这种情况下,可以使用字段别名:
1 | import typing |
上面的示例之所以能够工作,是因为对于字段填充,别名优先于字段名。访问
SQLModel
的metadata
属性将导致ValidationError
。
3.1.3.2 递归 ORM 模型
ORM实例将使用 from_orm
递归解析,也可以在顶层解析。
这里使用一个普通类来演示这个原理,但是可以使用任何ORM类。
1 | from typing import List |
Pydantic使用 GetterDict
类处理任意类,该类试图为任何类提供一个类似于字典的接口。可以通过将 GetterDict
的自定义子类设置为 Config.getter_dict
的值来覆盖默认行为(参考 模型配置)。
您还可以使用带有 pre=True
的 root_validators
定制类验证。在这种情况下,validator
函数将被传递一个可以复制和修改的GetterDict
实例。
3.1.4 错误处理
每当在正在验证的数据中发现错误时,pydantic都会引发 ValidationError
。
验证代码不应该引发
ValidationError
本身,而是引发ValueError
、TypeError
或AssertionError
(或ValueError
或TypeError
的子类),这些异常将被捕获并用于填充ValidationError
。
无论发现的错误数量如何,都只会出现一个异常,ValidationError
将包含关于所有错误及其发生方式的信息。
你可以通过几种方式访问这些错误:
e.errors()
返回输入数据中发现的错误的列表。
e.json()
返回一个表示
errors
的 JSON。str(e)
返回人类可读的错误表示。
每一个错误对象包含:
loc
错误的位置列表。列表中的第一项将是发生错误的字段,如果该字段是子模型,则将出现后续项以指示错误的嵌套位置。
type
计算机可读的错误类型的标识符。
msg
人类可读的错误解释。
ctx
包含呈现错误消息所需的值的可选对象。
下面是一个示例:
1 | from typing import List |