Pydantic 2.14 泛型 dataclass 带 Self,建 schema 时报错
递归 Self 字段放进参数化 dataclass 后,2.14.1 可能保留解析前的 origin。先区分类型构建与输入验证,保留类型参数,临时恢复兼容依赖或改用明确的递归注解。
一个用 Self 指向父节点的泛型 dataclass,未参数化时可以建立 TypeAdapter,换成 Node[int] 后却抛 PydanticSchemaGenerationError。若这是升级到 Pydantic 2.14.x 后出现的变化,应先检查类型构建路径,不能直接把父节点数据判成循环引用。
下面是涉及这条路径的结构示例,使用 Python 3.11 及以上的 typing.Self:
from dataclasses import dataclass
from typing import Generic, Self, TypeVar
from pydantic import TypeAdapter
T = TypeVar("T")
@dataclass
class Node(Generic[T]):
value: T
parent: Self | None = None
adapter = TypeAdapter(Node[int])
Pydantic 2.14.1 中,schema 生成先取得类型的 origin,内部再把 Self 解析为当前构建的具体类型。Self 解析前后的类型可能已经不同,但传下来的 origin 仍是旧值,导致参数化递归 dataclass 走错分支。普通标准库 dataclass 与 Pydantic 装饰的泛型 dataclass 都需要核对,不能只换一个装饰器就假定不受影响。
先保留失败类型,区分构建错误与验证错误
记录具体出错的是 Node 还是 Node[int],以及出错发生在建立适配器、定义外层模型,还是已经进入 validate_python()。这类故障可以在还没提供数据时发生,清空 parent 的输入内容并不能改变 schema 解析规则。
直接去掉 [int] 可能绕过参数化分支,但同时失去具体整数类型约束。对于图结构、节点容器或其他泛型模型,这会改变本来要验证的内容。也不要把父节点标成 Any 后只检查“能创建对象”,那没有证明递归类型仍得到验证。
临时维持已有依赖组合,或单独评估注解调整
已经确认 Pydantic 2.13.x 可以处理原结构的项目,可在兼容性检查后临时恢复原锁文件对应的完整依赖组合。明确对照的版本包括 2.13.5,不能只独立降级 core,也不应忽略其他库对 Pydantic 最低版本的约束。
另一种设计是使用明确指向递归节点类型的前向注解,避免依赖这个位置的 Self 替换。但 Self 带有“当前派生类”的含义,固定指向 Node[T] 在继承场景里未必等价。需要检查类型参数如何传给父节点、派生节点如何验证,以及是否产生了新的命名空间解析问题,再决定是否改模型声明。
不要直接给安装目录中的私有 _generate_schema.py 改一行来长期维持应用。即使局部调整 origin 能让这组结构通过,也不能证明它适用于所有 schema 缓存、递归引用与泛型分支。后续版本是否包含修补,要对照正式发行内容与原结构验证,不能把建议代码视作已经发布的修复。
验收既要建立参数化类型,也要验证正常嵌套节点、父节点为 None、非法的内部 value 以及实际用到的子类。通过建 schema 只是第一步;内部非法字符串不应因为绕开故障而悄悄当成合法整数。


