跳到内容

迁移指南

Pydantic V2 引入了许多 API 更改,包括一些破坏性更改。

此页面提供指南,重点介绍最重要的更改,以帮助您将代码从 Pydantic V1 迁移到 Pydantic V2。

安装 Pydantic V2

Pydantic V2 现在是 Pydantic 的当前生产版本。您可以从 PyPI 安装 Pydantic V2

pip install -U pydantic

如果您遇到任何问题,请在 GitHub 中创建 issue,并使用 bug V2 标签。这将帮助我们积极监控和跟踪错误,并继续提高库的性能。

如果您出于任何原因需要使用最新的 Pydantic V1,请参阅下面的 继续使用 Pydantic V1 功能 部分,了解有关从 pydantic.v1 安装和导入的详细信息。

代码转换工具

我们创建了一个工具来帮助您迁移代码。此工具仍处于 beta 阶段,但我们希望它能帮助您更快地迁移代码。

您可以从 PyPI 安装该工具

pip install bump-pydantic

用法很简单。如果您的项目结构是

* repo_folder
    * my_package
        * <python source files> ...

那么您需要执行

cd /path/to/repo_folder
bump-pydantic my_package

有关更多信息,请参阅 Bump Pydantic 仓库。

继续使用 Pydantic V1 功能

当您需要时,Pydantic V1 仍然可用,但我们建议迁移到 Pydantic V2 以获得其改进和新功能。

如果您需要使用最新的 Pydantic V1,可以使用以下命令安装它

pip install "pydantic==1.*"

Pydantic V2 包还继续通过从 pydantic.v1 导入来提供对 Pydantic V1 API 的访问。

例如,您可以使用 Pydantic V1 中的 BaseModel 类,而不是 Pydantic V2 的 pydantic.BaseModel

from pydantic.v1 import BaseModel

您还可以导入已从 Pydantic V2 中删除的函数,例如 lenient_isinstance

from pydantic.v1.utils import lenient_isinstance

Pydantic V1 文档可在 https://docs.pydantic.org.cn/1.10/ 找到。

在 v1/v2 环境中使用 Pydantic v1 功能

pydantic>=1.10.17 开始,pydantic.v1 命名空间可以在 V1 中使用。这使得迁移到 V2 更容易,V2 也支持 pydantic.v1 命名空间。为了解除 pydantic<2 依赖项并继续使用 V1 功能,请执行以下步骤

  1. pydantic<2 替换为 pydantic>=1.10.17
  2. 查找并替换所有出现的
from pydantic.<module> import <object>

替换为

from pydantic.v1.<module> import <object>

以下是如何根据您的 pydantic 版本导入 pydantic 的 v1 功能

v1.10.17 开始,.v1 命名空间在 V1 中可用,允许如下导入

from pydantic.v1.fields import ModelField

所有版本的 Pydantic V1 和 V2 都支持以下导入模式,以防您不知道您正在使用的 Pydantic 版本

try:
    from pydantic.v1.fields import ModelField
except ImportError:
    from pydantic.fields import ModelField

注意

当使用 pydantic>=1.10.17,<2.v1 命名空间导入模块时,这些模块将不是与不带 .v1 命名空间的相同导入的相同模块,但导入的符号是。例如,pydantic.v1.fields is not pydantic.fields,但 pydantic.v1.fields.ModelField is pydantic.fields.ModelField。幸运的是,这在绝大多数情况下不太可能相关。这只是为了提供更平滑的迁移体验而带来的不幸后果。

迁移指南

以下部分详细介绍了 Pydantic V2 中最重要的更改。

pydantic.BaseModel 的更改

各种方法名称已更改;所有未弃用的 BaseModel 方法现在都具有与格式 model_.*__.*pydantic.*__ 匹配的名称。在可能的情况下,我们保留了已弃用的方法及其旧名称,以帮助简化迁移,但调用它们将发出 DeprecationWarning

Pydantic V1 Pydantic V2
__fields__ model_fields
__private_attributes__ __pydantic_private__
__validators__ __pydantic_validator__
construct() model_construct()
copy() model_copy()
dict() model_dump()
json_schema() model_json_schema()
json() model_dump_json()
parse_obj() model_validate()
update_forward_refs() model_rebuild()
  • 一些内置的数据加载功能已被计划移除。特别是,parse_rawparse_file 现在已弃用。在 Pydantic V2 中,model_validate_json 的工作方式类似于 parse_raw。否则,您应该加载数据,然后将其传递给 model_validate
  • from_orm 方法已被弃用;您现在只需使用 model_validate(等效于 Pydantic V1 中的 parse_obj)即可实现类似的功能,只要您在模型配置中设置了 from_attributes=True
  • 模型的 __eq__ 方法已更改。
    • 模型只能与其他 BaseModel 实例相等。
    • 要使两个模型实例相等,它们必须具有相同的
      • 类型(或者,在泛型模型的情况下,非参数化的泛型原始类型)
      • 字段值
      • 额外值(仅当 model_config['extra'] == 'allow' 时相关)
      • 私有属性值;具有不同私有属性值的模型不再相等。
      • 模型不再等于包含其数据的 dict。
      • 不同类型的非泛型模型永远不相等。
      • 具有不同原始类型的泛型模型永远不相等。我们不要求精确的类型相等性,以便例如,MyGenericModel[Any] 的实例可以等于 MyGenericModel[int] 的实例。
  • 我们已用名为 RootModel 的新类型替换了使用 __root__ 字段来指定“自定义根模型”,该类型旨在替换 Pydantic V1 中名为 __root__ 的字段的功能。请注意,RootModel 类型不再支持 arbitrary_types_allowed 配置设置。有关解释,请参阅 此 issue 评论
  • 我们已显着扩展了 Pydantic 在自定义序列化方面的能力。特别是,我们添加了 @field_serializer@model_serializer@computed_field 装饰器,它们各自解决了 Pydantic V1 中的各种缺点。
    • 有关这些新装饰器的用法文档,请参阅 自定义序列化器
    • 由于性能开销和实现复杂性,我们现在已弃用在模型配置中指定 json_encoders 的支持。此功能最初是为了实现自定义序列化逻辑而添加的,我们认为在大多数常见情况下,新的序列化装饰器是更好的选择。
  • 当模型的子类作为父模型中的嵌套字段出现时,我们更改了与序列化模型子类相关的行为。在 V1 中,我们总是包含子类实例中的所有字段。在 V2 中,当我们转储模型时,我们只包含在字段的注解类型上定义的字段。这有助于防止一些意外的安全漏洞。您可以在模型导出文档的 BaseModel、dataclasses、TypedDict 字段的子类实例 部分阅读有关此内容的更多信息(包括如何选择退出此行为)。
  • GetterDict 已被移除,因为它只是已移除的 orm_mode 的实现细节。
  • 在许多情况下,传递给构造函数的参数将被复制,以便执行验证,并在必要时进行强制转换。在将可变对象作为参数传递给构造函数的情况下,这一点尤为重要。您可以在 此处 查看示例 + 更多详细信息。
  • .json() 方法已弃用,尝试使用带有 indentensure_ascii 等参数的已弃用方法可能会导致令人困惑的错误。为了获得最佳结果,请切换到 V2 的等效方法 model_dump_json()。如果您仍然想使用所述参数,可以使用 此解决方法
  • 非字符串键值的 JSON 序列化通常使用 str(key) 完成,这导致了一些行为上的变化,例如以下情况
