把 Pydantic V1 项目分阶段迁移到 V2:模型、验证、序列化与完整符号清单

原文作者:Pydantic 文档贡献者;Pydantic Services Inc. 与 individual contributors。中文译写与编辑整理:未完纪。

Pydantic V2 改变了公开 API,也改变了若干输入验证与输出序列化行为。迁移工作的难点不只在函数改名:一份旧输入是否仍被接受、缺失字段是否仍有默认值、输出 JSON 是否仍符合客户端约定,往往更决定升级能否完成。

本文依据 官方 Migration Guide 完整译写与整理,覆盖全部实质章节、示例及末尾符号清单。迁移页中少数早期说明与当前专题文档不一致,本文在对应位置明确勘误。文中的命令和代码没有执行,示例输出均来自原文或用于解释代码的预期行为。

迁移先记录版本和历史输入输出,用pydantic.v1稳定旧行为,再逐模块改用V2;随后比较验证及JSON契约,检查拆分依赖与Schema,在完成回归后切换并保留回退。
把迁移拆成可检查的阶段(原创技术示意图)

先建立一条可回退的迁移路线

编辑建议把工作分成几个可独立检查的阶段:先记录当前Python、依赖锁文件及有代表性的脱敏输入输出;再借助 pydantic.v1 让旧模型继续工作;随后逐模块转换公开API、配置与验证器;最后比较验证结果、JSON、JSON Schema和调用方行为。每个阶段都要在项目自己的测试环境验证,不能把包升级成功视为业务迁移完成。

以下仍按原指南的技术次序展开。迁移页将V2作为当前生产发布线,给出的安装命令是:

pip install -U pydantic

这条命令会改变环境。实际项目应在隔离环境和版本控制分支中验证,并按依赖管理方式锁定最终版本;不要直接在运行中的生产环境执行。遇到问题时,官方建议向 Pydantic 仓库 提交带 bug V2 标签的问题,并提供经过脱敏的复现信息。

自动改写可以加速,但仍需审核

官方提供 bump-pydantic 帮助改写代码,迁移页仍把它标为 beta。先安装工具;若项目结构是 repo_folder/my_package/,在仓库目录执行目标包的转换:

pip install bump-pydantic
cd /path/to/repo_folder
bump-pydantic my_package

/path/to/repo_folder 是应替换的示意路径。运行后要逐项检查 diff,尤其是自定义验证器、序列化规则、配置继承和第三方集成。工具可以改变代码形式,不能替你判断旧协议是否仍然成立。详见 Bump Pydantic。

用 pydantic.v1 保留过渡期的旧行为

仍需使用V1时,原指南提供如下安装方式:

pip install "pydantic==1.*"

V2发行包也提供V1兼容命名空间。下面分别示范旧版BaseModel和V2已移除工具函数的导入:

from pydantic.v1 import BaseModel
from pydantic.v1.utils import lenient_isinstance

V1文档可从 1.10文档 查阅。pydantic.v1 在V1中从1.10.17起也可用,因此可以先把 pydantic<2 依赖约束调整到允许 pydantic>=1.10.17,并把旧API的导入统一迁到 .v1。项目仍应根据已验证支持范围设置上界和锁文件,而不是不加约束地接受所有未来主版本。

# 原导入形式
from pydantic.fields import ModelField

# 过渡形式:V1 >= 1.10.17 与 V2 均提供该命名空间
from pydantic.v1.fields import ModelField

如果兼容的V1版本可能早于1.10.17,原文给出下面的兼容导入:

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

模块对象的身份还有一个细节:在V1的兼容命名空间下,pydantic.v1.fields is not pydantic.fields,但二者导出的 ModelField 是同一个符号。大多数调用不受影响;依赖模块对象身份进行判断的代码需要注意。

BaseModel:先改接口,再检查语义

V2把大量方法统一到 model_ 前缀,内部属性则多采用 __pydantic...__ 形式。部分旧方法为了过渡仍可调用,但会发出弃用警告。以下保留原迁移表的主要对应关系,并对有歧义之处作了标注:

V1用法或原迁移表项 V2方向
__fields__ model_fields,从模型类读取字段信息。
__private_attributes__ 原迁移表列为__pydantic_private__;二者当前语义不同,见下文。
__validators__ 原表列为__pydantic_validator__;装饰器元数据另见__pydantic_decorators__。
construct() model_construct()。
copy() model_copy()。
dict() model_dump()。
schema() model_json_schema();原迁移表写作json_schema(),已据V1文档纠正。
json() model_dump_json()。
parse_obj() model_validate()。
update_forward_refs() model_rebuild()。

