jsonschema+裝飾器 實現(xiàn)更簡單的python參數校驗

作為一個后端開發(fā)人員砍艾,永遠不要相信你的用戶輸入,也不要相信自己~所以巍举,參數校驗是一個非常重要的環(huán)節(jié)脆荷,千萬千萬不要忽視。
最近也涉及到很多需要嚴格參數校驗的接口開發(fā)工作懊悯,之前使用過很多方式進行參數校驗蜓谋,主要有以下兩種:

  • 在接口內部直接依次if else校驗,這種只針對比較簡單單一的接口炭分;
  • 自己針對不同參數校驗需求桃焕,定制的參數校驗方法,在相應接口內調用捧毛。但是寫多了會發(fā)現(xiàn)重復代碼較多观堂;
  • 使用marshmallow的序列化類進行校驗,使用較為方便和清晰呀忧,但是他主要是針對模型類序列化使用的师痕,我大多時候只是校驗json參數而已。

然后了解到了今天寫的主角:jsonschema庫而账,專為json數據校驗而生胰坟,更加的靈活,使用字典配置化的方式定義校驗泞辐。

一笔横、 安裝方式: pip install jsonschema

若不想自己定義schema,可在https://jsonschema.net/home輸入示例json在線生成schema哦铛碑!

二狠裹、官方簡單示例

這里先直接上一個官方的示例代碼,了解下jsonschema的使用方式:

>>> from jsonschema import validate

>>> # A sample schema, like what we'd get from json.load()
>>> schema = {
...     "type" : "object",
...     "properties" : {
...         "price" : {"type" : "number"},
...         "name" : {"type" : "string"},
...     },
... }

>>> # If no exception is raised by validate(), the instance is valid.
>>> validate(instance={"name" : "Eggs", "price" : 34.99}, schema=schema)

>>> validate(
...     instance={"name" : "Eggs", "price" : "Invalid"}, schema=schema,
... )                                   # doctest: +IGNORE_EXCEPTION_DETAIL
Traceback (most recent call last):
    ...
ValidationError: 'Invalid' is not of type 'number'

這里再做個簡單名詞解釋:

  • schema變量就是定義的具體參數校驗配置汽烦;
  • type指定該層級參數類型涛菠,object為json對象,array為數組撇吞,number為數字包含小數俗冻,string為字符串,integer為整數...
  • properties指定該層級具體字段名及屬性限制牍颈;
  • required指定該層級必須的字段名迄薄,未在其中的字段可缺失;
  • validate方法即調用參數驗證的方式煮岁,指定instance待校驗的json對象讥蔽,schema指定使用的校驗配置涣易;
    ...

下面將針對兩個實例做使用代碼展示。

三冶伞、需求實例

準備了兩個實例進行學習新症,明白后基本能夠學會該校驗方式的基本使用。

  1. 需求一:
    需要對下列結構的用戶參數進行校驗响禽,user_id為大于0的整數徒爹;user_name為字符串;age為一定范圍內的整數芋类;other字段為嵌套json的非必須字段隆嗅,但若other字段存在,則hobby字段為字符串且必須侯繁,height字段為數值型非必須胖喳。
{
      "user_id": 123456,
      "user_name": "John",
      "age": 12,
      "other": {
          "hobby": "swimming",
          "height": 170.3
        }
}
  1. 需求二:需要對下列結構的公司參數進行校驗,model字段為字符串巫击;count字段為不小于0的整數禀晓;data為數組結構,內嵌json數據坝锰,內嵌字段company_name與cust_uid不可缺失,value值為字符串重付,不為空且小于一定長度顷级。
{
      "model": "st",
      "count": 4,
      "data": [
          {"company_name": "測試公司1", "cust_uid": "asd4a676762jjhj"},
          {"company_name": "測試公司2", "cust_uid": "andfu58jkskjds3"}
        ]
}

四、代碼結構

為了增加代碼的復用性和可讀性确垫,我將模擬項目內代碼規(guī)范弓颈,使用分層代碼結構+裝飾器進行參數校驗功能實現(xiàn),代碼結構如下:


代碼結構
  • app.py:模擬接口文件删掀,其內包含接收json參數且需要進行參數校驗的接口函數翔冀;
  • decorators.py:用于存放項目內的裝飾器函數,這里只有一個參數校驗裝飾器披泪;
  • schema.py:定義各類參數校驗的配置字典纤子,也應該算是使用jsonschema庫的核心

