pydantic进阶用法示例详解
作者:it_miclon 发布时间:2022-01-31 07:00:03
pydantic
是一个Python的数据验证和转换库,它的特点是轻量、快速、可扩展、可配置。笔者常用的用于数据接口schema定义与检查。
具体的基本用法本文不再做过多的介绍,可以参考pydantic官方文档。本文主要是结合实际项目开发中遇到的问题和解题思路,介绍一些pydantic
的高阶玩法。
当前现状
在项目中,pydantic
的定义是在数据的出口进行规范化,从而使得下游接受方能更快地去解析和清洗这些数据。
from pydantic import BaseModel, Field
# 定义数据模型
class Project(BaseModel):
url: str = Field(...)
title: str = Field(...)
content: str = Field(...)
company: List[Dict] = Field(default=[])
industry: str = Field(...)
以上是简单的一个数据模型定义,代码仅为示例,隐去了一些字段和配置。也就是我们必须传输给Project
模型对应的数据才可以通过它的数据校验,否则就无法继续向下(可能是发往下游)
这么做一直以来没什么问题,直到本次项目中的接口返回出现了大更新,使得之前的所有代码层做的数据字段映射必须重新对应匹配。
比如之前title
字段对应的是title
,现在变成了detail
-article
-title
。
这使得我们必须在代码层做诸如:
# project_data均为接口返回的数据,加数据演示
# 之前的代码
project_data = {
"url": "https://www.baidu.com",
"title": "百度一下,你就知道",
}
project = Project(
**project_data
)
# 现在的代码
project_data = {
"detail": {
"url": "xxx"
"article": {
"title": "项目标题",
}
}
}
project = Project(
url=project_data["detail"]["url"],
title=project_data["detail"]["article"]["title"],
)
以上代码取值变得复杂,这还没考虑到数据可能存在出错的问题,比如detail
字段不存在,这样就会导致KeyError
异常。
而且这并不是夸张的举例(因为事实情况更复杂)。
我怎么能容忍这种情况呢?
解决方案
我当然不是想摒弃掉pydantic
,而是想找到一种结合它更优雅的方式来解决这个问题。
于是我第一时间想到了jmespath
模块,因为它是一个JSON查询语言,可以用来在JSON数据中查找和提取数据。
from jmespath import search
project_data = {
"detail": {
"article": {
"title": "项目标题",
}
}
}
title = search("detail.article.title", project_data)
assert title == "项目标题" # True
# 即使是path不存在,也不会异常,而是返回None
assert search("detail.article.title1", project_data) is None # True
所以我打算做一个结合pydantic
和jmespath
的方式来解决这个问题。
class Project(BaseModel):
url: str = Field(...)
title: str = Field(...)
content: str = Field(...)
company: List[Dict] = Field(default=[])
industry: str = Field(...)
@root_validator(pre=True, skip_on_failure=True)
def data_converter(cls, v):
return {
"url": search("detail.id", v),
"title": search("detail.article.title", v),
"content": search("detail.article.content", v),
"company": search("company[*].name", v),
"industry": search("industry", v)
}
@validator("url")
def url_validator(cls, v):
# 由于这里的v是拿到的ID,需要组合成url
return f"https://xxxxx/{v}"
从代码中可以知道,我是在root_validator
中提前做了数据的转换,将jmespath
的查询结果赋值给对应的字段。
但是做完之后我越看越变扭,我为了做这个事情,先要申明所有字段,还要对这些字段一一映射。
于是,我想到了pydantic
的Config
类,它可以用来配置pydantic
的一些行为。而且通过查看源码,我认为我可以通过Field
类中输入一个path变量,告诉未来的处理器,这个path是用来做数据提取的。
class Project(BaseModel):
url: str = Field(..., path="temporaryLibrary.id")
company_names: str = Field(..., path="company[0].enterprise.name")
versions: List[str] = Field(..., path="versionList[*].id")
当然现在代码是没有任何意义的,因为path
是我们自定义的,pydantic
并不知道如何处理它。
所以下一步我们要做的是,如何更好的让pydantic
知道如何处理path
。
在多次翻阅它源代码,并结合官方文档中对Model类的介绍,我找到了一个可行的方案。
Pydantic models can be created from arbitrary class instances to support models that map to ORM objects.
也就是说,我可以将原始数据通过from_orm
传递给pydantic
的模型,然后通过Data binding
的方式,将数据绑定到模型中。Data binding
允许我们自定义数据的取值来源。
class ProjectGetter(GetterDict):
def get(self, key: str, default: Any) -> Any: # noqa
# 由于getter_dict所能拿到的“数据权限”相对较低,
# 也就是它的权限仅仅是处理数据,而不是处理模型,
# 所以我们需要自己去拿到模型,然后再去拿到path
model, data = self._obj['model'], self._obj['data']
for name, field in model.__fields__.items():
path = field.field_info.extra.get('path')
if path and name == key:
return search(path, data)
return default
class Project(BaseModel):
url: str = Field(..., path="detail.id")
company_names: str = Field(..., path="company[0].enterprise.name")
versions: List[str] = Field(..., path="versionList[*].id")
@validator("url")
def url_validator(cls, v):
return f"https://www.baidu.com/{v}"
class Config:
# 通过orm_mode指定数据的来源
orm_mode = True
# 通过getter_dict指定数据的获取方式
getter_dict = ProjectGetter
project_data = {
"detail": {
"id": 1,
"article": {
"title": "项目标题",
}
},
"company": [
{
"enterprise": {
"name": "企业名称1"
}
},
{
"enterprise": {
"name": "企业名称2"
}
}
],
"versionList": [{"id": "1.0"}, {"id": "2.0"}]
}
project = Project.from_orm({"model": Project, "data": project_data})
print(project)
# url='https://www.baidu.com/1' company_names='企业名称1' versions=['1.0', '2.0']
这样我们在业务端,只需要对Field指定其对应数据提取的path
,而不需要再去写一堆的validator
或者是在数据进入前做一堆的数据转换。
来源:https://juejin.cn/post/7210028235623055415


