架构
🚧 正在进行中
此页面正在开发中。
从 Pydantic V2 开始,部分代码库是用 Rust 编写的,它位于名为 pydantic-core
的单独包中。这样做部分是为了提高验证和序列化性能(以牺牲内部逻辑的有限自定义性和可扩展性为代价)。
这份架构文档将首先介绍 pydantic
和 pydantic-core
这两个包如何交互,然后将介绍各种模式(模型定义、验证、序列化、JSON Schema)的架构细节。
Pydantic 库的使用可以分为两部分
- 模型定义,在
pydantic
包中完成。 - 模型验证和序列化,在
pydantic-core
包中完成。
模型定义¶
每当定义一个 Pydantic BaseModel
时,元类都会分析模型的主体以收集一些元素
- 定义的注解以构建模型字段(在
model_fields
属性中收集)。 - 模型配置,使用
model_config
设置。 - 额外的验证器/序列化器。
- 私有属性、类变量、泛型参数化识别等。
pydantic
和 pydantic-core
之间的通信:核心模式¶
然后我们需要一种方法来将从模型定义中收集的信息传达给 pydantic-core
,以便相应地执行验证和序列化。为此,Pydantic 使用核心模式的概念:一个结构化(且可序列化)的 Python 字典(使用 TypedDict
定义表示),它描述了特定的验证和序列化逻辑。它是用于在 pydantic
和 pydantic-core
包之间进行通信的核心数据结构。每个核心模式都有一个必需的 type
键,以及取决于此 type
的额外属性。
核心模式的生成在一个地方处理,由 GenerateSchema
类处理(无论它是否用于 Pydantic 模型或其他任何东西)。
注意
无法定义自定义核心模式。核心模式需要被 pydantic-core
包理解,因此我们只支持固定数量的核心模式类型。这也是 GenerateSchema
没有真正暴露且没有正确文档的原因之一。
核心模式定义可以在 pydantic_core.core_schema
模块中找到。
在 Pydantic 模型的情况下,将构建核心模式并将其设置为 __pydantic_core_schema__
属性。
为了说明核心模式的样子,我们将以 bool
核心模式为例
class BoolSchema(TypedDict, total=False):
type: Required[Literal['bool']]
strict: bool
ref: str
metadata: Any
serialization: SerSchema
在定义具有布尔字段的 Pydantic 模型时
from pydantic import BaseModel, Field
class Model(BaseModel):
foo: bool = Field(strict=True)
foo
字段的核心模式将如下所示
{
'type': 'bool',
'strict': True,
}
如 BoolSchema
定义所示,序列化逻辑也在核心模式中定义。如果我们要为 foo
(1)定义一个自定义序列化函数,则 serialization
键将如下所示
-
例如,使用
field_serializer
装饰器class Model(BaseModel): foo: bool = Field(strict=True) @field_serializer('foo', mode='plain') def serialize_foo(self, value: bool) -> Any: ...
{
'type': 'function-plain',
'function': <function Model.serialize_foo at 0x111>,
'is_field_serializer': True,
'info_arg': False,
'return_schema': {'type': 'int'},
}
请注意,这也是一个核心模式定义,只是它在序列化期间仅与 pydantic-core
相关。
核心模式涵盖范围广泛,并在我们希望在 Python 和 Rust 之间进行通信时使用。虽然之前的示例与验证和序列化有关,但理论上它可以用于任何事情:错误管理、额外元数据等。
JSON Schema 生成¶
您可能已经注意到,之前的序列化核心模式有一个 return_schema
键。这是因为核心模式还用于生成相应的 JSON Schema。
类似于核心模式的生成方式,JSON Schema 的生成由 GenerateJsonSchema
类处理。 generate
方法是主要入口点,它将获得该模型的核心模式。
回到我们的 bool
字段示例, bool_schema
方法将获得先前生成的 布尔核心模式,并将返回以下 JSON Schema
{
{"type": "boolean"}
}
自定义核心模式和 JSON 模式¶
虽然 GenerateSchema
和 GenerateJsonSchema
类处理相应模式的创建,但 Pydantic 提供了一种方法来在某些情况下对其进行自定义,遵循包装器模式。这种自定义是通过 __get_pydantic_core_schema__
和 __get_pydantic_json_schema__
方法完成的。
为了理解这种包装器模式,我们将以与 Annotated
一起使用的元数据类为例,其中可以使用 __get_pydantic_core_schema__
方法
from typing import Any
from pydantic_core import CoreSchema
from typing_extensions import Annotated
from pydantic import GetCoreSchemaHandler, TypeAdapter
class MyStrict:
@classmethod
def __get_pydantic_core_schema__(
cls, source: Any, handler: GetCoreSchemaHandler
) -> CoreSchema:
schema = handler(source) # (1)!
schema['strict'] = True
return schema
class MyGt:
@classmethod
def __get_pydantic_core_schema__(
cls, source: Any, handler: GetCoreSchemaHandler
) -> CoreSchema:
schema = handler(source) # (2)!
schema['gt'] = 1
return schema
ta = TypeAdapter(Annotated[int, MyStrict(), MyGt()])
MyStrict
是第一个要应用的注解。此时,schema = {'type': 'int'}
。MyGt
是最后一个要应用的注解。此时,schema = {'type': 'int', 'strict': True}
。
当 GenerateSchema
类构建 Annotated[int, MyStrict(), MyGt()]
的核心模式时,它将创建一个 GetCoreSchemaHandler
实例,并将其传递给 MyGt.__get_pydantic_core_schema__
方法。(1)
- 在我们使用的
Annotated
模式中,GetCoreSchemaHandler
是以嵌套方式定义的。调用它将递归地调用其他__get_pydantic_core_schema__
方法,直到它到达int
注解,在那里返回一个简单的{'type': 'int'}
模式。
source
参数取决于核心模式生成模式。在 Annotated
的情况下,source
将是被注解的类型。当 定义自定义类型 时,source
将是定义 __get_pydantic_core_schema__
的实际类。
模型验证和序列化¶
虽然模型定义的范围在类级别(即定义模型时),但模型验证和序列化发生在实例级别。这两个概念都在 pydantic-core
中处理(与 Pydantic V1 相比,性能提高了 5 到 20 倍),方法是使用之前构建的核心模式。
pydantic-core
公开了一个 SchemaValidator
和 SchemaSerializer
类来执行这些任务。
from pydantic import BaseModel
class Model(BaseModel):
foo: int
model = Model.model_validate({'foo': 1}) # (1)!
dumped = model.model_dump() # (2)!
- 提供的数据是通过
SchemaValidator.validate_python
方法发送到pydantic-core
的。pydantic-core
将验证(遵循模型的核心模式)数据并填充模型的__dict__
属性。 model
实例是通过SchemaSerializer.to_python
方法发送到pydantic-core
的。pydantic-core
将读取实例的__dict__
属性并构建适当的结果(同样,遵循模型的核心模式)。