五、代碼示例

以下開始直接分享針對兩個需求的參數校驗代碼款票,一定注意各個字段名含義及層級關系控硼,可對應上文中的需求介紹來了解每行是在做什么。

  1. schema.py
# 需求1的用戶校驗schema字典定義
schema_user = {
    "type": "object",
    "required": ["user_id", "user_name", "age"],
    "properties": {
        "user_id": {
            "type": "integer",
            "minimum": 1
        },
        "user_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 20
        },
        "age": {
            "type": "integer",
            "minimum": 1,
            "maximum": 120
        },
        "other": {
            "type": "object",
            "required": ["hobby"],
            "properties": {
                "hobby": {"type": "string"},
                "height": {"type": "number"}
            }
        }
    }
}

# 需求2的公司校驗schema字典定義
schema_company = {
    "type": "object",
    "required": ["data", "count"],
    "properties": {
        "model": {
            "type": "string"
        },
        "count": {
            "type": "integer",
            "minimum": 1
        },
        "data": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["company_name", "cust_uid"],
                "properties": {
                    "company_name": {
                        "type": "string",
                        "minLength": 1
                    },
                    "cust_uid": {
                        "type": "string",
                        "minLength": 10,
                        "maxLength": 100
                    }
                }
            }
        }
    }
}

  1. decorators.py
    該裝飾器接收一個schema參數艾少,即上面定義的shema校驗字典對象卡乾,可針對接收data參數的接口或方法進行data參數校驗,校驗通過再返回data數據缚够,否則打印或拋出異常參數信息幔妨, 我這里為了演示沒有拋出異常鹦赎,只是做了打印處理。
from jsonschema import validate, ValidationError


# data參數校驗裝飾器误堡,可指定不同的校驗schema
def json_validate(schema):
    def wrapper(func):
        def inner(data, *args, **kwargs):
            try:
                validate(data, schema)
            except ValidationError as e:
                print("參數校驗失敻奇ⅰ:{}!".format(e.message))
            else:
                print("參數校驗通過!")
                return func(data, *args, **kwargs)
        return inner
    return wrapper
  1. app.py
    用戶數據處理接口及公司數據處理接口埂伦,需要接收原始用戶或公司的json數據參數煞额,這里導入上面定義的裝飾器和schema字典實現(xiàn)簡潔的參數校驗功能。
from decorators import json_validate
from schema import schema_user, schema_company


@json_validate(schema=schema_company)
def company_api(data):
    # 公司數據操作接口示例
    print("company_api執(zhí)行入庫操作沾谜!")
    return data


@json_validate(schema=schema_user)
def user_api(data):
    # 用戶數據操作接口示例
    print("use_api執(zhí)行入庫操作膊毁!")
    return data

六. 測試校驗

app.py下進行測試校驗輸出:

  1. 正確的參數測試:
if __name__ == '__main__':

    company_dict = {
        "model": "st",
        "count": 4,
        "data": [
            {"company_name": "測試公司1", "cust_uid": "asd4a676762jjhj"},
            {"company_name": "測試公司2", "cust_uid": "andfu58jkskjds3"}
        ]
    }
    company_api(company_dict)

    user_dict = {
        "user_id": 123456,
        "user_name": "John",
        "age": 12,
        "other": {
            "hobby": "swimming",
            "height": 170.3
        }
    }

    user_api(user_dict)

輸出:

參數校驗通過!
company_api執(zhí)行入庫操作基跑!
參數校驗通過婚温!
use_api執(zhí)行入庫操作!
  1. 錯誤的用戶參數輸入測試:
  • 用戶年齡輸入:0媳否,小于最小值限制
if __name__ == '__main__':
    user_dict = {
        "user_id": 123456,
        "user_name": "John",
        "age": 0,
        "other": {
            "hobby": "swimming",
            "height": 170.3
        }
    }

    user_api(user_dict)

輸出:

參數校驗失斦っ:0 is less than the minimum of 1!
  • 內嵌字典不傳hobby參數
if __name__ == '__main__':
    user_dict = {
        "user_id": 123456,
        "user_name": "John",
        "age": 12,
        "other": {
            "height": 170.3
        }
    }

    user_api(user_dict)

輸出:

參數校驗失敗:'hobby' is a required property!
  1. 錯誤的公司參數輸入測試:
  • 內嵌字典參數cust_uid輸入空字符串:''
