marshmallow扩展Schemas

  • Pre-processing and Post-processing Methods

可以使用pre_load,post_load,pre_dump,post_dump装饰器,来对(反)序列化数据进行预处理和后处理操作

 1 from marshmallow import Schema, fields, pre_load
 2 
 3 class UserSchema(Schema):
 4     name = fields.String()
 5     slug = fields.String()
 6 
 7     @pre_load
 8     def slugify_name(self, in_data, **kwargs):
 9         print(1111)
10         in_data["slug"] = in_data["slug"].lower().strip().replace(" ", "-")
11         return in_data
12 
13     @post_load
14     def make_user(self, in_data, **kwargs):
15         print(2222)
16         return in_data
17 
18 schema = UserSchema()
19 result = schema.load({"name": "Steve", "slug": "Steve Loria"})
20 pprint(result)
21 
22 11111
23 22222
24 {'name': 'Steve', 'slug': 'steve-loria'}
  • Passing “many”

默认情况下,预处理和后处理方法同时只能接受一个对象/数据,如果要处理一个集合的数据,只需给装饰器方法传入pass_many=True即可

 1 from marshmallow import Schema, fields, pre_load, post_load, post_dump
 2 
 3 class BaseSchema(Schema):
 4     __envelope__ = {"single": None, "many": None}
 5     __model__ = User
 6 
 7     def get_envelope_key(self, many):
 8         key = self.__envelope__["many"] if many else self.__envelope__["single"]
 9         assert key is not None, "Envelope key undefined"
10         return key
11 
12     @pre_load(pass_many=True)
13     def unwrap_envelope(self, data, many, **kwargs):
14         key = self.get_envelope_key(many)
15         return data[key]
16 
17     @post_dump(pass_many=True)
18     def wrap_with_envelope(self, data, many, **kwargs):
19         key = self.get_envelope_key(many)
20         return {key: data}
21 
22     @post_load
23     def make_object(self, data, **kwargs):
24         return self.__model__(**data)
25 
26 class UserSchema(BaseSchema):
27     __envelope__ = {"single": "user", "many": "users"}
28     __model__ = User
29     name = fields.Str()
30     email = fields.Email()
31 
32 user_schema = UserSchema()
33 user = User("Mike", "mike@stones.com")
34 pprint(user_schema.dump(user))
35 
36 {'user': {'email': 'mike@stones.com', 'name': 'Mike'}}
37 
38 users = [
39     User("Keith", "keith@stones.com"),
40     User("Charlie", "charlie@stones.com"),
41 ]
42 users_data = user_schema.dump(users, many=True)
43 pprint(users_data)
44 
45 {'users': [{'email': 'keith@stones.com', 'name': 'Keith'},
46            {'email': 'charlie@stones.com', 'name': 'Charlie'}]}
47 
48 pprint(user_schema.load(users_data, many=True))
49 
50 [<User(name='Keith')>, <User(name='Charlie')>]
  • Raising Errors in Pre-/Post-processor Methods

预处理和后处理方法可能会触发ValidationError,默认情况下,错误会被保存在以“_schema”为键的字典中

 1 from marshmallow import Schema, fields, ValidationError, pre_load
 2 
 3 class BandSchema(Schema):
 4     name = fields.Str()
 5 
 6     @pre_load
 7     def unwrap_envelope(self, data, **kwargs):
 8         if "data" not in data:
 9             raise ValidationError("Input data must have a 'data' key.")
10         return data["data"]
11 
12 sch = BandSchema()
13 try:
14     sch.load({"name": "The Band"})
15 except ValidationError as err:
16     print(err.messages)
17 
18 {'_schema': ["Input data must have a 'data' key."]}

如果不想用“_schema”作为错误字典的键,只需往ValidationError传入第二个参数(自定义键)即可

 1 from marshmallow import Schema, fields, ValidationError, pre_load
 2 
 3 class BandSchema(Schema):
 4     name = fields.Str()
 5 
 6     @pre_load
 7     def unwrap_envelope(self, data, **kwargs):
 8         if "data" not in data:
 9             raise ValidationError("Input data must have a 'data' key.", "_preprocessing")