猜你喜欢
- 时钟实现实现这个时钟时间需要解决以下三个问题:获得当前时间,并格式化如何可以在页面中显示时间让时间动起来1、获得当前时间,并格式化要获得当前
- 今天来研究python中moviepy模块的用途近来有大量处理视频的需求,常会碰到一个问题是下载的视频音量过小,会需要将它调大声,虽然有在线
- 背景:项目中有多个组件调用同一接口,为提高代码可维护性,需要封装公共方法直接return 接口调用的结果export function ge
- 前言在Python中元组是一个相较于其他语言比较特别的一个内置序列类型。有些python入门教程把元组成为“不可变的列表”,这种说法是不完备
- 故事还得从下面的图说起:what? 两条sql执行结果的id列居然不一致。。。。。。一、LIMIT 处理过程为了故事的顺利发展,我们得先创建
- 摘要: Portal是IT领域的新技术,是企业信息化工作的发展方向之一。本文首先介绍了Oracle Portal的定义、特点,接着阐述了po
- 这篇文章主要介绍了python正则表达式匹配IP代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的
- 本文实例讲述了PHP实现按之字形顺序打印二叉树的方法。分享给大家供大家参考,具体如下:问题请实现一个函数按照之字形打印二叉树,即第一行按照从
- 求一个算式a=1b=2c=3 print c*(a/b)运行结果总是0,反复检查拆开以后,发现在Python里,整数初整数,只能得
- vue中代码的复用, 为我们提供了 mixnis. 模板的复用, 为我们提供了 插槽( slot )插槽的分类默认插槽具名插槽作用域插槽当我
- 误区 #30:有关备份的30个误区全是错的在开始有关备份的误区之前,如果你对备份的基础没有了解,请看之前我在TechNet Magazine
- 关于python3中的追加写入excel问题,这个问题坑了我几小时,其实加一个参数即可。因为之前有写好的excel,想追加写入,但是写入后却
- redux-saga在学习它之前先了解es6生成器生成器关键字:yield next()定义函数需要在函数名前急+*号function *t
- 前言索引对有一定开发经验的同学来说并不陌生,合理使用索引,能大大提升sql查询的性能,可以这么讲,随着业务数据量的不断增长,优化系统的响应速
- 1.列表:list# 1.list:Python内置的一种数据类型,列表;# 2.list是一种有序的集合,可以随时添加和删除其中的元素;#
- 目录什么是虚拟 dom?为什么需要虚拟dom?虚拟dom是如何转换为真实dom的?模板和虚拟dom的关系注入挂载完整流程总结什么是虚拟 do
- 异常:python3的xadmin主题只显示默认和bootstrap2解决办法:慢慢来相信能遇到这个问题的人,都是已经配置好xadmin的,
- 5月20日,微软正式提供了Windows XP下可用的雅黑字体下载,雅黑字体是一款近乎完美的字体,解决了宋体小文字无法辩认的问
- 下面继续为大家带来XHTML与HTML兼容的16条指引!1.避免将页面声明为XML类型,页面使用UTF-8或者UTF-16字符集。2.在空元
- Python encode()方法encode() 方法为字符串类型(str)提供的方法,用于将 str 类型转换成 bytes 类型,这个