两处内部属性不能做盲目替换。当前BaseModel API 仍把 __private_attributes__ 定义为私有属性的元数据,而 __pydantic_private__ 保存实例的私有值;验证器装饰器的元数据由 __pydantic_decorators__ 表达,__pydantic_validator__ 是执行验证的底层验证器。按用途改写比替换字符更重要。

Schema方法也需要辨清:V1 Schema文档 的模型方法是 schema() 和 schema_json();迁移表的V1 json_schema() 不能照抄成一个实际存在的旧方法。V2的 model_json_schema() 返回Schema字典。

parse_raw 与 parse_file 已弃用。对于JSON字符串或字节,使用 model_validate_json;其他数据格式先自行加载,再交给 model_validate。from_orm 也已弃用:在配置中设置 from_attributes=True 后,使用 model_validate 读取对象属性。

model_construct() 延续“从可信或已验证数据构造”的用途,不执行常规验证。它不是处理外部请求的快速替代通道;把不可信输入直接送进去会绕过数据约束。

相等性、根模型与序列化变化

V2模型只与其他BaseModel实例进行模型相等比较,不再与装有相同数据的普通字典相等。两个实例需要拥有相同的模型类型(泛型时按未参数化的泛型来源判断)、字段值、允许的额外值,以及私有属性值。不同非泛型类型不会相等;泛型来源不同也不会相等,但同一泛型来源的 MyGenericModel[Any] 与 MyGenericModel[int] 在相应值相同的情况下仍可能相等。

自定义根模型改用 RootModel,不再用名为 __root__ 的字段。RootModel 不支持 arbitrary_types_allowed 配置,不能假设把类型名称换掉就会接受任意旧根类型。

定制输出现在可以使用 @field_serializer、@model_serializer 与 @computed_field。配置中的 json_encoders 已被标为弃用,迁移页建议通常优先使用这些序列化装饰器。

嵌套模型的子类导出也改变了:V1往往把子类实例的全部字段导出;V2默认按父模型字段所声明的类型决定输出字段。这样可以减少意外泄露子类敏感字段的机会。如果旧客户端依赖额外字段,应先明确输出契约,再查阅序列化文档选择是否改变默认行为。

GetterDict 随旧 orm_mode 实现退出主API。为了验证和必要的转换,构造器接收的某些参数会被复制,传入可变对象时尤其需要重新检查共享引用假设。弃用的 .json() 在接收 indent、ensure_ascii 等参数时还可能报出令人困惑的错误,宜改用V2方法或对合适的 model_dump() 结果自行使用JSON工具处理。

JSON键名与空白可能改变

V2对非字符串JSON键通常使用 str(key)。下面的 None 键在两代中产生不同键名,这属于协议变化,不能仅当作空白差异忽略:

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})

print(v1_model.json())
# {"a": {"null": 123}}
print(v2_model.model_dump_json())
# {"a":{"None":123}}

此外,model_dump_json() 默认输出更紧凑。下面保留原文对V1、V2、普通 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'])

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

比较JSON语义时可以解析后比较对象;如果系统把原始字节用于签名、缓存键或快照,则空白也可能重要。这是编辑补充的回归方向,并不意味着应当对所有项目强制使用同一输出格式。

泛型模型不再需要 GenericModel

pydantic.generics.GenericModel 不再是必要基类。V2可以直接让模型同时继承 BaseModel 与 Generic[T],例如 class MyGenericModel(BaseModel, Generic[T]): ...。

V1与V2模型不支持混合使用,因此V2泛型模型的类型参数不能是V1模型。迁移时应按模型边界推进,而不是把新旧模型随意嵌进同一泛型体系。

不要用 isinstance(my_model, MyGenericModel[int]) 检查参数化泛型;可以检查 MyGenericModel。如果确实需要针对特定参数类型检查,先定义普通子类,例如 class MyIntModel(MyGenericModel[int]): ...,再使用 isinstance(my_model, MyIntModel)。

Field与约束应落在正确的类型层级

V2不再支持把任意关键字参数直接交给 Field 并加入JSON Schema。额外Schema信息应集中到 json_schema_extra 字典。没有设置别名时,Field 的 alias 在V2返回None,而V1会回退为字段名;生成映射或检查别名的代码要处理这一差异。