10         return data["data"]
11 
12 sch = BandSchema()
13     try:
14         sch.load({"name": "The Band"})
15     except ValidationError as err:
16         print(err.messages)
17 
18 {'_preprocessing': ["Input data must have a 'data' key."]}
  • Pre-/Post-processor Invocation Order

反序列化处理调用顺序如下:

  1. @pre_load(pass_many=True) methods
  2. @pre_load(pass_many=False) methods
  3. load(in_data, many) (validation and deserialization)
  4. @post_load(pass_many=True) methods
  5. @post_load(pass_many=False) methods

序列化类似,只是带pass_many=True的处理方法会在带pass_many=False的处理方法之后被调用

  1. @pre_dump(pass_many=False) methods
  2. @pre_dump(pass_many=True) methods
  3. dump(obj, many) (serialization)
  4. @post_dump(pass_many=False) methods
  5. @post_dump(pass_many=True) methods

Warning:

你可能会在同一个schema上注册多个处理方法,如果出现多个同类型的处理装饰器,则schema无法保证调用顺序。

如果你需要保证调用顺序,你应该将它们放到同一个方法里。

 1 from marshmallow import Schema, fields, pre_load
 2 
 3 # YES
 4 class MySchema(Schema):
 5     field_a = fields.Field()
 6 
 7     @pre_load
 8     def preprocess(self, data, **kwargs):
 9         step1_data = self.step1(data)
10         step2_data = self.step2(step1_data)
11         return step2_data
12 
13     def step1(self, data):
14         do_step1(data)
15 
16     # Depends on step1
17     def step2(self, data):
18         do_step2(data)
19 
20 
21 # NO
22 class MySchema(Schema):
23     field_a = fields.Field()
24 
25     @pre_load
26     def step1(self, data, **kwargs):
27         do_step1(data)
28 
29     # Depends on step1
30     @pre_load
31     def step2(self, data, **kwargs):
32         do_step2(data)
  • Schema-level Validation

可以使用marshmallow.validates_schema装饰器来注册schema级别的验证方法

 1 from marshmallow import Schema, fields, validates_schema, ValidationError
 2 
 3 class NumberSchema(Schema):
 4     field_a = fields.Integer()
 5     field_b = fields.Integer()
 6 
 7     @validates_schema
 8     def validate_numbers(self, data, **kwargs):
 9         if data["field_b"] >= data["field_a"]:
10             raise ValidationError("field_a must be greater than field_b")
11 
12 schema = NumberSchema()
13 try:
14     schema.load({"field_a": 1, "field_b": 2})
15 except ValidationError as err:
16     print(err.messages)
17 
18 {'_schema': ['field_a must be greater than field_b']}
  • Storing Errors on Specific Fields

当多个schema级别的验证器返回错误信息时,这些错误信息将在验证结束后被合并到一起返回

 1 from marshmallow import Schema, fields, validates_schema, ValidationError
 2 
 3 class NumberSchema(Schema):
 4     field_a = fields.Integer()
 5     field_b = fields.Integer()
 6     field_c = fields.Integer()
 7     field_d = fields.Integer()
 8 
 9     @validates_schema
10     def validate_lower_bound(self, data, **kwargs):
11         errors = {}
12         if data["field_b"] <= data["field_a"]:
13             errors["field_b"] = ["field_b must be greater than field_a"]
14         if data["field_c"] <= data["field_a"]:
15             errors["field_c"] = ["field_c must be greater than field_a"]
16         if errors:
17             raise ValidationError(errors)
18 
19     @validates_schema
20     def validate_upper_bound(self, data, **kwargs):
21         errors = {}
22         if data["field_b"] >= data["field_d"]:
23             errors["field_b"] = ["field_b must be lower than field_d"]
24         if data["field_c"] >= data["field_d"]:
25             errors["field_c"] = ["field_c must be lower than field_d"]
26         if errors:
27             raise ValidationError(errors)
28 
29 schema = NumberSchema()
30 try:
31     schema.load({
32         "field_a": 3,
33         "field_b": 2,
34         "field_c": 1,
35         "field_d": 0
36     })
37 except ValidationError as err:
38     pprint(err.messages)
39 
40 {'field_b': ['field_b must be greater than field_a',
41              'field_b must be lower than field_d'],
42  'field_c': ['field_c must be greater than field_a',
43              'field_c must be lower than field_d']}
  • Using Original Input Data