if __name__ == '__main__':

    company_dict = {
        "model": "st",
        "count": 4,
        "data": [
            {"company_name": "測試公司1", "cust_uid": ""},
            {"company_name": "測試公司2", "cust_uid": "andfu58jkskjds3"}
        ]
    }
    company_api(company_dict)

輸出:

參數校驗失斃榻摺:'' is too short!
  • count輸入小數:3.1
if __name__ == '__main__':

    company_dict = {
        "model": "st",
        "count": 3.1,
        "data": [
            {"company_name": "測試公司1", "cust_uid": "asd"},
            {"company_name": "測試公司2", "cust_uid": "andfu58jkskjds3"}
        ]
    }
    company_api(company_dict)

輸出:

參數校驗失斄ν肌:3.1 is not of type 'integer'!

以上,就不再一一測試每種情況了掺逼。

七吃媒、 總結

jsonschema使用類似配置字典的方式來定義參數校驗,使用起來十分的清晰吕喘,理解起來也比較簡單赘那,后期更改也更容易。且報錯信息也比較清晰氯质,一般無需再定制化錯誤信息募舟,個人目前更加喜歡這個方式來進行json參數的校驗。

本篇文章還使用了裝飾器的方式對需要驗參的方法進行裝飾闻察,復用性更強拱礁,且使主體邏輯更加簡潔,還是那句話蜓陌,裝飾器是個好東西觅彰,jsonschema也是好東西,都要盡量用起來才是真的啊~

本文對jsonschema更細節(jié)的內容未做介紹钮热,又更深的需求情參考官方文檔進行對照學習填抬,希望對你有幫助。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末隧期,一起剝皮案震驚了整個濱河市飒责,隨后出現(xiàn)的幾起案子赘娄,更是在濱河造成了極大的恐慌,老刑警劉巖宏蛉,帶你破解...
    沈念sama閱讀 221,331評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件遣臼,死亡現(xiàn)場離奇詭異,居然都是意外死亡拾并,警方通過查閱死者的電腦和手機揍堰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,372評論 3 398
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來嗅义,“玉大人屏歹,你說我怎么就攤上這事≈耄” “怎么了蝙眶?”我有些...
    開封第一講書人閱讀 167,755評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長褪那。 經常有香客問我幽纷,道長,這世上最難降的妖魔是什么博敬? 我笑而不...
    開封第一講書人閱讀 59,528評論 1 296
  • 正文 為了忘掉前任友浸,我火速辦了婚禮,結果婚禮上冶忱,老公的妹妹穿的比我還像新娘尾菇。我一直安慰自己,他們只是感情好囚枪,可當我...
    茶點故事閱讀 68,526評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著劳淆,像睡著了一般链沼。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上沛鸵,一...
    開封第一講書人閱讀 52,166評論 1 308
  • 那天括勺,我揣著相機與錄音,去河邊找鬼曲掰。 笑死疾捍,一個胖子當著我的面吹牛,可吹牛的內容都是我干的栏妖。 我是一名探鬼主播乱豆,決...
    沈念sama閱讀 40,768評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼吊趾!你這毒婦竟也來了宛裕?” 一聲冷哼從身側響起瑟啃,我...
    開封第一講書人閱讀 39,664評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎揩尸,沒想到半個月后蛹屿,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 46,205評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡岩榆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,290評論 3 340
  • 正文 我和宋清朗相戀三年错负,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勇边。...
    茶點故事閱讀 40,435評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡犹撒,死狀恐怖,靈堂內的尸體忽然破棺而出粥诫,到底是詐尸還是另有隱情油航,我是刑警寧澤,帶...
    沈念sama閱讀 36,126評論 5 349
  • 正文 年R本政府宣布怀浆,位于F島的核電站谊囚,受9級特大地震影響,放射性物質發(fā)生泄漏执赡。R本人自食惡果不足惜镰踏,卻給世界環(huán)境...
    茶點故事閱讀 41,804評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望沙合。 院中可真熱鬧奠伪,春花似錦、人聲如沸首懈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,276評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽究履。三九已至滤否,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間最仑,已是汗流浹背藐俺。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泥彤,地道東北人欲芹。 一個月前我還...
    沈念sama閱讀 48,818評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像吟吝,于是被迫代替她去往敵國和親菱父。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,442評論 2 359