升级pydantic遇到的问题
近期打算给某个项目所依赖的pydantic从1.x升级到2.x,之前通过查阅文档发现由于pydantic的底层用rust重写,导致很多API出现了不兼容问题,因此没有办法直接升级,需要将之前所有的数据模型修改一下,以下是我升级pydantic时对现有的数据模型类的重构过程
1、因为我定义的数据模型本身不算特别复杂,再加上pydantic官方也提供了bump-pydantic的模块,这也为迁移提供了一些方便。
安装好这个模块后只需要运行bump-pydantic model.py即可,此时大部分的数据模型已经重构好了
2、修复部分无法被重构的校验方法
经过第1步后,有两个地方会出现问题导致无法正常运行
class ProfileModel(BaseModel):
profile: str
config: ConfigModel
@validator("profile")
def profile_validator(cls, value):
if not value:
raise ValueError("profile不能为空")
return value
def custom_json_dumps(v: dict, *, default):
return json.dumps(
v.get("profile_list"), default=default, indent=4, ensure_ascii=False
)
问题1解决思路¶
对于问题1,通过参考pydantic教程发现,2.x在对每个具体的校验器方法上的写法发生了变化,尤其是涉及到全局检查的情况。因此问题1可以通过下面的代码去重构
class ConfigModel(BaseModel):
...
@model_validator(mode="after")
def pre_validator(self):
match self.agent:
case "SSH":
if self.SSH:
return self
else:
raise ValueError("agent字段不匹配")
case _:
return self
关于mode的选择
pydantic提供了三种校验,分别为before/after/wrap,我目前只知道before/after的一点区别,一个是在所有字段之前校验,一个在所有字段校验后校验,不过在以上的代码逻辑中,before和after的逻辑差别不是太大,所以就采用了after模式
另外发现一个细节,就是如果设置为after,那么在pycharm中得到的提示是def pre_validator(self):,但如果改成了before,会提示为def pre_validator(cls)
问题2解决思路¶
问题1还算好解决一些,但是问题2却卡了我很久,一直也没有找到好的解决办法,而且通过代码提示发现.json()方法已经标记为removed,但是pydantic 2.7文档中还有不少用.json()的方法,给代码重构带来了不少的阻碍
后来在pydantic技术文档中去找类似于序列化的一些示例代码,最后在某个犄角旮旯找到了这个参考示例1
from typing import Literal
from pydantic import BaseModel, model_serializer
class TemperatureModel(BaseModel):
unit: Literal['C', 'F']
value: int
@model_serializer()
def serialize_model(self):
if self.unit == 'F':
return {'unit': 'C', 'value': int((self.value - 32) / 1.8)}
return {'unit': self.unit, 'value': self.value}
temperature = TemperatureModel(unit='F', value=212)
print(temperature.model_dump())
#> {'unit': 'C', 'value': 100}
顺着这个思路去改写问题2,问题2的代码就重构成下面的效果
class ProfileListModel(BaseModel):
profile_list: list[ProfileModel]
@model_serializer
def serializer(self) -> list:
return self.profile_list
同时通过ProfileListModel的数据的代码也需要相应的重写
通过测试发现新的model_dump_json()方法解决了部分非ascii码的编码问题,因此不需要单独去指定ensure_ascii=False(其实也没有办法去指定)
通过查找资料发现新版的pydantic还提供了.model_validate_json(),可以不用像之前那样先手动读取json文件再转换成dict/list类型,然后再传入数据模型中,这就简化了一些操作。当然,实际测试中发现这个也不是100%适配的,比如有些特殊数据类型就不能这么做了,比如DateTime等类型,要么从重写序列化方法入手,要么就还是像之前那样写成model=PorfileModel(**data)的形式
总结¶
pydantic作为校验数据的库,总体用着还是挺满意的,不过2.x的中文资料实在太少,大部分还是停留在1.x,而且2.x的文档还有一些没有完善好的地方,还会遇到一些坑。如果觉着中文资料太少,并且2.x带来的性能提升对你来说没有太大的影响,用1.x也是可以的,也能避免走一些不必要的坑
-
原来找的参考示例一时半会找不到了,不过这个和当时找的效果差不太多 ↩