from typing import Optional

from pydantic import BaseModel as V2BaseModel
from pydantic.v1 import BaseModel as V1BaseModel


class V1Model(V1BaseModel):
    a: dict[Optional[str], int]


class V2Model(V2BaseModel):
    a: dict[Optional[str], int]


v1_model = V1Model(a={None: 123})
v2_model = V2Model(a={None: 123})

# V1
print(v1_model.json())
#> {"a": {"null": 123}}

# V2
print(v2_model.model_dump_json())
#> {"a":{"None":123}}
  • model_dump_json() 结果被压缩以节省空间,并且并不总是与 json.dumps() 输出完全匹配。话虽如此,您可以轻松修改 json.dumps() 结果中使用的分隔符,以使两个输出对齐
import json

from pydantic import BaseModel as V2BaseModel
from pydantic.v1 import BaseModel as V1BaseModel


class V1Model(V1BaseModel):
    a: list[str]


class V2Model(V2BaseModel):
    a: list[str]


v1_model = V1Model(a=['fancy', 'sushi'])
v2_model = V2Model(a=['fancy', 'sushi'])

# V1
print(v1_model.json())
#> {"a": ["fancy", "sushi"]}

# V2
print(v2_model.model_dump_json())
#> {"a":["fancy","sushi"]}

# Plain json.dumps
print(json.dumps(v2_model.model_dump()))
#> {"a": ["fancy", "sushi"]}

# Modified json.dumps
print(json.dumps(v2_model.model_dump(), separators=(',', ':')))
#> {"a":["fancy","sushi"]}

pydantic.generics.GenericModel 的更改

pydantic.generics.GenericModel 类不再必要,并且已被删除。相反,您现在可以通过直接在 BaseModel 子类上添加 Generic 作为父类来创建泛型 BaseModel 子类。这看起来像 class MyGenericModel(BaseModel, Generic[T]): ...

不支持混合使用 V1 和 V2 模型,这意味着此类泛型 BaseModel (V2) 的类型参数不能是 V1 模型。

虽然它可能不会引发错误,但我们强烈建议不要在 isinstance 检查中使用参数化泛型。

  • 例如,您不应该执行 isinstance(my_model, MyGenericModel[int])。但是,可以执行 isinstance(my_model, MyGenericModel)。(请注意,对于标准泛型,使用参数化泛型进行子类检查会引发错误。)
  • 如果您需要对参数化泛型执行 isinstance 检查,可以通过子类化参数化泛型类来完成。这看起来像 class MyIntModel(MyGenericModel[int]): ...isinstance(my_model, MyIntModel)

泛型模型 文档中查找更多信息。

pydantic.Field 的更改

Field 不再支持将任意关键字参数添加到 JSON schema。相反,您要添加到 JSON schema 的任何额外数据都应作为字典传递给 json_schema_extra 关键字参数。

在 Pydantic V1 中,当未设置别名时,alias 属性返回字段的名称。在 Pydantic V2 中,此行为已更改为在未设置别名时返回 None

