• 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"}}
  • 相关阅读:
    NSNotificationCenter通知
    UITextView 输入字数限制
    UITextView添加占位符 placeholder
    Label显示html文本
    响应者链
    UIKit框架各类简要说明
    [转]setValue和setObject的区别
    谓词(NSPredicate)
    iOS麦克风权限的检测和获取
    SOCKET是什么
  • 原文地址:https://www.cnblogs.com/dowi/p/11890958.html
Copyright © 2020-2023  润新知