0. 前言
- Marshmallow 是一個用於將 ORM 對象與 Python 原生數據類型之間轉換的庫。實現 object → dict、object → list、string → dict 和 string → list 等功能
1. Schema
- 實現一個 object 和 json 之間的轉化需要一個 Schema 對象作為中間載體,同時實現校驗的功能:
class ImageTpl(Schema):
value = fields.Dict(required=True, error_messages=get_field_valid_msg("圖片模版值"))
height = fields.Int(required=True, error_messages=get_field_valid_msg("圖片模版高度"))
width = fields.Int(required=True, error_messages=get_field_valid_msg("圖片模版寬度"))
2. 序列化
- 序列化使用 dump() 或者 dumps() 方法,其中 dump() 實現 object → dict,dumps() 實現 object → string:
from marshmallow import pprint
user = User(name="Monty", email="monty@python.org")
schema = UserSchema()
result = schema.dump(user)
pprint(result.data)
# {"name": "Monty",
# "email": "monty@python.org",
# "created_at": "2014-08-17T14:54:16.049594+00:00"}
from pprint import pprint
user_data = {
'created_at': '2014-08-11T05:26:03.869245',
'email': u'ken@yahoo.com',
'name': u'Ken'
}
schema = UserSchema()
result = schema.load(user_data)
pprint(result.data)
# {'name': 'Ken',
# 'email': 'ken@yahoo.com',
# 'created_at': datetime.datetime(2014, 8, 11, 5, 26, 3, 869245)},
3. 反序列化
- 反序列化使用 load() 或者 loads() 方法,分別實現 dict → object 和 string → object。其中 dict → object 需要添加裝飾器,自己實現邏輯:
from marshmallow import Schema, fields, post_load
class UserSchema(Schema):
name = fields.Str()
email = fields.Email()
created_at = fields.DateTime()
@post_load
def make_user(self, data):
return User(**data)
user_data = {
'name': 'Ronnie',
'email': 'ronnie@stones.com'
}
schema = UserSchema()
result = schema.load(user_data)
result.data # => <User(name='Ronnie')>
4. Field 對象
- Schema 對象為每個屬性賦值為一個 Field 對象設定轉換類型的校驗參數,具體如下:
- validate 參數:指定一個 lambda 函數或者函數,定義校驗邏輯,傳入函數定義了 ValidationError 的話,返回信息會記錄拋出的異常:
from marshmallow import Schema, fields, ValidationError
def validate_quantity(n):
if n < 0:
raise ValidationError('Quantity must be greater than 0.')
if n > 30:
raise ValidationError('Quantity must not be greater than 30.')
class ItemSchema(Schema):
quantity = fields.Integer(validate=validate_quantity)
in_data = {'quantity': 31}
result, errors = ItemSchema().load(in_data)
errors # => {'quantity': ['Quantity must not be greater than 30.']}
- required 參數:標記該字段必須傳遞切被校驗
- error_messages 參數:傳遞字典定義錯誤返回信息:
def get_field_valid_msg(field_name):
return {
'required': '[%s] 字段必填' % field_name,
'type': '[%s] 字段類型不合法' % field_name, # used by Unmarshaller
'null': '[%s] 字段不能為空' % field_name,
'validator_failed': '[%s] 字段值不合法' % field_name
}
- many 參數:同 fields.Nested 同用表示指定元素類型的數組類型,嚴格按照數據類型校驗,fields.Nested 表示制定另一個 Schema 作為外鍵:
......
'components': fields.Nested(Component, required=True, many=True,
error_messages=get_field_valid_msg("組件信息")),
......
- fields.List 類型:一類 Field 元素,可以指定元素類型,對於傳遞的非數組類型的元素,會自動包裝為一個數組
5. 驗證
- 對於 Schema 的校驗有很多方式,通過上述 Field 元素的相關參數已經實現了很多對元素的校驗
- 也可以通過定義 @validates(field_name) 裝飾器定義特定屬性的校驗函數
- 也可以通過定義 @validate_schema() 裝飾器定義 Schema 級別的校驗函數:
@validates_schema
def validate_element_type(self, value):
switch = {
DpaVideoPackageElementType.TEXT : lambda x : x in DpaVideoPackageDataType.text_types(),
DpaVideoPackageElementType.IMAGE : lambda x : x in DpaVideoPackageDataType.image_types(),
DpaVideoPackageElementType.LOGO : lambda x : x in DpaVideoPackageDataType.logo_types()
}
try:
if not switch[value['element_type']](value['data_type']):
raise ValidationError("組件類型和組件數據類型不匹配")
except KeyError as e:
raise ValidationError("組件類型不存在")
- 對於一個 Schema,load() 和 loads 方法會在返回值中加入驗證錯誤的信息:
class ImageOrImageMeta(Schema):
value = fields.String(required=True, error_messages=get_field_valid_msg("圖片或圖片元數據值"))
height = fields.Int(required=True, error_messages=get_field_valid_msg("圖片或圖片元數據高度"))
width = fields.Int(required=True, error_messages=get_field_valid_msg("圖片或圖片元數據寬度"))
class ImageTpl(Schema):
value = fields.Dict(required=True, error_messages=get_field_valid_msg("圖片模版值"))
height = fields.Int(required=True, error_messages=get_field_valid_msg("圖片模版高度"))
width = fields.Int(required=True, error_messages=get_field_valid_msg("圖片模版寬度"))
class TextOrTextMeta(Schema):
value = fields.String(required=True, error_messages=get_field_valid_msg("文本或文本元數據值"))
default_val = fields.String(error_messages=get_field_valid_msg("文本或文本元數據默認值"))
max_length = fields.Int(required=True, error_messages=get_field_valid_msg("文本或文本元數據最大值"))
......
switch = {
DpaVideoPackageDataType.IMAGE : lambda x : ImageOrImageMeta().load(x),
DpaVideoPackageDataType.IMAGE_META : lambda x : ImageOrImageMeta().load(x),
DpaVideoPackageDataType.IMAGE_TPL : lambda x : ImageTpl().load(x),
DpaVideoPackageDataType.TEXT : lambda x : TextOrTextMeta().load(x),
DpaVideoPackageDataType.TEXT_META : lambda x : TextOrTextMeta().load(x),
}
result = switch[value['data_type']](value['data'])
if result.errors:
raise ValidationError(result.errors)
4. 參考文獻