/发布

Pydantic v2.8

Sydney Runkle avatar
Sydney Runkle
10 分钟

Pydantic v2.8 现已发布!您现在可以通过 PyPi 或您喜欢的包管理器安装它

pip install --upgrade pydantic

此版本包含了超过 50 位贡献者的工作成果!在这篇文章中,我们将介绍此版本的亮点。您可以在 github 上查看完整的更新日志。

此版本尤其侧重于类型相关的错误修复和改进,以及一些可选的性能增强功能。

Pydantic v2.8 引入了一项名为快速失败验证的新功能。此功能目前仅适用于有限数量的序列类型,包括 listtuplesetfrozenset。当您使用 FailFast 验证时,Pydantic 将在遇到错误时立即停止验证。

当您更关心数据的有效性而不是验证错误的彻底性时,此功能非常有用。对于许多人来说,这种错误特异性的权衡非常值得性能的提升。

您可以将 FailFast() 用作类型注解,或者在 Field 构造函数中指定 fail_fast 参数。

例如

from typing import List

from typing_extensions import Annotated

from pydantic import BaseModel, FailFast, Field, ValidationError


class Model(BaseModel):
    x: Annotated[List[int], FailFast()]
    y: List[int] = Field(..., fail_fast=True)


# This will raise a single error for the first invalid value in each list
# At which point, validation for said field will stop
try:
    obj = Model(x=[1, 2, 'a', 4, 5, 'b', 7, 8, 9, 'c'], y=[1, 2, 'a', 4, 5, 'b', 7, 8, 9, 'c'])
except ValidationError as e:
    print(e)
    """
    2 validation errors for Model
    x.2
    Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
        For further information visit https://errors.pydantic.dev/2.8/v/int_parsing
    y.2
    Input should be a valid integer, unable to parse string as an integer [type=int_parsing, input_value='a', input_type=str]
        For further information visit https://errors.pydantic.dev/2.8/v/int_parsing
    """

我们计划在未来将此功能扩展到其他类型!

您可以在 API 参考 中阅读有关 FailFast 的更多信息

在 v2.7 中,我们引入了弃用模型和字段的功能。在 v2.8 中,我们扩展了此功能,在 JSON Schema 中包含了弃用信息。

from typing_extensions import deprecated

from pydantic import BaseModel, Field

@deprecated('DeprecatedModel is... sadly deprecated')
class DeprecatedModel(BaseModel):
    deprecated_field: str = Field(..., deprecated=True)

json_schema = DeprecatedModel.schema()
assert json_schema['deprecated'] is True
assert json_schema['properties']['deprecated_field']['deprecated'] is True

这项新功能允许您使用可调用对象为您的模型和字段生成标题。当您想要根据模型或字段的属性动态生成标题时,这非常有用。

您可以在模型和字段之间共享可调用标题生成器,这有助于保持您的代码 DRY(不要重复自己)。

import json

from pydantic import BaseModel, ConfigDict, Field


class MyModel(BaseModel):
    foo: str = Field(..., field_title_generator=lambda name, field_info: f'title-{name}-from-field')
    bar: str

    model_config = ConfigDict(
        field_title_generator=lambda name, field_info: f'title-{name}-from-config',
        model_title_generator=lambda cls: f'title-{cls.__name__}-from-config',
    )

print(json.dumps(MyModel.model_json_schema(), indent=2))
"""
{
  "properties": {
    "foo": {
      "title": "title-foo-from-field",
      "type": "string"
    },
    "bar": {
      "title": "title-bar-from-config",
      "type": "string"
    },
  },
  "required": [
    "foo",
    "bar",
  ],
  "title": "title-MyModel-from-config",
  "type": "object"
}
"""

想了解更多?请查看 JSON Schema 自定义文档

在 v2.7 中,我们添加了对为 BaseModel 的序列化器传递上下文的支持。在 v2.8 中,我们将该支持扩展到了 TypeAdapter 的序列化方法。

这是一个简单的示例,我们在 context 中使用提供的 unit 来转换 distance 字段

from typing_extensions import Annotated

from pydantic import SerializationInfo, TypeAdapter, PlainSerializer