如果想使用原始的未处理的输入数据,只需要在post_load装饰器上传入pass_original=True即可

 1 from marshmallow import Schema, fields, post_load, ValidationError
 2 
 3 class MySchema(Schema):
 4     foo = fields.Integer()
 5     bar = fields.Integer()
 6 
 7     class Meta:
 8         unknown = EXCLUDE
 9 
10     @post_load(pass_original=True)
11     def add_baz_to_bar(self, data, original_data, **kwargs):
12         baz = original_data.get("baz")
13         if baz:
14             data["bar"] = data["bar"] + baz
15         return data
16 
17 schema = MySchema()
18 pprint(schema.load({"foo": 1, "bar": 2, "baz": 3}))
19 
20 {'bar': 5, 'foo': 1}
  • Custom Error Handling

自定义错误处理,可以通过重写handle_error方法来自定义一个错误处理方法,这个方法接受ValidationError和原始的反序列化的输入数据

 1 import logging
 2 from marshmallow import Schema, fields
 3 
 4 class AppError(Exception):
 5     pass
 6 
 7 class UserSchema(Schema):
 8     email = fields.Email()
 9 
10     def handle_error(self, error, data, **kwargs):
11         logging.error(error.messages)
12         raise AppError("An error occurred with input: {0}".format(data))
13 
14 schema = UserSchema()
15 schema.load({"email": "invalid-email"})
16 
17 AppError: An error occurred with input: {'email': 'invalid-email'}
  • Custom “class Meta” Options

class Meta 设置是一种配置和修改schema行为的方式,查看API

可以通过子类SchemaOpts来自定义class Meta配置

  • Example:Enveloping,Revisited

同样为上面的的例子添加一个序列化输出的封装。这次,使用封装的键来自定义class Meta的配置。

 1 # Example outputs
 2 {
 3     'user': {
 4         'name': 'Keith',
 5         'email': 'keith@stones.com'
 6     }
 7 }
 8 # List output
 9 {
10     'users': [{'name': 'Keith'}, {'name': 'Mick'}]
11 }

首先,添加命名空间配置到一个自定义的设置类

 1 from marshmallow import Schema, SchemaOpts
 2 
 3 class NamespaceOpts(SchemaOpts):
 4     """Same as the default class Meta options, but adds "name" and
 5     "plural_name" options for enveloping.
 6     """
 7 
 8     def __init__(self, meta, **kwargs):
 9         SchemaOpts.__init__(self, meta, **kwargs)
10         self.name = getattr(meta, "name", None)
11         self.plural_name = getattr(meta, "plural_name", self.name)

 然后用这个设置类创建一个自定义的schema

 1 class NamespacedSchema(Schema):
 2     OPTIONS_CLASS = NamespaceOpts
 3 
 4     @pre_load(pass_many=True)
 5     def unwrap_envelope(self, data, many, **kwargs):
 6         key = self.opts.plural_name if many else self.opts.name
 7         return data[key]
 8 
 9     @post_dump(pass_many=True)
10     def wrap_with_envelope(self, data, many, **kwargs):
11         key = self.opts.plural_name if many else self.opts.name
12         return {key: data}

现在应用schemas就可以继承这个自定义的schema类了

 1 class UserSchema(NamespacedSchema):
 2     name = fields.String()
 3     email = fields.Email()
 4 
 5     class Meta:
 6         name = "user"
 7         plural_name = "users"
 8 
 9 
10 ser = UserSchema()
11 user = User("Keith", email="keith@stones.com")
12 result = ser.dump(user)
13 result  # {"user": {"name": "Keith", "email": "keith@stones.com"}}
posted @ 2019-11-20 09:23  dowi  阅读(822)  评论(0编辑  收藏  举报