原Field参数 迁移方向
const 已移除,需要用适合的类型/约束表达固定值。
min_items、max_items 改用min_length、max_length。
unique_items 已移除,按业务要求选择集合或显式校验。
allow_mutation 改用frozen,注意逻辑方向相反。
regex 改用pattern。
final 使用typing.Final类型提示。

泛型容器的约束不再自动下传给元素。list[str] = Field(pattern=".*") 不能表示逐个校验字符串,应把约束注在元素类型上:

from typing import Annotated
from pydantic import BaseModel, Field

class Model(BaseModel):
    my_list: list[Annotated[str, Field(pattern=".*")]]

数据类的验证、生命周期与配置

Pydantic数据类仍可为标准数据类形式增加验证,而不必继承BaseModel。作为模型字段时,无论是Pydantic数据类还是普通数据类,都不再接受元组作为验证输入,应提供字典。

Pydantic数据类的 __post_init__ 在验证之后调用,旧的 __post_init_post_parse__ 因而被移除。它们不再依赖隐藏的BaseModel,也没有旧的 __pydantic_model__。需要外部验证、序列化或生成Schema时,应使用 TypeAdapter 包装数据类。

普通数据类作为字段时,不再隐式继承父模型的配置。需要覆盖设置,可以使用 @dataclass(config=...);不要以为父模型的 model_config 自动穿透这一边界。

关于extra的原文勘误: 迁移页仍写有“不支持extra=’allow’”的旧概括,但Pydantic维护者在 讨论7362 中已经确认该说法有误,并给出了支持这一设置的测试。当前 数据类专题 强调的是:额外数据不进入序列化,也不能像BaseModel那样通过 __pydantic_extra__ 自定义额外值的验证。extra='ignore' 则用于忽略意外字段。迁移时应分别检查接收、属性存储、对象表示和序列化,不能把这些行为当成一件事。

配置从内部Config类迁到model_config

V2推荐在模型上定义字典形式的 model_config。V1常见的内嵌 class Config 已弃用。模型子类会继承配置;多继承如 class MyModel(Model1, Model2) 会合并非默认设置,冲突项以Model2的设置为准。

以下是原指南列出的移除项,全部保留:

移除的配置 处理说明
allow_mutation 使用frozen,将原布尔逻辑反转。
error_msg_templates 已移除,重新设计错误表达。
fields 已移除,优先以Annotated修饰字段。
getter_dict 随旧orm_mode实现退出。
smart_union V2默认union_mode为smart。
underscore_attrs_are_private V2行为相当于V1始终开启。
json_loads、json_dumps 不再以旧配置项定制。
copy_on_model_validation、post_init_call 已移除,核对新的复制和初始化行为。

以下设置改名:

V1 V2
allow_population_by_field_name populate_by_name;2.11起另有validate_by_name等更细的配置,应按目标版本使用。
anystr_lower str_to_lower
anystr_strip_whitespace str_strip_whitespace
anystr_upper str_to_upper
keep_untouched ignored_types
max_anystr_length str_max_length
min_anystr_length str_min_length
orm_mode from_attributes
schema_extra json_schema_extra
validate_all validate_default

验证器变化需要逐个审查

field_validator与model_validator

@validator 已弃用,改用 @field_validator。新装饰器没有 each_item 参数;容器元素的校验应放在类型参数上,例如 list[Annotated[int, Field(ge=0)]]。即使暂时保留旧装饰器,也不能继续在验证函数签名中接收旧的 field 或 config 参数。

always=True 容易引出默认值差异:它不只触发自定义验证器,类型的标准验证也会用于默认值。原文下面的验证函数本身不会主动报错,Model() 仍会因为字符串字段默认值是整数而失败:

from pydantic import BaseModel, validator

class Model(BaseModel):
    x: str = 1

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

Model()  # 原文用于说明会抛出 ValidationError

迁移到V2时,使用 Field(validate_default=True) 显式控制默认值是否验证,并确保默认值本身符合类型。这并不是让错误默认值自动变正确,也不保证旧的所有转换规则恢复。

@root_validator 改用 @model_validator,签名及接收值可能不同。特别是在开启 validate_assignment 后进行赋值验证等场景,验证器可能收到模型实例,而非值字典。即使继续使用弃用的root_validator,也不能再使用 skip_on_failure=False;需明确设置为True。

从ValidationInfo获取上下文

旧 config 参数曾是配置类,V2配置已变为字典,应通过 info.config 读取。旧 field 参数所提供的ModelField不再是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)