def serialize_distance(v: float, info: SerializationInfo) -> float:
    """We assume a distance is provided in meters, but we can convert it to other units if a context is provided."""
    context = info.context
    if context and 'unit' in context:
        if context['unit'] == 'km':
            v /= 1000  # convert to kilometers
        elif context['unit'] == 'cm':
            v *= 100  # convert to centimeters
    return v


distance_adapter = TypeAdapter(Annotated[float, PlainSerializer(serialize_distance)])

print(distance_adapter.dump_python(500))  # no context, dumps in meters
# > 500.0

print(distance_adapter.dump_python(500, context={'unit': 'km'}))  # with context, dumps in kilometers
# > 0.5

print(distance_adapter.dump_python(500, context={'unit': 'cm'}))  # with context, dumps in centimeters
# > 50000

在 v2.8.0 中,我们引入了一种用于引入实验性功能和设置的新模式。我们在 版本策略 中添加了一个部分,解释了我们未来将如何处理实验性功能。

您可以在 实验性功能 部分找到我们新的实验性功能的文档。

实验性功能将是

  • 位于 experimental 模块中,因此您可以通过 from pydantic.experimental import ... 导入它们
  • experimental 为前缀,因此您可以像 some_func(experimental_param=...)some_model.experimental_method(...) 这样使用它们

当您从 experimental 模块导入实验性功能时,您将看到 PydanticExperimentalWarning。您可以通过以下方式过滤此警告

import warnings

from pydantic import PydanticExperimentalWarning

warnings.filterwarnings('ignore', category=PydanticExperimentalWarning)

在我们的版本策略中,我们还提到了实验性功能的 生命周期。实验性功能很可能会在未来的版本中经历不向后兼容的更改或被完全删除,因此在选择使用它们时请注意它们的易变性。

Pipeline API — 实验性

Pydantic v2.8.0 引入了一个实验性的 “pipeline” API,它允许以比现有 API 更类型安全的方式组合解析(验证)、约束和转换。

通常,pipeline API 用于定义应用于传入数据的验证步骤序列。以下示例说明了如何使用 pipeline API 首先将 int 验证为字符串,去除多余的空格,然后将其解析为 int,最后确保它大于或等于 0。

import warnings

from typing_extensions import Annotated

from pydantic import BaseModel, PydanticExperimentalWarning, ValidationError

warnings.filterwarnings('ignore', category=PydanticExperimentalWarning)

from pydantic.experimental.pipeline import validate_as


class Model(BaseModel):
    data: Annotated[str, validate_as(str).str_strip().validate_as(...).ge(0)]


print(repr(Model(data='  123  ')))
#> Model(data=123)

try:
    Model(data='  -123  ')
except ValidationError as e:
    print(e)
    """
    1 validation error for Model
    data
    Input should be greater than or equal to 0 [type=greater_than_equal, input_value='  -123  ', input_type=str]
        For further information visit https://errors.pydantic.dev/2.8/v/greater_than_equal
    """

请注意,validate_as(...) 等同于 validate_as(<type_in_annotation>)。因此,对于上面的示例,validate_as(...) 等同于 validate_as(int)

如果您想了解更多信息,我们在 pipeline 文档 中添加了一些其他示例。

