簡潔的 Python Schema


Python Schema使用說明

項目地址:GitHub

1. Schema是什么?

不管我們做什么應用,只要和用戶輸入打交道,就有一個原則--永遠不要相信用戶的輸入數據。意味着我們要對用戶輸入進行嚴格的驗證,web開發時一般輸入數據都以JSON形式發送到后端API,API要對輸入數據做驗證。一般我都是加很多判斷,各種if,導致代碼很丑陋,能不能有一種方式比較優雅的驗證用戶數據呢?Schema就派上用場了。

Schema非常簡單,也就幾百行的代碼,最核心的類就一個:Schema。

2. 安裝

pip install schema

1. 給Schema類傳入類型(int、str、float等)

例如:

from schema import Schema

Schema(int).validate(10)
# 10
Schema(int).validate('10')
# SchemaUnexpectedTypeError: '10' should be instance of 'int'

可見Schema會去驗證validate方法傳入的對象是不是所指定的類型,是則返回傳入的數據,否則拋出一個SchemaError的異常(SchemaUnexpectedTypeError是SchemaError的子類)。

2. 給Schema類傳入可調用的對象(函數、帶__call__的類等)

例如:

Schema(lambda x: 0<x<10).validate(5)
# 5
Schema(lambda x: 0<x<10).validate(57)
# SchemaError: <lambda>(57) should evaluate to True

自定義函數:

def secre_validate(password):
    password_list = ["123456", "666666", "888888", "abcdef", "aaaaaa", "112233"]
    if password in password_list:
        return False
    return True

Schema(secre_validate,  error="字符串已經存在").validate("123456")

# schema.SchemaError: 字符串已經存在

可見Schema會把validate方法傳入的值傳入到對應的函數里面作為參數,如果函數返回值為True則返回輸入數據,否則拋出異常。

3. 給Schema類傳入帶有validate方法的對象

Schema也內置了一些類(Use、And、Or等等),這些類的實例都帶有validate方法,亦可作為Schema的參數傳入,例如:

from schema import Schema, And

# And代表兩個條件必須同時滿足
Schema(And(str, lambda s: len(s) > 2)).validate('abcd')
# 'abcd'

4. 給Schema類傳入容器對象(list、tuple、set等)

例如:

Schema([int, float]).validate([1, 2, 3, 4.0])
# [1, 2, 3, 4.0]

相當於,對於[1, 2, 3, 4.0]當中的任何一個元素,必須是int或者float才行(注意是or的關系)

5. 給Schema傳入一個字典對象(大部分使用Schema的場景都是傳入字典對象,這個很重要)

Schema({'name': str, 'age': int}).validate({'name': 'foobar', 'age': 18})
# {'age': 18, 'name': 'foobar'}
Schema({'name': str, 'age': int}).validate({'name': 'foobar'})
# SchemaMissingKeyError: Missing keys: 'age'

首先,明確兩個概念,Schema類傳入的字典,稱之為模式字典,valdiate方法傳入的字典稱之為數據字典。

首先,Schema會判斷, 模式字典和數據字典的key是否完全一樣,不一樣的話直接拋出異常。如果一樣,就去拿數據字典的value去驗證模式字典相應的value,如果數據字典的全部value都可以驗證通過的話才返回數據,否則拋出異常,是不是感覺這種驗證頓時感覺清爽了呢?

6. faqs

6.1 Schema傳入字典很好用,但是我有的數據是可選的,也就是說有的key可以不提供怎么辦?

from schema import Optional, Schema


Schema({'name': str, Optional('age'): int}).validate({'name': 'foobar'})
# {'name': 'foobar'}
Schema({'name': str, Optional('age', default=18): int}).validate({'name': 'foobar'})
# {'age': 18, 'name': 'foobar'}

6.2 禁止傳入某個key:Forbidden

Forbidden可以將某個key禁止:

from schema import Schema, Forbidden

Schema({Forbidden('name'): str, 'age': int}).validate({"age": 15})                            # {"age": 15}
Schema({Forbidden('name'): str, 'age': int}).validate({"name": "laozhang", "age": 15})        # schema.SchemaForbiddenKeyError
Schema({Forbidden('name'): str, 'age': int}).validate({"name": 10, "age": 15})                # schema.SchemaWrongKeyError

值得注意的是,與禁用秘鑰配對的值將決定了它是否會被拒絕:

from schema import Schema, Forbidden

Schema({Forbidden('name'): int, 'name': str}).validate({'name': 'laozhang'})                  # {'name': 'laozhang'}
Schema({Forbidden('name'): str, 'name': str}).validate({'name': 'laozhang'})                  # schema.SchemaForbiddenKeyError

另外,Forbidden的優先級要比Optional要高:

from schema import Schema, Forbidden, Optional

Schema({Forbidden('name'): str, Optional('name'): str}).validate({"name": "laozhang"})        # schema.SchemaForb

6.3 我想讓Schema只驗證傳入字典中的一部分數據,可以有多余的key但是不要抱錯,怎么做?

Schema({'name': str, 'age': int}, ignore_extra_keys=True).validate({'name': 'foobar', 'age': 100, 'sex': 'male'})
# {'age': 100, 'name': 'foobar'}

6.4 Schema拋出的異常信息不是很友好,我想自定義錯誤信息,怎么辦?

Schema自帶的類(Use、And、Or、Regex、Schema等)都有一個參數error,可以自定義錯誤信息

Schema({'name': str, 'age': Use(int, error='年齡必須是整數')}).validate({'name': 'foobar', 'age': 'abc'})
# SchemaError: 年齡必須是整數

6.5 Use

Use在驗證的時候,會自動幫你轉換它的值

from schema import Schema, Use

print Schema(Use(int)).validate(10)                 # 10-->int類型
print Schema(Use(int)).validate('10')               # 10-->int類型
print Schema(Use(int)).validate('xiaoming')         # schema.SchemaError

6.6 Const

我們知道Use在驗證的時候,會自動幫你轉換它的值。Const可以保持原始數據不變:

from schema import Schema, Use, Const

print Schema(Const(Use(int))).validate('10')        # 10-->str類型

7. 一個稍微復雜的例子:

from schema import Schema, And, Optional, SchemaError, Regex


def name_check(name):
    password_list = ["root", "admin", "888888", "baba", "aaaaaa", "112233"]
    if name in password_list:
        return False
    return True


schema = {
    "id": And(int, lambda x: 100 <= x, error="id必須是整數,大於等於100"),
    "name": And(str, name_check, error="name已經存在"),
    "price": And(float, lambda x: 111 > x > 0, error="price必須是大於0小於111的小數"),
    "data": {
        "password": And(Regex("[a-z0-9A-Z]{8,20}"), error="密碼為數字、字母,長度8-20"),
        "email": And(Regex("^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$"), error="email格式錯誤")
    },
    Optional("info", default="這個key可以不提供,我設置了這一段默認值。"): str,
}

data = {
    "id": 111,
    "name": "jarvis",
    "price": 9.5,
    "data": {
        "password": "222agfwetAAA",
        "email": "www.qq.afsdf@email.cn",
    }
}

try:
    a = Schema(schema).validate(data)
except SchemaError as e:
    print(e)
else:
    print("驗證成功!\n{}".format(a))


# 驗證成功!
# {'id': 111, 'name': 'jarvis', 'price': 9.5, 'data': {'password': '222agfwetAAA', 'email': 'www.qq.afsdf@email.cn'}, 'info': '這個key可以不提供,我設置了這一段默認值。'}

參考文章:https://segmentfault.com/a/1190000011777230


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM