Pydantic 2.14 冻结时间后报 FakeDatetime schema 错误
freezegun 会替换 datetime 类型,Pydantic 2.14 的处理表按导入时类型匹配。尽量在冻结前构建模型与 TypeAdapter;延迟导入的第三方模型也需纳入检查。
测试一进入 freeze_time(),还没执行模型验证,就报:
PydanticSchemaGenerationError: Unable to generate pydantic-core schema for <class 'freezegun.api.FakeDatetime'>
如果错误出现在 Pydantic 2.14.0 或 2.14.1,且模型、TypeAdapter 或 model_rebuild() 在冻结时间期间建立,先检查 freezegun 替换 datetime.datetime 的时机。
Pydantic 2.14 的 schema 处理表在导入时保存了类型键。freezegun 随后把模块里的 datetime.datetime 替换为 FakeDatetime,新注解指向的是另一个类,无法按原键命中。2.13.5 的相关匹配路径在构建时读取模块属性,因此同一结构曾经能通过;不能据此认为 2.14 中只需重建模型就会恢复。
能调整构建时机时,在冻结前准备 schema
模型定义和类型适配器都可以提前完成的测试,先在冻结上下文之外建立它们,冻结期间只创建或验证值:
import datetime
from freezegun import freeze_time
from pydantic import BaseModel, TypeAdapter
class Event(BaseModel):
happened_at: datetime.datetime
adapter = TypeAdapter(datetime.datetime)
with freeze_time("2026-01-01"):
now = datetime.datetime.now()
event = Event(happened_at=now)
parsed = adapter.validate_python(now)
这个调整的前提是 schema 确实已经建立。若项目启用了延迟构建、在上下文中重新定义类,或又调用 model_rebuild(force=True),仍可能在冻结期间回到类型解析入口。应检查真正失败的类定义与调用位置,而不只把测试函数外观移到另一个文件。
进入上下文就失败,也要查延迟导入
freezegun 启动时会扫描已加载模块的属性。带有模块级 __getattr__ 的包可能在这个过程中延迟加载子模块,而子模块刚好定义 Pydantic 模型。因此,测试没有直接使用模型,也可能在进入冻结上下文时抛错。
可以在冻结前显式导入测试确实依赖的模块,让其中的模型在真实类型环境下建立;但只预先创建自己的一两个模型,不足以覆盖第三方 SDK 后续加载的所有类型。若库仍在运行期动态建模,需要从冻结工具兼容性或依赖组合上处理。
暂时必须保留原测试机制时,可评估恢复已验证的 2.13.5 配套依赖,作为有期限的兼容措施;应由锁文件和项目依赖约束决定,不能单独硬换 pydantic-core。另一条路径是采用不通过相同方式替换类型的时间控制工具,但其时区、时间推进与受影响时钟范围应按原测试重新核对。
不要用 arbitrary_types_allowed=True 把 FakeDatetime 简单放行来冒充原有日期验证;那可能改变字符串解析、schema 与序列化契约。也不要给 Pydantic 私有 _handlers 写补丁,让整个测试进程依赖全局类型表污染。
完成后检查冻结前定义的模型、上下文内的验证、延迟导入以及项目实际的日期序列化。测试结束还应确认真实时钟恢复,不能只凭某个模型不再报错就认定时间控制与依赖升级都兼容。


