定义一个 schema
import datetime as dt
class User:
def __init__(self, name, email):
self.name = name
self.email = email
self.created_at = dt.datetime.now()
def __repr__(self):
return "<User(name={self.name!r})>".format(self=self)
首先我们定义了一个 User 来代替数据库中的模型,然后根据这个模型来定义对应的序列化分序列化规则:
常用方式
from marshmallow import Schema, fields
class UserSchema(Schema):
name = fields.Str()
email = fields.Email()
created_at = fields.DateTime()
通过字典来进行定义
from marshmallow import Schema, fields
UserSchema = Schema.from_dict(
{
"name": fields.Str(),
"email": fields.Email(),
"created_at": fields.DateTime()
}
)
序列化
通过 Loading 来反序列化
默认情况下,load 方法会将传入字典当中的 value 转为为指定的数据类型:
user_data = {
"created_at": "2014-08-11T05:26:03.869245",
"email": "ken@yahoo.com",
"name": "Ken",
}
schema = UserSchema()
result = schema.load(user_data)
pprint(result)
# {'name': 'Ken',
# 'email': 'ken@yahoo.com',
# 'created_at': datetime.datetime(2014, 8, 11, 5, 26, 3, 869245)}
从上面我们可以看出,时间的字符串被转化为了 datatime.datetime
通过 validate 参数进行限制
当前端传入内容满足对应的数据类型时,如果我们还希望加入一些内容的限制时,此时我们就可以通过 **validate **参数来进行更多的要求。
from marshmallow import Schema, fields, validate, ValidationError
class UserSchema(Schema):
name = fields.Str(validate=validate.Length(min=1))
permission = fields.Str(validate=validate.OneOf(["read",
"write",
"admin"]))
age = fields.Int(validate=validate.Range(min=18, max=40))
in_data = {"name": "", "permission": "invalid", "age": 71}
try:
UserSchema().load(in_data)
except ValidationError as err:
pprint(err.messages)
# {'age': ['Must be greater than or equal to 18 and less than or equal to 40.'],
# 'name': ['Shorter than minimum length 1.'],
# 'permission': ['Must be one of: read, write, admin.']}
有关自定义异常条件的书写方式,详见下文:自定义异常
注意: validate 的限制只会在反序列化的时候才会触发。而序列化的内容默认都是有效的。
Required 参数
通过传递required = True
来对指定的序列化字段进行条件的限制。
**说明:**该限制只在序列化的时候触发
同时,我们也可以对 required 条件不满足的时候抛出的异常进行第自定义:
from marshmallow import Schema, fields, ValidationError
class UserSchema(Schema):
name = fields.String(required=True)
age = fields.Integer(required=True, error_messages={
"required": "Age is required."
})
city = fields.String(
required=True,
error_messages={"required":
{"message": "City required", "code": 400}
},
)
email = fields.Email()
try:
result = UserSchema().load({"email": "foo@bar.com"})
except ValidationError as err:
print(err.messages)
# {'age': ['Age is required.'],
# 'city': {'code': 400, 'message': 'City required'},
# 'name': ['Missing data for required field.']}
partial 参数
对应 Required 参数,如果我们在 Schema 中指定了 required=True
,但是希望前端没有传入对应字段的时候,不进行 required 异常的检出,即跳过对应的字段:
1. 指定跳过缺失的字段
class UserSchema(Schema):
name = fields.String(required=True)
age = fields.Integer(required=True)
result = UserSchema().load({"age": 42}, partial=("name",))
# OR UserSchema(partial=('name',)).load({'age': 42})
print(result) # => {'age': 42}
2. 跳过所有缺失字段
class UserSchema(Schema):
name = fields.String(required=True)
age = fields.Integer(required=True)
result = UserSchema().load({"age": 42}, partial=True)
# OR UserSchema(partial=True).load({'age': 42})
print(result) # => {'age': 42}
处理未知的字段
默认的,如果接收的参数当中有 Schema 对象没有定义的字段时,默认将会抛出ValidationError
。但是我们也是可以自定义它的行为。
通过 unknown 选项,其接收未知参数的处理方式将有以下三种:
- RAISE(default):如果传入的参数没有定义的话,抛出一个异常。
- EXCLUDE:将未指定的字段排除在外。
- INCLUDE: 接收未定义的字段。
书写方式
在定义 Schema 时进行指定:
from marshmallow import Schema, INCLUDE
class UserSchema(Schema):
class Meta:
unknown = INCLUDE
在实例化Schema 对象时进行指定:
schema = UserSchema(unknown=INCLUDE)
在调用 load
方法时进行指定:
UserSchema().load(data, unknown=INCLUDE)
说明: 该三种指定方式的优先级从下到上,依次降低:
load 方式 > 实例对象传参 > Meta 当中指定
反序列化
通过 Dump 方法来进行序列化
user = User(name="Monty", email="monty@python.org")
schema = UserSchema()
result = schema.dump(user)
print(result)
# {"name": "Monty",
# "email": "monty@python.org",
# "created_at": "2014-08-17T14:54:16.049594+00:00"}
当然,我们也可以将数据序列化为 JSON 数据类型。
json_result = schema.dumps(user)
print(json_result)
# '{"name": "Monty", "email": "monty@python.org", "created_at": "2014-08-17T14:54:16.049594+00:00"}'
指定反序列化的内容
对于某个对象,我们并不想返回其所有的内容,这个时候我们就可以指定其返回的字段。这里我们通过 only 和 exclude 参数来进行。
summary_schema = UserSchema(only=("name", "email"))
summary_schema.dump(user)
# {"name": "Monty", "email": "monty@python.org"}
反序列化为一个对象
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, **kwargs):
return User(**data)
在 Schema 类当中定义被 @post_load
装饰的实例方法,我们便可以将待反序列化的字段转化为对应的 User 对象。
user_data = {"name": "Ronnie", "email": "ronnie@stones.com"}
schema = UserSchema()
result = schema.load(user_data)
print(result) # => <User(name='Ronnie')>
这个方式对于操作数据库十分方便,如果我们传入的对象反序列化之后,正好是一个 Model 类型的对象,这个时候就可以直接插入到数据库当中了。
一次性反序列化多个对象
我们可以通过给定 Schema 的实例对象传入 many = True
,来表示一次性序列化多个对象。
user1 = User(name="Mick", email="mick@stones.com")
user2 = User(name="Keith", email="keith@stones.com")
users = [user1, user2]
schema = UserSchema(many=True)
result = schema.dump(users) # OR UserSchema().dump(users, many=True)
pprint(result)
# [{'name': u'Mick',
# 'email': u'mick@stones.com',
# 'created_at': '2014-08-17T14:58:57.600623+00:00'}
# {'name': u'Keith',
# 'email': u'keith@stones.com',
# 'created_at': '2014-08-17T14:58:57.600623+00:00'}]
指定默认值
默认值的设定分为序列化和反序列化两种情况:
-
在对应字段中指定
missing
参数,其默认效果将在反序列化的时候触发,即后端接收前端参数的时候。 -
在对应字段中指定
default
参数,其默认效果将在序列化的时候触发,即后端向前端返回数据的时候。
class UserSchema(Schema):
id = fields.UUID(missing=uuid.uuid1)
birthdate = fields.DateTime(default=dt.datetime(2017, 9, 29))
UserSchema().load({})
# {'id': UUID('337d946c-32cd-11e8-b475-0022192ed31b')}
UserSchema().dump({})
# {'birthdate': '2017-09-29T00:00:00+00:00'}
对序列化和反序列化的 key 进行指定
当我们序列化和反序列化之后返回内容中的 key 值不一致的时候,就需要data_key 这个参数来实现这一个需求。例如前端传入的字段为 emailAdress,而后端数据库当中模型的字段名为 email,这个时候我们就需要对这两者的 key 进行转化。
class UserSchema(Schema):
name = fields.String()
email = fields.Email(data_key="emailAddress")
s = UserSchema()
data = {"name": "Mike", "email": "foo@bar.com"}
result = s.dump(data)
# {'name': u'Mike',
# 'emailAddress': 'foo@bar.com'}
data = {"name": "Mike", "emailAddress": "foo@bar.com"}
result = s.load(data)
# {'name': u'Mike',
# 'email': 'foo@bar.com'}
异常
注意:
schema 类中定义的字段务必写成小写类型,否则容易出现奇怪的异常。
传入数据类型错误导致异常
例如我们将前端传入的数据进行反序列化,如果传入的 key-value 并不满足 Schema 当中定义的类型,此时将会抛出 Validation error :
from marshmallow import ValidationError
try:
result = UserSchema().load({"name": "John", "email": "foo"})
except ValidationError as err:
print(err.messages) # => {"email": ['"foo" is not a valid email address.']}
print(err.valid_data) # => {"name": "John"}
如果我们实例化 Schema 对象时指定 **many=True ** 的时候,此时我们传入一组待验证的数据,mashmallow 会分别对数据进行验证。并对不满足条件的数据指定编号并返回:
from marshmallow import Schema, fields, ValidationError
class BandMemberSchema(Schema):
name = fields.String(required=True)
email = fields.Email()
user_data = [
{"email": "mick@stones.com", "name": "Mick"},
{"email": "invalid", "name": "Invalid"}, # invalid email
{"email": "keith@stones.com", "name": "Keith"},
{"email": "charlie@stones.com"}, # missing "name"
]
try:
BandMemberSchema(many=True).load(user_data)
except ValidationError as err:
pprint(err.messages)
# {1: {'email': ['Not a valid email address.']},
# 3: {'name': ['Missing data for required field.']}}
自定义异常
当我们希望指定 validate,并根据传入的内容来进行异常的抛出的时候,就可以通过如下的方式:
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}
try:
result = ItemSchema().load(in_data)
except ValidationError as err:
print(err.messages)
# => {'quantity': ['Quantity must not be greater than 30.']}
当然,我们也可以将 validate 函数直接写到指定的 Schema 类当中,并指定需要验证的字段。
from marshmallow import fields, Schema, validates, ValidationError
class ItemSchema(Schema):
quantity = fields.Integer()
@validates("quantity")
def validate_quantity(self, value):
if value < 0:
raise ValidationError(
"Quantity must be greater than 0.")
if value > 30:
raise ValidationError(
"Quantity must not be greater than 30.")
边边角角
判断是否有效
有时候,我们并不想序列化传入的参数,而只是想看下该参数是否对应指定的类型,即查看其是否有效。这个时候,我们只需要调用 validate
方法即可。
errors = UserSchema().validate(
{"name": "Ronnie", "email": "invalid-email"}
)
print(errors) # {'email': ['Not a valid email address.']}
字段的隐式创建
fileds
如果传入的字段在 Python 当中已经有对应的内置数据类型,此时我们如果嫌麻烦,不想要再在 Schema 当中指定字段的类型,此时我们就可以通过 Meta当中的 fileds 参数:
class UserSchema(Schema):
uppername = fields.Function(lambda obj: obj.name.upper())
class Meta:
fields = ("name", "email", "created_at", "uppername")
additions
还可以不用 fileds,而是使用 additions 来进行操作:
class UserSchema(Schema):
uppername = fields.Function(lambda obj: obj.name.upper())
class Meta:
# No need to include 'uppername'
additional = ("name", "email", "created_at")
有序输出
为了让序列化之后的数据顺序保持原状,此时我们可以通过 Meta 当中的 ordered 参数来进行指定。
from collections import OrderedDict
from pprint import pprint
from marshmallow import Schema, fields
class UserSchema(Schema):
first_name = fields.String()
last_name = fields.String()
email = fields.Email()
class Meta:
ordered = True
u = User("Charlie", "Stones", "charlie@stones.com")
schema = UserSchema()
result = schema.dump(u)
assert isinstance(result, OrderedDict)
pprint(result, indent=2)
# OrderedDict([('first_name', 'Charlie'),
# ('last_name', 'Stones'),
# ('email', 'charlie@stones.com')])
此时序列化出来的对象就不是 字典了,而是 OrderedDict。