这个例子的打印用于展示元数据。本次没有执行。编辑补充:生产数据校验不应依赖可能被Python优化模式移除的assert来实施安全约束;这里的assert只是原文对内部配置存在性的检查。

TypeError不再包装成ValidationError

V1可能把验证器内部的TypeError包装为ValidationError,导致调用错误或函数签名错误被当成用户输入问题。V2不再这样转换,规则适用于所有验证装饰器。原文用故意把整数传给字符串方法的方式说明:

import pytest
from pydantic import BaseModel, field_validator

class Model(BaseModel):
    x: int

    @field_validator('x')
    def val_x(cls, v: int) -> int:
        return str.lower(v)  # 故意触发 TypeError

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

这是说明错误类别的反例,不是可用于校验整数的实现。接口层不能只捕获ValidationError便认定所有其他错误都不可能出现;同时也不应把程序错误的内部细节直接返回给外部用户。

转换规则与allow_reuse

V2默认不再把int、float和Decimal自动转换成字符串;这种数字转字符串能力成为可选设置。成对元素的可迭代对象也不再自动变成字典。具体输入类型应对照当前转换表,而不是沿用V1的宽松程度。

allow_reuse=True 在多数情况下可以删除。V1通过函数的完整限定名判断重复使用,可能产生误报;V2主要检查同一类内部重复定义同名方法,更接近类型检查器和代码检查器的行为。删除该参数不意味着可以在一个类中任意覆盖验证器定义。

validate_arguments更名为validate_call

调用参数验证的装饰器改为 @validate_call。迁移页概括旧的 raw_function 和 validate 等附加属性没有保留,但这个说法不能作为当前全部V2版本的结论:当前验证装饰器文档 明确支持通过 raw_function 访问原始函数。旧的“只验证参数而不调用函数”的 validate 接口则不能据此推定已经恢复。

当前专题的示例说明了两条调用路径:

from pydantic import validate_call

@validate_call
def repeat(s: str, count: int, *, separator: bytes = b'') -> bytes:
    b = s.encode()
    return separator.join(b for _ in range(count))

print(repeat('hello', 3))
# b'hellohellohello'

print(repeat.raw_function('good bye', 2, separator=b', '))
# b'good bye, good bye'

第二条路径绕过装饰器验证,只适合已可信的输入,不能作为公共请求入口。部分类型检查器不能识别这一附加属性;应按当前版本的文档处理类型提示,而不是把运行能力与静态可见性混为一谈。

输出类型符合声明,不保证保留每种输入容器

V1曾尽力保留集合输入的具体子类型,但代价高且行为并不一致。V2主要保证输出符合类型注解,不保证所有输入子类都保留下来。例如声明为 Mapping[str, int],传入自定义dict子类后,通常得到普通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 collections.abc import Mapping
from typing import Annotated, Any, 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

编辑补充:这个模式假定输入类型能用校验后的值重新构造,不适合不加审核地套到所有容器,例如需要额外构造参数或特殊状态的类型。它也不能消除校验过程中对值的转换。

V2仍会保留BaseModel子类和数据类子类的输入类型。原文分别展示这两种情况:

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))

保留运行时子类实例与“序列化时输出子类所有字段”是不同问题,前者不抵消前面介绍的按注解类型导出规则。

标准类型的几项关键变化

字典与联合类型

声明为dict的字段不再接受成对元素的可迭代对象,空可迭代对象也包含在内。若旧数据源返回的是键值对列表,应在清晰的输入边界显式转换,并定义重复键等业务规则。

联合类型在V2中尽量保留输入原有类型。例如字符串 '1' 本身符合str分支,就不会因为int排在前面而必然变成整数:

from typing import Union
from pydantic import BaseModel

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

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

V1在这个例子中会得到 x=1。如果业务确实需要从左到右尝试的旧行为,可在联合字段使用 Field(union_mode='left_to_right')。默认smart模式还有自己的匹配规则,这个简单例子不能代表任意复杂模型联合的全部选择逻辑。

必填、可省略和可为None是三个不同概念

Optional[T] 表示可以为None,不等于有None默认值。V2让这一点更接近普通数据类的语义;Any也不再隐含None默认值。原文的七种情况如下:

