Pydantic v2.8 现已发布!您可以通过 PyPi 或您喜欢的包管理器立即安装它。
pip install --upgrade pydantic
此版本包含了 50 多位贡献者的工作!在这篇文章中,我们将介绍此版本的亮点。您可以在 github 上查看完整的更改日志。
此版本特别关注与类型相关的错误修复和改进,以及一些可选的性能增强功能。
新功能
快速失败验证
Pydantic v2.8 引入了一项名为快速失败验证的新功能。目前,此功能仅适用于有限数量的序列类型,包括 list
、tuple
、set
和 frozenset
。当您使用 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
的更多信息。
JSON 模式中的模型/字段弃用
在 v2.7 中,我们引入了弃用模型和字段的功能。在 v2.8 中,我们已将此功能扩展到包括 JSON 模式中的弃用信息。
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 模式自定义文档。
TypeAdapter
的序列化上下文
在 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)
在我们的版本策略中,我们还讨论了实验性功能的 生命周期。实验性功能很可能在未来的版本中经历不向后兼容的更改或完全删除,因此在选择使用它们时请注意其不稳定性。
管道 API — 实验性
Pydantic v2.8.0 引入了一个实验性的“管道”API,它允许以比现有 API 更类型安全的方式组合解析(验证)、约束和转换。
通常,管道 API 用于定义在验证期间应用于传入数据的步骤序列。以下示例说明了如何使用管道 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)
。
如果您想了解更多信息,我们已在 管道文档 中添加了一些其他示例。
defer_build
支持 `TypeAdapter — 实验性
Pydantic BaseModel
目前在其配置中支持 defer_build
设置,允许延迟架构构建(直到第一个验证调用)。这有助于减少可能拥有大量复杂模型(这些模型会产生沉重的架构构建成本)的应用程序的启动时间。
在 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
持续优化
我们一直在与 codeflash
团队合作,对 Pydantic 的源代码进行 LLM 驱动的优化。到目前为止,我们已经在内部逻辑中进行了一些小的优化,并且我们期待在未来进行更重要的改进。
我们还希望将 codeflash
集成到我们的 CI/CD 管道中,以确保我们始终检查优化机会。
值得注意的改进/修复
支持 Python 3.13
Pydantic V2 现在支持 Python 3.13!
我们的一些面向测试的依赖项尚未与 Python 3.13 兼容,但我们计划很快对其进行升级。这意味着并非所有测试都可以在 Python 3.13 上运行(只有少数几个尚未兼容),但这应该只会影响贡献者。
对于用户来说,Pydantic 应该可以在 Python 3.13 上按预期工作。如果您遇到任何问题,请 告知我们!
更智能的 smart
Union
验证
在针对类型联合验证数据时,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')
在许多其他更复杂的情况下,新行为更直观且正确。总体思路是我们现在将有效字段的数量纳入评分算法,此外还考虑了匹配的精确度(就严格、宽松等而言)。
我们已记录了匹配评分 算法 以提供透明度和可预测性。但是,我们保留将来修改智能联合算法以进一步改善其行为的权利。通常,我们预计此类更改只会影响极端情况,并且我们只会在此类更改几乎普遍改善此类极端情况的处理时进行此类更改。
更好地理解此算法还可以帮助您选择适合您的用例的联合模式。
如果您正在寻找比智能模式提供的更有效的联合验证,我们建议您使用 标记联合。
在受约束的字符串验证中尊重正则表达式标志
Python 的 re
模块支持可以传递给正则表达式模式以更改其行为的标志。例如,re.IGNORECASE
标志使模式不区分大小写。在以前的 Pydantic 版本中,即使使用 python-re
正则表达式引擎,这些标志也会被忽略。
现在,我们通过以下方式改进了 Pydantic 的受约束的字符串验证:
- 不重新编译 Python 正则表达式模式(这更高效)
- 尊重传递给已编译模式的标志
!!! note 如果您使用已编译的正则表达式模式,则无论此设置如何,都将使用 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 Logfire
如果您喜欢 Pydantic,您可能会**非常**喜欢 Pydantic Logfire,这是一个由 Pydantic 团队构建的新型可观测性工具。您现在可以在我们的公开测试期内免费试用 Logfire。我们非常乐意您加入Pydantic Logfire Slack 并告诉我们您的想法!