以下属性已从 Field 中删除或更改

  • const
  • min_items(请改用 min_length
  • max_items(请改用 max_length
  • unique_items
  • allow_mutation(请改用 frozen
  • regex(请改用 pattern
  • final(请改用 typing.Final 类型提示)

字段约束不再自动向下推送到泛型的参数。例如,您不能再通过提供 my_list: list[str] = Field(pattern=".*") 来验证列表的每个元素是否与正则表达式匹配。相反,请使用 typing.Annotatedstr 本身提供注解:my_list: list[Annotated[str, Field(pattern=".*")]]

dataclasses 的更改

Pydantic dataclasses 继续用于在标准 dataclasses 上启用数据验证,而无需继承 BaseModel 子类。Pydantic V2 对此 dataclass 行为引入了以下更改

  • 当用作字段时,dataclasses(Pydantic 或 vanilla)不再接受元组作为验证输入;应改用 dict。
  • Pydantic dataclasses 中的 __post_init__ 现在将在验证之后而不是之前调用。
    • 因此,__post_init_post_parse__ 方法将变得冗余,因此已被删除。
  • Pydantic 不再支持 Pydantic dataclasses 的 extra='allow',其中传递给初始化程序的额外字段将作为 dataclass 上的额外属性存储。extra='ignore' 仍然受支持,用于在解析数据时忽略意外字段,它们只是不会存储在实例上。
  • Pydantic dataclasses 不再具有属性 __pydantic_model__,并且不再使用底层 BaseModel 来执行验证或提供其他功能。
    • 要执行验证、生成 JSON schema 或使用 V1 中可能需要 __pydantic_model__ 的任何其他功能,您现在应该使用 TypeAdapter下面将详细讨论)包装 dataclass 并使用其方法。
  • 在 Pydantic V1 中,如果您使用 vanilla(即非 Pydantic)dataclass 作为字段,则父类型的配置将像dataclass 本身的配置一样使用。在 Pydantic V2 中,情况不再如此。
    • 在 Pydantic V2 中,要覆盖配置(就像您在 BaseModel 上使用 model_config 一样),您可以使用 @dataclass 装饰器上的 config 参数。有关示例,请参阅 Dataclass 配置

config 的更改

  • 在 Pydantic V2 中,要在模型上指定配置,您应该将名为 model_config 的类属性设置为一个 dict,其中包含您希望用作配置的键/值对。Pydantic V1 行为在父 BaseModel 子类的命名空间中创建名为 Config 的类现在已弃用。

  • 当子类化模型时,model_config 属性会被继承。如果您想为许多模型使用具有给定配置的基类,这将非常有用。请注意,如果您从多个 BaseModel 子类继承,例如 class MyModel(Model1, Model2),则来自两个模型的 model_config 属性中的非默认设置将被合并,并且对于在两者中定义的任何设置,来自 Model2 的设置将覆盖来自 Model1 的设置。

  • 以下配置设置已被删除

    • allow_mutation — 此项已被删除。您应该能够等效地使用 frozen(当前用法的逆向)。
    • error_msg_templates
    • fields — 这是各种 bug 的来源,因此已被删除。您应该能够使用 Annotated 在字段上根据需要修改它们。
    • getter_dictorm_mode 已被删除,并且此实现细节不再必要。
    • smart_union - Pydantic V2 中的默认 union_mode'smart'
    • underscore_attrs_are_private — Pydantic V2 行为现在与在 Pydantic V1 中始终设置为 True 时相同。
    • json_loads
    • json_dumps
    • copy_on_model_validation
    • post_init_call
  • 以下配置设置已重命名

    • allow_population_by_field_namepopulate_by_name(或从 v2.11 开始为 validate_by_name
    • anystr_lowerstr_to_lower
    • anystr_strip_whitespacestr_strip_whitespace
    • anystr_upperstr_to_upper
    • keep_untouchedignored_types
    • max_anystr_lengthstr_max_length
    • min_anystr_lengthstr_min_length
    • orm_modefrom_attributes
    • schema_extrajson_schema_extra
    • validate_allvalidate_default

有关更多详细信息,请参阅 ConfigDict API 参考

validators 的更改

@validator@root_validator 已弃用

  • @validator 已被弃用,应替换为 @field_validator,后者提供了各种新功能和改进。
    • 新的 @field_validator 装饰器没有 each_item 关键字参数;您要应用于泛型容器中项目的验证器应通过注解类型参数来添加。有关详细信息,请参阅 Annotated 元数据中的验证器。这看起来像 list[Annotated[int, Field(ge=0)]]
    • 即使您继续使用已弃用的 @validator 装饰器,您也不能再将 fieldconfig 参数添加到验证器函数的签名中。如果您需要访问这些参数,则需要迁移到 @field_validator — 有关更多详细信息,请参阅下一节
    • 如果您对验证器函数使用 always=True 关键字参数,请注意,即使对于默认值,也会应用注解类型的标准验证器,而不仅仅是自定义验证器。例如,尽管下面的验证器永远不会出错,但以下代码会引发 ValidationError

注意

为避免这种情况,您可以在 Field 函数中使用 validate_default 参数。当设置为 True 时,它会模拟 Pydantic v1 中 always=True 的行为。但是,鼓励使用新的 validate_default 方法,因为它提供了更大的灵活性和控制。

from pydantic import BaseModel, validator


class Model(BaseModel):
    x: str = 1

    @validator('x', always=True)
    @classmethod
    def validate_x(cls, v):
        return v


Model()
  • @root_validator 已被弃用,应替换为 @model_validator,后者也提供了新功能和改进。
    • 在某些情况下(例如当 model_config['validate_assignment'] is True 时的赋值),@model_validator 装饰器将接收模型的实例,而不是值字典。您可能需要小心处理这种情况。
    • 即使您继续使用已弃用的 @root_validator 装饰器,由于验证逻辑中的重构,您也不能再使用 skip_on_failure=False 运行(这是此关键字参数的默认值,因此必须显式设置为 True)。

@validator 允许的签名更改

在 Pydantic V1 中,由 @validator 包装的函数可以接收带有关于正在验证的内容的元数据的关键字参数。Pydantic V2 中的 @field_validator 中删除了一些这些参数

  • config:Pydantic V2 的配置现在是一个字典而不是一个类,这意味着此参数不再向后兼容。如果您需要访问配置,则应迁移到 @field_validator 并使用 info.config
  • field:此参数曾经是一个 ModelField 对象,这是一个准内部类,在 Pydantic V2 中不再存在。仍然可以通过使用 info.field_name 中的字段名称索引到 cls.model_fields 来访问此信息的大部分内容
from pydantic import BaseModel, ValidationInfo, field_validator


class Model(BaseModel):
    x: int

    @field_validator('x')
    def val_x(cls, v: int, info: ValidationInfo) -> int:
        assert info.config is not None
        print(info.config.get('title'))
        #> Model
        print(cls.model_fields[info.field_name].is_required())
        #> True
        return v


Model(x=1)

TypeError 不再在 validators 中转换为 ValidationError

以前,当在验证器函数中引发 TypeError 时,该错误将被包装到 ValidationError 中,并且在某些情况下(例如使用 FastAPI),这些错误可能会显示给最终用户。这导致了各种不良行为 — 例如,使用错误的签名调用函数可能会产生面向用户的 ValidationError

但是,在 Pydantic V2 中,当在验证器中引发 TypeError 时,它不再转换为 ValidationError

import pytest

from pydantic import BaseModel, field_validator  # or validator


class Model(BaseModel):
    x: int

    @field_validator('x')
    def val_x(cls, v: int) -> int:
        return str.lower(v)  # raises a TypeError


with pytest.raises(TypeError):
    Model(x=1)

这适用于所有验证装饰器。

Validator 行为更改

Pydantic V2 包括一些类型强制转换的更改。例如

  • intfloatDecimal 值强制转换为字符串现在是可选的,默认情况下禁用,请参阅 将数字强制转换为字符串
  • 成对的迭代器不再强制转换为 dict。

有关 Pydantic V2 类型强制转换默认值的详细信息,请参阅 转换表

allow_reuse 关键字参数不再必要

以前,Pydantic 跟踪装饰器中的“重用”函数,因为这是错误的常见来源。我们通过比较函数的完全限定名称(模块名称 + 函数名称)来做到这一点,这可能会导致误报。当这是有意为之时,可以使用 allow_reuse 关键字参数禁用此功能。

我们检测重复定义函数的方法已得到彻底检查,仅针对单个类中的重新定义发出错误,从而减少误报,并使行为更符合类型检查器和 linter 对于在单个类定义中多次定义同名方法时给出的错误。

在几乎所有情况下,如果您使用 allow_reuse=True,您应该能够简单地删除该关键字参数,并使事情按预期继续工作。

@validate_arguments 已重命名为 @validate_call

在 Pydantic V2 中,@validate_arguments 装饰器已重命名为 @validate_call

在 Pydantic V1 中,装饰的函数添加了各种属性,例如 raw_functionvalidate(可用于验证参数而无需实际调用装饰的函数)。由于这些属性的使用有限以及面向性能的实现更改,我们没有在 @validate_call 中保留此功能。

输入类型未被保留

在 Pydantic V1 中,我们付出了巨大的努力来保留泛型集合的所有字段输入的类型,当它们是字段注解的适当子类型时。例如,给定注解 Mapping[str, int],如果您传入 collection.Counter(),您将获得 collection.Counter() 作为值。

在 V2 中支持此行为会对一般情况产生负面的性能影响(我们每次都必须检查类型),并且会增加验证的复杂性。此外,即使在 V1 中,此行为也是不一致且部分损坏的:它不适用于许多类型(strUUID 等),并且对于泛型集合,如果不进行大量特殊情况处理,则无法正确重建原始输入(考虑 ChainMap;重建输入是必要的,因为我们需要在验证后替换值,例如,如果将字符串强制转换为 int)。

在 Pydantic V2 中,我们不再尝试在所有情况下都保留输入类型;相反,我们只保证输出类型将与类型注解匹配。

回到 Mapping 示例,我们保证输出将是有效的 Mapping,并且在实践中它将是一个普通的 dict

from typing import Mapping

from pydantic import TypeAdapter


class MyDict(dict):
    pass


ta = TypeAdapter(Mapping[str, int])
v = ta.validate_python(MyDict())
print(type(v))
#> <class 'dict'>
from collections.abc import Mapping

from pydantic import TypeAdapter


class MyDict(dict):
    pass


ta = TypeAdapter(Mapping[str, int])
v = ta.validate_python(MyDict())
print(type(v))
#> <class 'dict'>

如果您希望输出类型是特定类型,请考虑将其注解为该类型或实现自定义验证器

from typing import Annotated, Any, Mapping, TypeVar

from pydantic import (
    TypeAdapter,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    WrapValidator,
)


def restore_input_type(
    value: Any, handler: ValidatorFunctionWrapHandler, _info: ValidationInfo
) -> Any:
    return type(value)(handler(value))


T = TypeVar('T')
PreserveType = Annotated[T, WrapValidator(restore_input_type)]


ta = TypeAdapter(PreserveType[Mapping[str, int]])


class MyDict(dict):
    pass


v = ta.validate_python(MyDict())
assert type(v) is MyDict
from typing import Annotated, Any, TypeVar
from collections.abc import Mapping

from pydantic import (
    TypeAdapter,
    ValidationInfo,
    ValidatorFunctionWrapHandler,
    WrapValidator,
)


def restore_input_type(
    value: Any, handler: ValidatorFunctionWrapHandler, _info: ValidationInfo
) -> Any:
    return type(value)(handler(value))


T = TypeVar('T')
PreserveType = Annotated[T, WrapValidator(restore_input_type)]


ta = TypeAdapter(PreserveType[Mapping[str, int]])


class MyDict(dict):
    pass


v = ta.validate_python(MyDict())
assert type(v) is MyDict

虽然我们不保证在所有地方都保留输入类型,但我们确实BaseModel 的子类和 dataclasses 保留它们

import pydantic.dataclasses
from pydantic import BaseModel


class InnerModel(BaseModel):
    x: int


class OuterModel(BaseModel):
    inner: InnerModel


class SubInnerModel(InnerModel):
    y: int


m = OuterModel(inner=SubInnerModel(x=1, y=2))
print(m)
#> inner=SubInnerModel(x=1, y=2)


@pydantic.dataclasses.dataclass
class InnerDataclass:
    x: int


@pydantic.dataclasses.dataclass
class SubInnerDataclass(InnerDataclass):
    y: int


@pydantic.dataclasses.dataclass
class OuterDataclass:
    inner: InnerDataclass


d = OuterDataclass(inner=SubInnerDataclass(x=1, y=2))
print(d)
#> OuterDataclass(inner=SubInnerDataclass(x=1, y=2))

标准类型处理的更改

Dicts

成对的迭代器(包括空迭代器)不再通过类型为 dict 的字段的验证。

Unions

虽然联合类型仍将尝试从左到右验证每个选项,但它们现在尽可能保留输入的类型,即使正确的类型不是输入将通过验证的第一个选项。作为演示,请考虑以下示例

from typing import Union

from pydantic import BaseModel


class Model(BaseModel):
    x: Union[int, str]


print(Model(x='1'))
#> x='1'
from pydantic import BaseModel


class Model(BaseModel):
    x: int | str


print(Model(x='1'))
#> x='1'

在 Pydantic V1 中,打印的结果将是 x=1,因为该值将作为 int 通过验证。在 Pydantic V2 中,我们认识到该值是其中一种情况的实例,并短路了标准联合验证。

要恢复到 V1 的非短路从左到右行为,请使用 Field(union_mode='left_to_right') 注解联合。有关更多详细信息,请参阅 联合模式

必需、可选和可空字段

Pydantic V2 更改了用于指定注解为 Optional 的字段是必需的(即,没有默认值)还是非必需的(即,具有 None 的默认值或相应类型的任何其他值)的一些逻辑,现在更紧密地匹配 dataclasses 的行为。同样,注解为 Any 的字段不再具有 None 的默认值。

下表描述了 V2 中字段注解的行为

状态 字段定义
必需,不能为 None f1: str
非必需,不能为 None,默认为 'abc' f2: str = 'abc'
必需,可以为 None f3: Optional[str]
非必需,可以为 None,默认为 None f4: Optional[str] = None
非必需,可以为 None,默认为 'abc' f5: Optional[str] = 'abc'
必需,可以是任何类型(包括 None f6: Any
非必需,可以是任何类型(包括 None f7: Any = None

注意

被注解为 typing.Optional[T] 的字段将是必需的,并允许值为 None。但这并不意味着该字段具有 None 的默认值。 (这是 V1 版本的一个重大变更。)

注意

任何提供的默认值都会使字段变为非必需。

这是一个代码示例,演示了上述内容

from typing import Optional

from pydantic import BaseModel, ValidationError


class Foo(BaseModel):
    f1: str  # required, cannot be None
    f2: Optional[str]  # required, can be None - same as str | None
    f3: Optional[str] = None  # not required, can be None
    f4: str = 'Foobar'  # not required, but cannot be None


try:
    Foo(f1=None, f2=None, f4='b')
except ValidationError as e:
    print(e)
    """
    1 validation error for Foo
    f1
      Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    """
from pydantic import BaseModel, ValidationError


class Foo(BaseModel):
    f1: str  # required, cannot be None
    f2: str | None  # required, can be None - same as str | None
    f3: str | None = None  # not required, can be None
    f4: str = 'Foobar'  # not required, but cannot be None


try:
    Foo(f1=None, f2=None, f4='b')
except ValidationError as e:
    print(e)
    """
    1 validation error for Foo
    f1
      Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
    """

字符串的模式/正则表达式

Pydantic V1 使用 Python 的 regex 库。Pydantic V2 使用 Rust 的 regex crate。这个 crate 不仅仅是“Rust 版本的正则表达式”,它是一种完全不同的正则表达式方法。特别是,它承诺对字符串进行线性时间搜索,以换取放弃一些功能(即环视和反向引用)。我们认为这是一个值得的权衡,特别是因为 Pydantic 用于验证不受信任的输入,确保事情不会因不受信任的输入而意外地以指数时间运行非常重要。另一方面,对于任何不使用这些复杂正则表达式功能的人来说,正则表达式验证应该会快几个数量级,因为它是在 Rust 中完成的,并且是线性时间的。

如果您仍然想使用 Python 的 regex 库,您可以使用 regex_engine 配置设置。

从浮点数到整数的类型转换

在 V1 中,每当字段被注解为 int 时,任何浮点数值都会被接受,如果浮点数值包含非零小数部分,这可能会导致潜在的数据丢失。在 V2 中,仅当小数部分为零时,才允许从浮点数到整数的类型转换。

from pydantic import BaseModel, ValidationError


class Model(BaseModel):
    x: int


print(Model(x=10.0))
#> x=10
try:
    Model(x=10.2)
except ValidationError as err:
    print(err)
    """
    1 validation error for Model
    x
      Input should be a valid integer, got a number with a fractional part [type=int_from_float, input_value=10.2, input_type=float]
    """

TypeAdapter 的介绍

Pydantic V1 对验证或序列化非 BaseModel 类型类型的支持较弱。

要使用它们,您必须创建一个“根”模型,或者使用 pydantic.tools 中的实用程序函数(即 parse_obj_asschema_of)。

在 Pydantic V2 中,这容易得多TypeAdapter 类允许您创建一个对象,该对象具有用于验证、序列化和为任意类型生成 JSON 模式的方法。这可以完全替代 parse_obj_asschema_of(现在已弃用),并且还涵盖了“根”模型的一些用例。(RootModel如上所述,涵盖了其他用例。)

from pydantic import TypeAdapter

adapter = TypeAdapter(list[int])
assert adapter.validate_python(['1', '2', '3']) == [1, 2, 3]
print(adapter.json_schema())
#> {'items': {'type': 'integer'}, 'type': 'array'}

由于常见类型检查器推断泛型类型的限制,为了在某些情况下获得正确的类型提示,您可能需要显式指定泛型参数。

from pydantic import TypeAdapter

adapter = TypeAdapter[str | int](str | int)
...

有关更多信息,请参阅 Type Adapter

定义自定义类型

我们已经彻底修改了在 pydantic 中定义自定义类型的方式。

我们已经公开了用于生成 pydantic-core 和 JSON 模式的钩子,即使在使用您自己的自定义类型时,您也可以获得 Pydantic V2 的所有性能优势。

我们还引入了使用 typing.Annotated 为您自己的类型添加自定义验证的方法。

主要变化是

  • __get_validators__ 应该替换为 __get_pydantic_core_schema__。有关更多信息,请参阅 自定义数据类型
  • __modify_schema__ 变为 __get_pydantic_json_schema__。有关更多信息,请参阅 JSON 模式自定义

此外,您可以使用 typing.Annotated 通过注解类型而不是修改类型本身来修改或提供类型的 __get_pydantic_core_schema____get_pydantic_json_schema__ 函数。这为将第三方类型与 Pydantic 集成提供了一种强大而灵活的机制,并且在某些情况下可能有助于您删除 Pydantic V1 中引入的用于解决自定义类型限制的 hack。

有关更多信息,请参阅 自定义数据类型

JSON 模式生成的变更

多年来,我们收到了许多关于更改 pydantic 生成的 JSON 模式的请求。

在 Pydantic V2 中,我们试图解决许多常见请求

  • Optional 字段的 JSON 模式现在指示允许值 null
  • Decimal 类型现在在 JSON 模式中公开(并序列化)为字符串。
  • JSON 模式不再将 namedtuples 保留为 namedtuples。
  • 我们默认生成的 JSON 模式现在目标是 draft 2020-12(带有一些 OpenAPI 扩展)。
  • 当它们不同时,您现在可以指定是否需要表示验证输入的 JSON 模式,还是来自序列化的输出的 JSON 模式。

但是,多年来,对于我们未选择实施的更改,已经有很多合理的请求。

在 Pydantic V1 中,即使您愿意自己实施更改,也非常困难,因为 JSON 模式生成过程涉及各种递归函数调用;要覆盖一个,您必须复制并修改整个实现。

在 Pydantic V2 中,我们的设计目标之一是使自定义 JSON 模式生成更容易。为此,我们引入了类 GenerateJsonSchema,它实现了将类型的 pydantic-core 模式转换为 JSON 模式。通过设计,此类将 JSON 模式生成过程分解为更小的方法,这些方法可以在子类中轻松覆盖,以修改生成 JSON 模式的“全局”方法。

可用于生成 JSON 模式的各种方法(例如 BaseModel.model_json_schemaTypeAdapter.json_schema)接受关键字参数 schema_generator: type[GenerateJsonSchema] = GenerateJsonSchema,您可以将自定义子类传递给这些方法,以便使用您自己的方法来生成 JSON 模式。

希望这意味着,如果您不同意我们所做的任何选择,或者如果您依赖于 Pydantic V1 中已在 Pydantic V2 中更改的行为,则可以使用自定义 schema_generator,根据您的应用程序的需要修改 GenerateJsonSchema 类。

BaseSettings 已移动到 pydantic-settings

BaseSettings,Pydantic 设置管理的基础对象,已移动到一个单独的包 pydantic-settings

此外,parse_env_var 类方法已被删除。因此,您需要自定义设置源以拥有自己的解析函数。

颜色和支付卡号已移动到 pydantic-extra-types

以下特殊用途类型已移动到 Pydantic Extra Types 包,如果需要,可以单独安装。

pydantic.networks 中的 Url 和 Dsn 类型不再继承自 str

在 Pydantic V1 中,AnyUrl 类型继承自 str,所有其他 UrlDsn 类型都继承自这些类型。在 Pydantic V2 中,这些类型是使用 Annotated 构建在两个新的 UrlMultiHostUrl 类之上的。

str 继承有优点和缺点,对于 V2,我们决定最好删除它。要在期望 str 的 API 中使用这些类型,您现在需要转换它们(使用 str(url))。

Pydantic V2 使用 Rust 的 Url crate 进行 URL 验证。一些 URL 验证与 V1 中之前的行为略有不同。一个值得注意的区别是,新的 Url 类型会在验证版本的末尾附加斜杠,如果未包含路径,即使在 Url 类型构造函数的参数中未指定斜杠。请参阅下面的示例以了解此行为

from pydantic import AnyUrl

assert str(AnyUrl(url='https://google.com')) == 'https://google.com/'
assert str(AnyUrl(url='https://google.com/')) == 'https://google.com/'
assert str(AnyUrl(url='https://google.com/api')) == 'https://google.com/api'
assert str(AnyUrl(url='https://google.com/api/')) == 'https://google.com/api/'

如果您仍然想使用没有附加斜杠的旧行为,请查看此解决方案

约束类型

Constrained* 类已删除,您应该用 Annotated[<type>, Field(...)] 替换它们,例如

from pydantic import BaseModel, ConstrainedInt


class MyInt(ConstrainedInt):
    ge = 0


class Model(BaseModel):
    x: MyInt

...变为

from typing import Annotated

from pydantic import BaseModel, Field

MyInt = Annotated[int, Field(ge=0)]


class Model(BaseModel):
    x: MyInt

通过 Annotated 组合类型 文档中阅读更多相关信息。

对于 ConstrainedStr,您可以使用 StringConstraints 代替。

Mypy 插件

Pydantic V2 在 pydantic.mypy 中包含一个 mypy 插件。

当使用 V1 功能 时,可能还需要启用 pydantic.v1.mypy 插件。

要配置 mypy 插件

[mypy]
plugins = pydantic.mypy, pydantic.v1.mypy  # include `.v1.mypy` if required.
[tool.mypy]
plugins = [
    "pydantic.mypy",
    "pydantic.v1.mypy",  # include `.v1.mypy` if required.
]

其他变更

  • 放弃了对 email-validator<2.0.0 的支持。请务必使用 pip install -U email-validator 更新。

在 Pydantic V2 中移动

Pydantic V1 Pydantic V2
pydantic.BaseSettings pydantic_settings.BaseSettings
pydantic.color pydantic_extra_types.color
pydantic.types.PaymentCardBrand pydantic_extra_types.PaymentCardBrand
pydantic.types.PaymentCardNumber pydantic_extra_types.PaymentCardNumber
pydantic.utils.version_info pydantic.version.version_info
pydantic.error_wrappers.ValidationError pydantic.ValidationError
pydantic.utils.to_camel pydantic.alias_generators.to_pascal
pydantic.utils.to_lower_camel pydantic.alias_generators.to_camel
pydantic.PyObject pydantic.ImportString

在 Pydantic V2 中已弃用和移动

Pydantic V1 Pydantic V2
pydantic.tools.schema_of pydantic.deprecated.tools.schema_of
pydantic.tools.parse_obj_as pydantic.deprecated.tools.parse_obj_as
pydantic.tools.schema_json_of pydantic.deprecated.tools.schema_json_of
pydantic.json.pydantic_encoder pydantic.deprecated.json.pydantic_encoder
pydantic.validate_arguments pydantic.deprecated.decorator.validate_arguments
pydantic.json.custom_pydantic_encoder pydantic.deprecated.json.custom_pydantic_encoder
pydantic.json.ENCODERS_BY_TYPE pydantic.deprecated.json.ENCODERS_BY_TYPE
pydantic.json.timedelta_isoformat pydantic.deprecated.json.timedelta_isoformat
pydantic.decorator.validate_arguments pydantic.deprecated.decorator.validate_arguments
pydantic.class_validators.validator pydantic.deprecated.class_validators.validator
pydantic.class_validators.root_validator pydantic.deprecated.class_validators.root_validator
pydantic.utils.deep_update pydantic.v1.utils.deep_update
pydantic.utils.GetterDict pydantic.v1.utils.GetterDict
pydantic.utils.lenient_issubclass pydantic.v1.utils.lenient_issubclass
pydantic.utils.lenient_isinstance pydantic.v1.utils.lenient_isinstance
pydantic.utils.is_valid_field pydantic.v1.utils.is_valid_field
pydantic.utils.update_not_none pydantic.v1.utils.update_not_none
pydantic.utils.import_string pydantic.v1.utils.import_string
pydantic.utils.Representation pydantic.v1.utils.Representation
pydantic.utils.ROOT_KEY pydantic.v1.utils.ROOT_KEY
pydantic.utils.smart_deepcopy pydantic.v1.utils.smart_deepcopy
pydantic.utils.sequence_like pydantic.v1.utils.sequence_like

在 Pydantic V2 中已删除

  • pydantic.ConstrainedBytes
  • pydantic.ConstrainedDate
  • pydantic.ConstrainedDecimal
  • pydantic.ConstrainedFloat
  • pydantic.ConstrainedFrozenSet
  • pydantic.ConstrainedInt
  • pydantic.ConstrainedList
  • pydantic.ConstrainedSet
  • pydantic.ConstrainedStr
  • pydantic.JsonWrapper
  • pydantic.NoneBytes
    • 这是 None | bytes 的别名。
  • pydantic.NoneStr
    • 这是 None | str 的别名。
  • pydantic.NoneStrBytes
    • 这是 None | str | bytes 的别名。
  • pydantic.Protocol
  • pydantic.Required
  • pydantic.StrBytes
    • 这是 str | bytes 的别名。
  • pydantic.compiled
  • pydantic.config.get_config
  • pydantic.config.inherit_config
  • pydantic.config.prepare_config
  • pydantic.create_model_from_namedtuple
  • pydantic.create_model_from_typeddict
  • pydantic.dataclasses.create_pydantic_model_from_dataclass
  • pydantic.dataclasses.make_dataclass_validator
  • pydantic.dataclasses.set_validation
  • pydantic.datetime_parse.parse_date
  • pydantic.datetime_parse.parse_time
  • pydantic.datetime_parse.parse_datetime
  • pydantic.datetime_parse.parse_duration
  • pydantic.error_wrappers.ErrorWrapper
  • pydantic.errors.AnyStrMaxLengthError
  • pydantic.errors.AnyStrMinLengthError
  • pydantic.errors.ArbitraryTypeError
  • pydantic.errors.BoolError
  • pydantic.errors.BytesError
  • pydantic.errors.CallableError
  • pydantic.errors.ClassError
  • pydantic.errors.ColorError
  • pydantic.errors.ConfigError
  • pydantic.errors.DataclassTypeError
  • pydantic.errors.DateError
  • pydantic.errors.DateNotInTheFutureError
  • pydantic.errors.DateNotInThePastError
  • pydantic.errors.DateTimeError
  • pydantic.errors.DecimalError
  • pydantic.errors.DecimalIsNotFiniteError
  • pydantic.errors.DecimalMaxDigitsError
  • pydantic.errors.DecimalMaxPlacesError
  • pydantic.errors.DecimalWholeDigitsError
  • pydantic.errors.DictError
  • pydantic.errors.DurationError
  • pydantic.errors.EmailError
  • pydantic.errors.EnumError
  • pydantic.errors.EnumMemberError
  • pydantic.errors.ExtraError
  • pydantic.errors.FloatError
  • pydantic.errors.FrozenSetError
  • pydantic.errors.FrozenSetMaxLengthError
  • pydantic.errors.FrozenSetMinLengthError
  • pydantic.errors.HashableError
  • pydantic.errors.IPv4AddressError
  • pydantic.errors.IPv4InterfaceError
  • pydantic.errors.IPv4NetworkError
  • pydantic.errors.IPv6AddressError
  • pydantic.errors.IPv6InterfaceError
  • pydantic.errors.IPv6NetworkError
  • pydantic.errors.IPvAnyAddressError
  • pydantic.errors.IPvAnyInterfaceError
  • pydantic.errors.IPvAnyNetworkError
  • pydantic.errors.IntEnumError
  • pydantic.errors.IntegerError
  • pydantic.errors.InvalidByteSize
  • pydantic.errors.InvalidByteSizeUnit
  • pydantic.errors.InvalidDiscriminator
  • pydantic.errors.InvalidLengthForBrand
  • pydantic.errors.JsonError
  • pydantic.errors.JsonTypeError
  • pydantic.errors.ListError
  • pydantic.errors.ListMaxLengthError
  • pydantic.errors.ListMinLengthError
  • pydantic.errors.ListUniqueItemsError
  • pydantic.errors.LuhnValidationError
  • pydantic.errors.MissingDiscriminator
  • pydantic.errors.MissingError
  • pydantic.errors.NoneIsAllowedError
  • pydantic.errors.NoneIsNotAllowedError
  • pydantic.errors.NotDigitError
  • pydantic.errors.NotNoneError
  • pydantic.errors.NumberNotGeError
  • pydantic.errors.NumberNotGtError
  • pydantic.errors.NumberNotLeError
  • pydantic.errors.NumberNotLtError
  • pydantic.errors.NumberNotMultipleError
  • pydantic.errors.PathError
  • pydantic.errors.PathNotADirectoryError
  • pydantic.errors.PathNotAFileError
  • pydantic.errors.PathNotExistsError
  • pydantic.errors.PatternError
  • pydantic.errors.PyObjectError
  • pydantic.errors.PydanticTypeError
  • pydantic.errors.PydanticValueError
  • pydantic.errors.SequenceError
  • pydantic.errors.SetError
  • pydantic.errors.SetMaxLengthError
  • pydantic.errors.SetMinLengthError
  • pydantic.errors.StrError
  • pydantic.errors.StrRegexError
  • pydantic.errors.StrictBoolError
  • pydantic.errors.SubclassError
  • pydantic.errors.TimeError
  • pydantic.errors.TupleError
  • pydantic.errors.TupleLengthError
  • pydantic.errors.UUIDError
  • pydantic.errors.UUIDVersionError
  • pydantic.errors.UrlError
  • pydantic.errors.UrlExtraError
  • pydantic.errors.UrlHostError
  • pydantic.errors.UrlHostTldError
  • pydantic.errors.UrlPortError
  • pydantic.errors.UrlSchemeError
  • pydantic.errors.UrlSchemePermittedError
  • pydantic.errors.UrlUserInfoError
  • pydantic.errors.WrongConstantError
  • pydantic.main.validate_model
  • pydantic.networks.stricturl
  • pydantic.parse_file_as
  • pydantic.parse_raw_as
  • pydantic.stricturl
  • pydantic.tools.parse_file_as
  • pydantic.tools.parse_raw_as
  • pydantic.types.JsonWrapper
  • pydantic.types.NoneBytes
  • pydantic.types.NoneStr
  • pydantic.types.NoneStrBytes
  • pydantic.types.PyObject
  • pydantic.types.StrBytes
  • pydantic.typing.evaluate_forwardref
  • pydantic.typing.AbstractSetIntStr
  • pydantic.typing.AnyCallable
  • pydantic.typing.AnyClassMethod
  • pydantic.typing.CallableGenerator
  • pydantic.typing.DictAny
  • pydantic.typing.DictIntStrAny
  • pydantic.typing.DictStrAny
  • pydantic.typing.IntStr
  • pydantic.typing.ListStr
  • pydantic.typing.MappingIntStrAny
  • pydantic.typing.NoArgAnyCallable
  • pydantic.typing.NoneType
  • pydantic.typing.ReprArgs
  • pydantic.typing.SetStr
  • pydantic.typing.StrPath
  • pydantic.typing.TupleGenerator
  • pydantic.typing.WithArgsTypes
  • pydantic.typing.all_literal_values
  • pydantic.typing.display_as_type
  • pydantic.typing.get_all_type_hints
  • pydantic.typing.get_args
  • pydantic.typing.get_origin
  • pydantic.typing.get_sub_types
  • pydantic.typing.is_callable_type
  • pydantic.typing.is_classvar
  • pydantic.typing.is_finalvar
  • pydantic.typing.is_literal_type
  • pydantic.typing.is_namedtuple
  • pydantic.typing.is_new_type
  • pydantic.typing.is_none_type
  • pydantic.typing.is_typeddict
  • pydantic.typing.is_typeddict_special
  • pydantic.typing.is_union
  • pydantic.typing.new_type_supertype
  • pydantic.typing.resolve_annotations
  • pydantic.typing.typing_base
  • pydantic.typing.update_field_forward_refs
  • pydantic.typing.update_model_forward_refs
  • pydantic.utils.ClassAttribute
  • pydantic.utils.DUNDER_ATTRIBUTES
  • pydantic.utils.PyObjectStr
  • pydantic.utils.ValueItems
  • pydantic.utils.almost_equal_floats
  • pydantic.utils.get_discriminator_alias_and_values
  • pydantic.utils.get_model
  • pydantic.utils.get_unique_discriminator_alias
  • pydantic.utils.in_ipython
  • pydantic.utils.is_valid_identifier
  • pydantic.utils.path_type
  • pydantic.utils.validate_field_name
  • pydantic.validate_model