字段声明 是否必填 是否允许None及默认值
f1: str 是 不允许None。
f2: str = 'abc' 否 不允许None,默认’abc’。
f3: Optional[str] 是 允许None,没有默认值。
f4: Optional[str] = None 否 允许None,默认None。
f5: Optional[str] = 'abc' 否 允许None,默认’abc’。
f6: Any 是 可以是任何类型,包括None。
f7: Any = None 否 可以是任何类型,默认None。

只要提供默认值,字段就不再必填;是否允许None仍由类型注解决定。下面的原文示例给f1传None,因此失败,而f2允许None:

from typing import Optional
from pydantic import BaseModel, ValidationError

class Foo(BaseModel):
    f1: str
    f2: Optional[str]
    f3: Optional[str] = None
    f4: str = 'Foobar'

try:
    Foo(f1=None, f2=None, f4='b')
except ValidationError as e:
    print(e)
    # f1: Input should be a valid string
    # type=string_type, input_value=None, input_type=NoneType

实际项目还要回归“字段完全缺失”和“显式传入null”两类请求。上例仅演示错误,生产日志中的ValidationError可能包含用户输入,应按数据敏感程度脱敏。

正则表达式引擎

V1使用Python正则库,V2默认使用Rust的regex库。后者以不支持环视和反向引用等特性为代价,提供线性时间搜索的保证;这对验证不可信字符串尤其重要。不能把它当作Python正则的逐项等价实现。

如果项目必须保留Python正则行为,可使用 regex_engine 配置选择相应引擎。但要重新评估模式与输入长度带来的回溯风险。原文提到Rust执行和线性算法可能带来明显性能改善,这不是本次性能测量,也不能直接推广成所有项目的固定倍数。

浮点数转整数不再默默截断小数

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)
    # 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处理任意类型

V1验证或序列化非BaseModel类型时,常需创建根模型,或使用 parse_obj_as、schema_of 等工具。V2的 TypeAdapter 为任意适用类型提供验证、序列化和Schema生成方法,可替代这些已弃用工具,也覆盖一部分原根模型场景;需要模型形态的其余场景使用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)

这里的 str | int 是Python联合类型语法,读者应使用与项目Python版本及Pydantic支持范围相容的写法。

自定义类型与JSON Schema生成

自定义类型的底层钩子已经重构。旧 __get_validators__ 迁到 __get_pydantic_core_schema__,旧 __modify_schema__ 迁到 __get_pydantic_json_schema__。这使自定义类型能够接入pydantic-core与JSON Schema生成。

也可以通过 typing.Annotated 为现有类型增加验证和Schema行为,而不必修改该类型本身。这对第三方类型尤其有用,并可能消除V1时代为绕过限制而加入的补丁。钩子内部协议应依据当前自定义类型文档实现,不能仅更改旧函数名称而保留原签名和返回值。

JSON Schema方面,原文列出以下变化:Optional字段会体现null;Decimal在Schema及序列化中表现为字符串;namedtuple不再保留为命名元组形式;默认目标规范变成draft 2020-12,并带有部分OpenAPI扩展。输入验证与输出序列化不一致时,可以选择为哪一侧生成Schema。

V1的Schema生成过程由相互递归的函数组成,较难只修改某一环节。V2引入 GenerateJsonSchema,把生成步骤拆成可覆盖的方法。BaseModel.model_json_schema 和 TypeAdapter.json_schema 等入口接受 schema_generator 参数,默认是 GenerateJsonSchema;传入其子类可以统一改变生成策略。

这种定制能力适合确实依赖旧Schema格式的系统,但仍需验证下游客户端、OpenAPI工具和验证器是否按预期工作;改变Schema描述并不自动改变输入验证逻辑。

拆分出去的包与类型

BaseSettings

BaseSettings 已迁往独立的 pydantic-settings 包。旧的 parse_env_var 类方法被移除,定制环境变量解析需改用自定义设置来源。迁移时同时检查部署环境、秘密读取方式和配置解析测试,而不只是修改导入。

颜色与支付卡类型

Color和Payment Card Numbers等专门类型迁往按需安装的 pydantic-extra-types。迁移页索引把支付卡类型列在该包名下;实际模块路径应以 当前Payment API 为准,例如 pydantic_extra_types.payment。这些类型做格式校验,不等于完成支付授权或卡片有效性验证。

URL与DSN不再继承str

V1的AnyUrl继承str,其它URL和DSN类型也沿用这一层次。V2改为围绕新的URL实现构建,不再是字符串子类。调用要求str的外部API时,要显式使用 str(url)。

URL验证改用Rust的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/'

