工作中用到的Marshmallow
最近在Nomad Health实习,当从前端返回json object的时候,我们要通过mongoengine存到mongodb中。在这个过程中需要去serialize(将Pyhon datatype转变为JSON objects)和deserialize data(将JSON objects转变为Python datatype),我们用了Marshmallow去完成这件事情。它的兼容性好,能与多种框架和数据层相兼容。官方定义为:
marshmallow is an ORM/ODM/framework-agnostic library for converting complex datatypes, such as objects, to and from native Python datatypes.
作为一个小白,这篇文章是对自己在工作中所用到、学到的东西的总结。doc已经写的很清楚了,就是一些自己觉得需要记一下的点。
Validator
Schema.load() is a deserialization process, it will return two values, the first one is the deserialized schema, the second one is errors. For some fields like Email and URL, it has built-in validators. If we want to define our own validator, we can pass in a callable like this: age = fields.Number(validate=lambda n: 18 <= n <= 40)
The validation function will return a boolean or a ValidationError. Of course, instead of lambda function we can define our validator as a method, e.g.
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.')
We can validate required fields passing a parameter and error message like this:
class UserSchema(Schema):
city = fields.String(
required=True,
error_messages={'required': {'message': 'City required', 'code': 400}}
)
data, errors = UserSchema().load({})
errors
# 'city': {'message': 'City required', 'code': 400}}
Sometimes we need a partial validator because we may only want to validate certain fields under certain conditions. For example, we don't want to validate when the doc is saved as a draft. We can do partial validator on certain fields, or ignore the required statements entirely like this:
class UserSchema(Schema):
name = fields.String(required=True)
age = fields.Integer(required=True)
data, errors = UserSchema().load({'age': 42}, partial=True)
# OR UserSchema(partial=True).load({'age': 42})
data, errors # => ({'age': 42}, {})
When we are deserializing an object, often the JSON object comes in camelCase and in Python we use the underscore convention. So often we want to specify deserialization keys by using load_from
. The serialization process we should use dump_to
:
class UserSchema(Schema):
name = fields.String()
email = fields.Email(load_from='emailAddress')
data = {
'name': 'Mike',
'emailAddress': 'foo@bar.com'
}
We can register schema-level validation functions for a Schema using the validates_schema decorator. Schema-level validation errors will be stored on the _schema key of the errors dictonary.
from marshmallow import Schema, fields, validates_schema, ValidationError
class NumberSchema(Schema):
field_a = fields.Integer()
field_b = fields.Integer()
@validates_schema
def validate_numbers(self, data):
if data['field_b'] >= data['field_a']:
raise ValidationError('field_a must be greater than field_b')
schema = NumberSchema()
result, errors = schema.load({'field_a': 2, 'field_b': 1})
errors['_schema'] # => ["field_a must be greater than field_b"]
Sort of like pre_load, it can pass in original data by doing @validates_schema(pass_original=True)
.
If you want to store schema-level validation errors on a specific field, you can pass a field name (or multiple field names) to the ValidationError:
class NumberSchema(Schema):
field_a = fields.Integer()
field_b = fields.Integer()
@validates_schema
def validate_numbers(self, data):
if data['field_b'] >= data['field_a']:
raise ValidationError(
'field_a must be greater than field_b',
'field_a'
)
Pre-processing and Post-processing Methods
pre_load register a method to invoke before deserializing an object. So if we want to validate the raw incoming data, we can validate it in a pre_load method, otherwise the validation would happen after the deserialization.
Method & Function
The only difference between the two is that, Method takes in a method in the serialization/deserialization process while Function can take in a lambda function.