marshmallow轻量自动化验证和序列化

源代码

import functools
from tornado.web import HTTPError
from marshmallow_peewee import ModelSchema
from marshmallow.fields import Field
from marshmallow_peewee import Related
from marshmallow.validate import (
    URL, Email, Range, Length, Equal, Regexp,
    Predicate, NoneOf, OneOf, ContainsOnly
)

URL.default_message = '无效的链接'
Email.default_message = '无效的邮箱地址'
Range.message_min = '不能小于{min}'
Range.message_max = '不能小于{max}'
Range.message_all = '不能超过{min}和{max}这个范围'
Length.message_min = '长度不得小于{min}位'
Length.message_max = '长度不得大于{max}位'
Length.message_all = '长度不能超过{min}和{max}这个范围'
Length.message_equal = '长度必须等于{equal}位'
Equal.default_message = '必须等于{other}'
Regexp.default_message = '非法输入'
Predicate.default_message = '非法输入'
NoneOf.default_message = '非法输入'
OneOf.default_message = '无效的选择'
ContainsOnly.default_message = '一个或多个无效的选择'
Field.default_error_messages = {
        'required': '该字段是必填字段',
        'type': '无效的输入类型',
        'null': '字段不能为空',
        'validator_failed': '无效的值'
}


def validate_schema(_model, **schema_kwargs):

    """
    用于检查参数正确性。

    :param _model: 数据库模块,用于绑定字段校验。
    :param schema_kwargs:  根据marshmallow.schema.Schema的
                            参数要求进行要求,下面是详细参数列表:
                            extra=None
                            only=()
                            exclude=()
                            prefix=''
                            strict=None
                            many=False
                            context=None
                            load_only=()
                            dump_only=()
                            partial=False
    :return: 校验正确则执行handler,校验失败则返回错误信息。
    """

    def decorator(f):
        @functools.wraps(f)
        def wrapper(self, *args, **kwargs):

            class CommonSchema(ModelSchema):

                class Meta:
                    model = _model

            # 检查验证结果
            result, errors = (CommonSchema(**schema_kwargs)
                              .load(self.plain_arguments()))

            # 当验证结果存在错误时, 返回错误信息.
            if errors:
                msg = ['{}{}'.format(getattr(_model, k).verbose_name, v[0])
                       for k, v in errors.items()]
                raise HTTPError(status_code=400, log_message='\n'.join(msg))

            # 当验证结果正确时, 执行handler.
            return f(self, *args, **kwargs)
        return wrapper
    return decorator


def serializer_schema(_model, *related, **schema_kwargs):
    """
    用于序列化。
    :param _model: 数据库模块,用于绑定字段校验。
    :param related: 声明那些关联字段是需要一起序列化的,参数格式如下:
                     [
                     # 表示外键
                     (field_name, None),
                     # 表示该字段为不是关联字段,但序列化需要存在的字段.
                     (field_name, callback)
                     ]
    :param schema_kwargs: 根据marshmallow.schema.Schema的
                            参数要求进行要求,下面是详细参数列表:
                            extra=None
                            only=()
                            exclude=()
                            prefix=''
                            strict=None
                            many=False
                            context=None
                            load_only=()
                            dump_only=()
                            partial=False
    :return: 返回经过序列化后的数据集。
    """

    class CommonSchema:
        class Meta:
            model = None

    # 绑定类对象
    CommonSchema.Meta.model = _model
    [setattr(CommonSchema, field, callback())
     if callback else setattr(CommonSchema, field, Related())
     for field, callback in related]

    # 混入
    schema_cls = type(
        str('CommonSchema'), (CommonSchema, ModelSchema), {}
    )

    return schema_cls(**schema_kwargs)

目的

针对marshmallow和peewee进行数据库字段整合,便于自动映射、验证和序列化行为。
通过下面的实现,可以简化一些简单的定义工作(复杂的暂时不支持),例如: 验证post
表单参数可以通过指定validate_schema来自动完成检查行为,而不需要去定义
ValidateSchema;序列化peewee查询结果可以通过serializer_schema来自动完成结构
体自动绑定,而不需要去定义serializerSchema结构体。(警告: 目前这两个方法只
支持简单的自动检查和序列化,非常复杂的场景还是需要手动去定义结构体),这里
列出一些使用方法:

验证案例

利用validate_schema对登录页面提交的参数进行验证。
validate_schema的第一个参数 User 是一个models对象,装饰器默认会根据这个User。 对象自动映射和验证全部字段,通过利用only参数可以通知验证器仅验证哪几个字段。除了only参数之外,常用的还有 exclude和many参数。

@route('/login')
class Login(BaseHandler):
    # 装饰器在这里工作
    @validate_schema(User, only=('mobile', 'password'))      
    def post(self):
        # 验证用户名是否存在。
        
        # 验证密码是否正确。
        
        # local.storage/cookie/session保存用户信息。
        self.write('ok')

序列化案例:

from marshmallow import pprint
s = (User.select()
     .where(User.id=='1')
     .get())

result, errors = serializer_schema(User).dump(s)
pprint(result)


# 输出结果
{
 'created_at': 'XXX',
 'email': None,
 'id': '1',
 'is_active': True,
 'mobile': 'XXXXXX',
 'name': 'XX',
 'password': 'XXXXXXXXXX'
}

利用only参数来控制数据结果

result, errors = serializer_schema(User, only=('mobile', 'email', 'name')).dump(s)
pprint(result)
# 输出结果
{'email': None, 'mobile': 'XXXXXX', 'name': 'XX'}

利用exclude参数来控制数据结果

result, errors = serializer_schema(User, exclude=('mobile', )).dump(s)
pprint(result)
# 输出结果
{
 'created_at': 'XXX',
 'email': None,
 'id': '1',
 'is_active': True,
 'name': 'XX',
 'password': 'XXXXXXXXXX'
}

一对多关系处理

result, errors = serializer_schema(User, *[('logs', None), ], exclude=('password', )).dump(s)
pprint(result)


# 查看结果
{
 'created_at': 'XX',
 'email': None,
 'id': 1,
 'is_active': True,
 'logs': [{'created_at': 'XX',
           'id': 1,
           'ip': '111',
           'user': 1}],
 'mobile': 'XXXXXX',
 'name': 'XX',
}

多对一关系处理

s = (UserLog.select().where(UserLog.id=='1').get())
pprint(serializer_schema(UserLog, *[('user', None)]).dump(s)[0])
# 查看结果
{'created_at': 'XX',
 'id': 1,
 'ip': '111',
 'user': {
          'created_at': '2017-01-23T11:40:19.534108+00:00',
          'email': None,
          'id': 1,
          'is_active': True,
          'mobile': 'XXXXXX',
          'name': 'XX',
          'password': 'XXXXXXXXXX'}}

未完待续(多对多关系处理)

未完待续(复杂的多表联合处理)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容