defer_build 支持 `TypeAdapter — 实验性

Pydantic BaseModel 目前在其配置中支持 defer_build 设置,允许延迟 schema 构建(直到第一次验证调用)。这可以帮助减少可能具有大量复杂模型(这些模型承担着沉重的 schema 构建成本)的应用程序的启动时间。

在 v2.8 中,我们为 TypeAdapter 的配置添加了实验性 defer_build 支持。

以下是如何使用此新实验性功能的示例

from pydantic import ConfigDict, TypeAdapter

from typing import TypeAlias


# in practice, this would be some complex type for which schema building
# takes a while, hence the value of deferring the build
SuperDuperComplexType: TypeAlias = int


ta = TypeAdapter(
    SuperDuperComplexType,
    config=ConfigDict(
        defer_build=True,
        experimental_defer_build_mode=('model', 'type_adapter'),
    ),
)

assert ta._core_schema is None
print(ta.validate_python(0))

# after a call is made that requires the core schema, it's built and cached
assert ta.core_schema == {'type': 'int'}

我们一直在与 codeflash 团队合作,对 Pydantic 的源代码进行 LLM 驱动的优化。到目前为止,我们在内部逻辑中进行了一些小的优化,我们期待在未来合作进行更重大的改进。

我们还希望将 codeflash 集成到我们的 CI/CD pipeline 中,以确保我们始终如一地检查优化机会。

Pydantic V2 现在支持 Python 3.13!

我们的一些面向测试的依赖项尚不兼容 Python 3.13,但我们计划尽快升级它们。这意味着并非所有测试都可以在 Python 3.13 上运行(只有少数几个尚不兼容),但这应该只会影响贡献者。

对于用户而言,Pydantic 应该可以在 Python 3.13 上正常工作。如果您遇到任何问题,请 告诉我们

在针对类型联合验证数据时,Pydantic 提供了 smart 模式和 left_to_right 模式。

我们对 smart 模式进行了一些改进,以改善针对包含许多相同字段的类型联合进行验证时的行为。以下示例展示了旧行为与新行为的示例

from pydantic import BaseModel, TypeAdapter


class ModelA(BaseModel):
    a: int


class ModelB(ModelA):
    b: str

ta = TypeAdapter(ModelA | ModelB)
print(repr(ta.validate_python({'a': 1, 'b': 'foo'})))
#> old behavior: ModelA(a=1)
#> new behavior: ModelB(a=1, b='foo')

在许多其他更复杂的情况下,新行为更加直观和正确。总体思路是,除了匹配的精确度(在 strict、lax 等方面)之外,我们现在还将有效字段的数量纳入评分算法。

我们记录了匹配评分 算法,以提供透明度和可预测性。但是,我们保留在未来修改智能联合算法以进一步改进其行为的权利。一般来说,我们预计此类更改只会影响边缘情况,并且我们只会在它们几乎普遍改善此类边缘情况的处理时才进行此类更改。

更好地理解此算法还可以帮助您选择哪种联合模式适合您的用例。

如果您正在寻找比智能模式提供更高性能的联合验证,我们建议您使用 标记联合

Python 的 re 模块支持可以传递给 regex 模式以更改其行为的标志。例如,re.IGNORECASE 标志使模式不区分大小写。在以前版本的 Pydantic 中,即使在使用 python-re regex 引擎时,这些标志也会被忽略。

现在,我们通过以下方式改进了 Pydantic 的约束字符串验证:

  1. 不重新编译 Python regex 模式(这更高效)
  2. 尊重传递给已编译模式的标志

!!! note 如果您使用已编译的 regex 模式,则无论此设置如何,都将使用 python-re 引擎。这是为了使诸如 re.IGNORECASE 之类的标志得到尊重。

这是一个说明这些标志作用的示例

import re

from typing_extensions import Annotated

from pydantic import BaseModel, ConfigDict, StringConstraints


class Model(BaseModel):
    a: Annotated[str, StringConstraints(pattern=re.compile(r'[A-Z]+', re.IGNORECASE))]

    model_config = ConfigDict(regex_engine='python-re')


# allows lowercase letters, even though the pattern is uppercase only due to the re.IGNORECASE flag
assert Model(a='abc').a == 'abc'

您可以在我们的 模型配置文档 中了解有关 regex_engine 设置的更多信息

凭借这些新功能和性能改进,Pydantic v2.8.0 是迄今为止最好且功能最丰富的 Pydantic 版本。如果您有任何问题或反馈,请打开一个 Github 讨论。如果您遇到任何错误,请打开一个 Github issue

感谢我们所有的贡献者使此版本成为可能!我们特别要感谢以下个人对本次发布的重大贡献

如果您喜欢 Pydantic,您可能会非常喜欢 Pydantic Logfire,这是 Pydantic 团队构建的新型可观测性工具。您现在可以 免费试用 Logfire,在我们公开 Beta 测试期间。如果您能加入 Pydantic Logfire Slack 并告诉我们您的想法,我们将非常高兴!