pydantic進(jìn)階用法示例詳解
正文
pydantic
是一個Python的數(shù)據(jù)驗(yàn)證和轉(zhuǎn)換庫,它的特點(diǎn)是輕量、快速、可擴(kuò)展、可配置。筆者常用的用于數(shù)據(jù)接口schema定義與檢查。
具體的基本用法本文不再做過多的介紹,可以參考pydantic官方文檔。本文主要是結(jié)合實(shí)際項(xiàng)目開發(fā)中遇到的問題和解題思路,介紹一些pydantic
的高階玩法。
當(dāng)前現(xiàn)狀
在項(xiàng)目中,pydantic
的定義是在數(shù)據(jù)的出口進(jìn)行規(guī)范化,從而使得下游接受方能更快地去解析和清洗這些數(shù)據(jù)。
from pydantic import BaseModel, Field # 定義數(shù)據(jù)模型 class Project(BaseModel): url: str = Field(...) title: str = Field(...) content: str = Field(...) company: List[Dict] = Field(default=[]) industry: str = Field(...)
以上是簡單的一個數(shù)據(jù)模型定義,代碼僅為示例,隱去了一些字段和配置。也就是我們必須傳輸給Project
模型對應(yīng)的數(shù)據(jù)才可以通過它的數(shù)據(jù)校驗(yàn),否則就無法繼續(xù)向下(可能是發(fā)往下游)
這么做一直以來沒什么問題,直到本次項(xiàng)目中的接口返回出現(xiàn)了大更新,使得之前的所有代碼層做的數(shù)據(jù)字段映射必須重新對應(yīng)匹配。
比如之前title
字段對應(yīng)的是title
,現(xiàn)在變成了detail
-article
-title
。
這使得我們必須在代碼層做諸如:
# project_data均為接口返回的數(shù)據(jù),加數(shù)據(jù)演示 # 之前的代碼 project_data = { "url": "https://www.baidu.com", "title": "百度一下,你就知道", } project = Project( **project_data ) # 現(xiàn)在的代碼 project_data = { "detail": { "url": "xxx" "article": { "title": "項(xiàng)目標(biāo)題", } } } project = Project( url=project_data["detail"]["url"], title=project_data["detail"]["article"]["title"], )
以上代碼取值變得復(fù)雜,這還沒考慮到數(shù)據(jù)可能存在出錯的問題,比如detail
字段不存在,這樣就會導(dǎo)致KeyError
異常。
而且這并不是夸張的舉例(因?yàn)槭聦?shí)情況更復(fù)雜)。
我怎么能容忍這種情況呢?
解決方案
我當(dāng)然不是想摒棄掉pydantic
,而是想找到一種結(jié)合它更優(yōu)雅的方式來解決這個問題。
于是我第一時間想到了jmespath
模塊,因?yàn)樗且粋€JSON查詢語言,可以用來在JSON數(shù)據(jù)中查找和提取數(shù)據(jù)。
from jmespath import search project_data = { "detail": { "article": { "title": "項(xiàng)目標(biāo)題", } } } title = search("detail.article.title", project_data) assert title == "項(xiàng)目標(biāo)題" # True # 即使是path不存在,也不會異常,而是返回None assert search("detail.article.title1", project_data) is None # True
所以我打算做一個結(jié)合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
中提前做了數(shù)據(jù)的轉(zhuǎn)換,將jmespath
的查詢結(jié)果賦值給對應(yīng)的字段。
但是做完之后我越看越變扭,我為了做這個事情,先要申明所有字段,還要對這些字段一一映射。
于是,我想到了pydantic
的Config
類,它可以用來配置pydantic
的一些行為。而且通過查看源碼,我認(rèn)為我可以通過Field
類中輸入一個path變量,告訴未來的處理器,這個path是用來做數(shù)據(jù)提取的。
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")
當(dāng)然現(xiàn)在代碼是沒有任何意義的,因?yàn)?code>path是我們自定義的,pydantic
并不知道如何處理它。
所以下一步我們要做的是,如何更好的讓pydantic
知道如何處理path
。
在多次翻閱它源代碼,并結(jié)合官方文檔中對Model類的介紹,我找到了一個可行的方案。
Pydantic models can be created from arbitrary class instances to support models that map to ORM objects.
也就是說,我可以將原始數(shù)據(jù)通過from_orm
傳遞給pydantic
的模型,然后通過Data binding
的方式,將數(shù)據(jù)綁定到模型中。Data binding
允許我們自定義數(shù)據(jù)的取值來源。
class ProjectGetter(GetterDict): def get(self, key: str, default: Any) -> Any: # noqa # 由于getter_dict所能拿到的“數(shù)據(jù)權(quán)限”相對較低, # 也就是它的權(quán)限僅僅是處理數(shù)據(jù),而不是處理模型, # 所以我們需要自己去拿到模型,然后再去拿到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指定數(shù)據(jù)的來源 orm_mode = True # 通過getter_dict指定數(shù)據(jù)的獲取方式 getter_dict = ProjectGetter project_data = { "detail": { "id": 1, "article": { "title": "項(xiàng)目標(biāo)題", } }, "company": [ { "enterprise": { "name": "企業(yè)名稱1" } }, { "enterprise": { "name": "企業(yè)名稱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='企業(yè)名稱1' versions=['1.0', '2.0']
這樣我們在業(yè)務(wù)端,只需要對Field指定其對應(yīng)數(shù)據(jù)提取的path
,而不需要再去寫一堆的validator
或者是在數(shù)據(jù)進(jìn)入前做一堆的數(shù)據(jù)轉(zhuǎn)換。
總結(jié)
通過這個小例子,我們可以看到,pydantic
的靈活性是非常強(qiáng)的,它可以通過Config
類來配置一些行為,而且它的Field
類也可以通過extra
參數(shù)來傳遞一些額外的信息。這大大的提高了我們的對數(shù)據(jù)的處理能力。筆者也會在后續(xù)的文章中,繼續(xù)分享pydantic
的一些使用技巧。
以上就是pydantic進(jìn)階用法示例詳解的詳細(xì)內(nèi)容,更多關(guān)于pydantic進(jìn)階用法的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Python3創(chuàng)建Django項(xiàng)目的幾種方法(3種)
這篇文章主要介紹了Python3創(chuàng)建Django項(xiàng)目的幾種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-06-06Python實(shí)現(xiàn)語音識別和語音合成功能
聲音的本質(zhì)是震動,震動的本質(zhì)是位移關(guān)于時間的函數(shù),波形文件(.wav)中記錄了不同采樣時刻的位移。這篇文章主要介紹了Python實(shí)現(xiàn)語音識別和語音合成,需要的朋友可以參考下2019-09-09python爬蟲實(shí)戰(zhàn)之爬取京東商城實(shí)例教程
這篇文章主要介紹了python爬取京東商城的相關(guān)資料,文中通過爬取一個實(shí)例頁面進(jìn)行了講解,通過示例代碼和圖文介紹的非常詳細(xì),相信對大家具有一定的參考價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧。2017-04-04Python Print實(shí)現(xiàn)在輸出中插入變量的例子
今天小編就為大家分享一篇Python Print實(shí)現(xiàn)在輸出中插入變量的例子,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-12-12python3+PyQt5 自定義窗口部件--使用窗口部件樣式表的方法
今天小編就為大家分享一篇python3+PyQt5 自定義窗口部件--使用窗口部件樣式表的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2019-06-06python 轉(zhuǎn)換 Javascript %u 字符串為python unicode的代碼
這篇文章主要介紹了python 轉(zhuǎn)換 Javascript %u 字符串為python unicode的代碼,需要的朋友可以參考下2016-09-09如何優(yōu)雅地改進(jìn)Django中的模板碎片緩存詳解
這篇文章主要給大家介紹了關(guān)于如何優(yōu)雅地改進(jìn)Django中的模板碎片緩存的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-07-07用django-allauth實(shí)現(xiàn)第三方登錄的示例代碼
這篇文章主要介紹了用django-allauth實(shí)現(xiàn)第三方登錄的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2019-06-06