若旧系统依赖未补斜杠的原始字符串,应检查实际用途和当前URL配置能力,不能用肉眼看起来相同来代替缓存键、签名或路径比较的回归。

受约束类型改为Annotated

Constrained* 类被移除,可用 Annotated[类型, Field(...)] 表达约束。原文的V1写法是:

from pydantic import BaseModel, ConstrainedInt

class MyInt(ConstrainedInt):
    ge = 0

class Model(BaseModel):
    x: MyInt

迁到V2后,对应为:

from typing import Annotated
from pydantic import BaseModel, Field

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

class Model(BaseModel):
    x: MyInt

ConstrainedStr 可使用 StringConstraints。上面的V1片段作为对照保留,不是要求在V2中继续导入已移除的ConstrainedInt。

类型检查与其他依赖变化

V2的mypy插件为 pydantic.mypy;项目仍使用V1兼容功能时,可能还要启用 pydantic.v1.mypy。原文同时给出两种配置文件形式:

[mypy]
plugins = pydantic.mypy, pydantic.v1.mypy
[tool.mypy]
plugins = [
    "pydantic.mypy",
    "pydantic.v1.mypy",  # 仍需 V1 功能时加入
]

email-validator<2.0.0 不再受支持。原文建议通过下面命令升级;实际项目应和其它依赖一起在测试环境锁定、验证:

pip install -U email-validator

迁移到新位置的符号

下表完整覆盖原文迁移索引。支付卡两项保留原索引的包级归属,并在前文说明实际使用的payment模块;表格用于定位改动,不代表任意旧路径都仍可直接导入。

V1位置 V2位置或归属
pydantic.BaseSettings pydantic_settings.BaseSettings
pydantic.color pydantic_extra_types.color
pydantic.types.PaymentCardBrand 原索引:pydantic_extra_types.PaymentCardBrand;实际查看pydantic_extra_types.payment.PaymentCardBrand。
pydantic.types.PaymentCardNumber 原索引:pydantic_extra_types.PaymentCardNumber;实际查看pydantic_extra_types.payment.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

已弃用并移动的符号

这些位置保留兼容功能,不意味着是新代码首选。下面逐项保留原文索引:

V1 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

如何使用末尾的移除清单

原指南最后列出大量V2移除符号。本文在附录逐项保留完整名单,便于全文检索旧导入,没有因为索引长而截断。NoneBytes、NoneStr、NoneStrBytes、StrBytes 原先分别只是 None | bytes、None | str、None | str | bytes、str | bytes 的别名。其余错误类、类型工具和内部辅助函数应按实际用途迁移,不应自行猜测一个新类名替代。

完成代码改写后,回到真实边界验证:旧请求是否仍被接受,None与缺失是否区分,错误类型是否正确进入异常处理层,模型相等性是否影响缓存或测试,序列化是否漏掉或意外增加敏感字段,URL规范化及Schema是否改变下游行为。确认这些问题,比“仓库里再也搜不到旧方法名”更能说明迁移已经完成。

来源、版本与译写说明

原文为 Pydantic Migration Guide,归属Pydantic Services Inc.与文档贡献者,适用 MIT许可证。本次2026-10-05读取完整英文正文,并以当前验证装饰器、数据类、BaseModel API、Payment API和V1 Schema文档核对易误读之处。全文对这些勘误均给出来源;没有安装依赖、执行示例或运行迁移测试。配图为未完纪原创技术示意图。

附录:V2 移除符号完整检索表

以下 188 项逐项保留原文末尾移除索引,用于检索旧导入。它们不构成替代 API 指南;适用替代方式见正文及当前 API 文档。

  • pydantic.ConstrainedBytes
  • pydantic.ConstrainedDate
  • pydantic.ConstrainedDecimal
  • pydantic.ConstrainedFloat
  • pydantic.ConstrainedFrozenSet
  • pydantic.ConstrainedInt
  • pydantic.ConstrainedList
  • pydantic.ConstrainedSet
  • pydantic.ConstrainedStr
  • pydantic.JsonWrapper
  • pydantic.NoneBytes
  • pydantic.NoneStr
  • pydantic.NoneStrBytes
  • pydantic.Protocol
  • pydantic.Required
  • pydantic.StrBytes
  • 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

代码版权与完整许可

保留上游 Pydantic 版权与 MIT 许可;文档译写保留原始署名与来源,编辑勘误已分别标出。

The MIT License (MIT)

Copyright (c) 2017 to present Pydantic Services Inc. and individual contributors.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容