欢迎来到十九分快乐的博客

生死看淡,不服就干。

3. 序列化模块Marshmallow相关操作

Marshmallow

官方文档:https://marshmallow.readthedocs.io/en/latest/

Marshmallow,中文译作:棉花糖。

​ 是一个轻量级的数据格式转换的模块,也叫序列化和反序列化模块,常用于将复杂的orm模型对象与python原生数据类型之间相互转换。marshmallow提供了丰富的api功能。如下:

  1. Serializing - 服务端数据传到客户端展示,不需要数据校验

    序列化[可以把数据模型对象转化为可存储或可传输的数据类型,例如:objects/object->list/dict,dict/list->string]

  2. Deserializing - 客户端数据传到服务端存储, 需要数据校验

    反序列化器[把可存储或可传输的数据类型转换成数据模型对象,例如:list/dict->objects/object,string->dict/list]

  3. Validation

    数据校验,可以在反序列化阶段,针对要转换数据的内容进行类型验证或自定义验证。

Marshmallow本身是一个单独的库,基于我们当前项目使用框架是flask并且数据库ORM框架使用SQLAlchemy,所以我们可以通过安装flask-sqlalchemy和marshmallow-sqlalchemy集成到项目就可以了。

基本安装和配置

模块安装:

pip install -U marshmallow-sqlalchemy -i https://pypi.douban.com/simple
pip install -U flask-sqlalchemy -i https://pypi.douban.com/simple
pip install -U flask-marshmallow -i https://pypi.douban.com/simple

模块初始化:docs/marshDemo.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

from datetime import datetime

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)
ms = Marshmallow(app)

class User(db.Model):
    __tablename__ = 'db_user'
    id = db.Column(db.Integer, primary_key=True, comment='主键')
    username = db.Column(db.String(255), index=True, comment='用户名')
    password = db.Column(db.String(222), comment='密码')
    mobile = db.Column(db.String(15), index=True, comment='手机号')
    sex = db.Column(db.Boolean, default=True, comment='性别')
    email = db.Column(db.String(233), comment='邮箱')
    create_time = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
    update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')

    def __repr__(self):
        return '<%s:%s>'%(self.__class__.__name__, self.username)

@app.route('/')
def index():
    return 'hello world!!'

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', port=6100, debug=True)

基本构造器(BaseSchema)

marshmallow转换数据格式主要通过构造器类来完成。在marshmallow使用过程中所有的构造器类必须直接或间接继承于Schema基类,而Schema类提供了数据转换的基本功能:序列化数据验证反序列化

基于Schema完成数据序列化转换

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow

from datetime import datetime

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)
ms = Marshmallow(app)

class User(db.Model):
    __tablename__ = 'db_user'
    id = db.Column(db.Integer, primary_key=True, comment='主键')
    username = db.Column(db.String(255), index=True, comment='用户名')
    password = db.Column(db.String(222), comment='密码')
    mobile = db.Column(db.String(15), index=True, comment='手机号')
    sex = db.Column(db.Boolean, default=True, comment='性别')
    email = db.Column(db.String(233), comment='邮箱')
    create_time = db.Column(db.DateTime, default=datetime.now, comment='创建时间')
    update_time = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now, comment='更新时间')

    def __repr__(self):
        return '<%s:%s>'%(self.__class__.__name__, self.username)


from marshmallow import Schema, fields
# marshmallow转换数据格式通过构造器类来完成,必须直接或间接继承于Schema基类
class UserSchema(Schema):
    # 定义要转换的字段级类型
    username = fields.String()
    mobile = fields.String()
    email = fields.Email()
    create_time = fields.DateTime()


@app.route('/')
def index():
    # 实例化模型类
    user = User(
        username = 'jiayinghe',
        mobile = '13944271700',
        sex = True,
        email = 'jia@qq.com',
        create_time = datetime.now(),
        update_time = datetime.now(),
    )
    print(user) # 模型对象<User:jiayinghe>

    """1. 序列化单个对象"""
    # 调用Marsh构造器类,把模型对象转换成数据格式
    us = UserSchema() # 序列化多个数据,需要指定many=True
    res1 = us.dump(user) # 格式化输出成字典
    res2 = us.dumps(user) # 格式化输出成json字符串
    print(type(res1),f'res1 = {res1}')
    print(type(res2),f'res2 = {res2}')
    """
<class 'dict'> res1 = {'create_time': '2021-06-03T10:57:54.291193', 'mobile': '13944271700', 'username': 'jiayinghe', 'email': 'jia@qq.com'}
<class 'str'> res2 = {"create_time": "2021-06-03T10:57:54.291193", "mobile": "13944271700", "username": "jiayinghe", "email": "jia@qq.com"}
    """

    """2. 序列化多个对象"""
    user1 = User(
        username="xiaoming1号",
        mobile="13312345677",
        sex=True,
        email="133123456@qq.com",
        create_time=datetime.now(),
        update_time=datetime.now()
    )
    # 把所有需要序列化的对象放到列表中
    user_list = [user, user1]
    us = UserSchema(many=True)
    res1_list = us.dump(user_list) # 格式化输出列表(元素是字典)
    res2 = us.dumps(user_list) # 格式化输出json字符串(显示与上边一样)
    print(type(res1_list), f'res1_list = {res1_list}')
    print(type(res2), f'res2 = {res2}')
    """
    <class 'list'> res1_list = [{},{}]
    <class 'str'> res2 = [{},{}]
    """
    return 'hello world!!'

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', port=6100, debug=True)

schema常用属性数据类型

类型 描述
fields.Dict(keys, type]] = None, values, …) 字典类型,常用于接收json类型数据
fields.List(cls_or_instance, type], **kwargs) 列表类型,常用于接收数组数据
fields.Tuple(tuple_fields, *args, **kwargs) 元组类型
fields.String(*, default, missing, data_key, …) 字符串类型
fields.UUID(*, default, missing, data_key, …) UUID格式类型的字符串
fields.Number(*, as_string, **kwargs) 数值基本类型
fields.Integer(*, strict, **kwargs) 整型
fields.Decimal(places, rounding, *, allow_nan, …) 数值型
fields.Boolean(*, truthy, falsy, **kwargs) 布尔型
fields.Float(*, allow_nan, as_string, **kwargs) 浮点数类型
fields.DateTime(format, **kwargs) 日期时间类型
fields.Time(format, **kwargs) 时间类型
fields.Date(format, **kwargs) 日期类型
fields.Url(*, relative, schemes, Set[str]]] = None, …) url网址字符串类型
fields.Email(*args, **kwargs) 邮箱字符串类型
fields.IP(*args[, exploded]) IP地址字符串类型
fields.IPv4(*args[, exploded]) IPv4地址字符串类型
fields.IPv6(*args[, exploded]) IPv6地址字符串类型
fields.Method(serialize, deserialize, **kwargs) 基于Schema类方法返回值的字段
fields.Function(serialize, Any], Callable[[Any, …) 基于函数返回值得字段
fields.Nested(nested, type, str, Callable[[], …) 外键类型

Schema数据类型的常用通用属性

属性名 描述
default 序列化阶段中设置字段的默认值
missing 反序列化阶段中设置字段的默认值
validate 反序列化阶段调用的内置数据验证器或者内置验证集合
required 设置当前字段的必填字段
allow_none 是否允许为空 None,""
load_only 是否在反序列化阶段才使用到当前字段,相当于之前的write_only
dump_only 是否在序列化阶段才使用到当前字段,相当于之前的read_only
error_messages 字典类型,可以用来替代默认的字段异常提示语,格式:
error_messages=

构造器嵌套使用

一对一,一对多,多对多,代码:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import Schema, fields


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)
ms = Marshmallow(app)

"""模拟模型类"""
# 作者类
class Author(object):
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.create_time = datetime.now()
        self.books = []   # 一对多外键
        self.courses = [] # 多对多外键

# 书籍类
class Book(object):
    def __init__(self, title, author):
        self.title = title
        self.author = author

# 写作课程类
class Course(object):
    def __init__(self, name):
        self.name = name
        self.authors = [] # 多对多外键

"""创建构造器"""
# 作者 - 书籍 一对多嵌套
class AuthorSchema(Schema):
    name = fields.String()
    email = fields.Email()
    # 双向嵌套,例如找出所有属于该作者的所有书籍,exclude剔除字段,避免循环嵌套
    books = fields.List(fields.Nested(lambda : BookSchema(exclude=['author'])))


# 书籍
class BookSchema(Schema):
    title = fields.String()
    # 用来序列化外键对象,可以将外键对象按照指定的格式进行序列化
    # 使用lambda匿名函数,使只有在使用author时才会找AuthorSchema对象,不然可能出现先后调用
    author = fields.Nested(lambda :AuthorSchema())

# 写作课程 - 作者 多对多嵌套
class CourseSchema(Schema):
    name = fields.String()
    authors = fields.List(fields.Nested(lambda :AuthorSchema2(exclude=['courses'])))

# 作者 - 写作课程 多对多嵌套
class AuthorSchema2(Schema):
    name = fields.String()
    email = fields.Email()
    # # 方式一:通用嵌套
    # courses = fields.List(fields.Nested(lambda : CourseSchema(exclude=['authors'])))

    # # 方式二:指定序列化的字段, 构造器必须写在CourseSchema之后
    # courses = fields.Nested(CourseSchema, only=('name',), many=True)

    # # 方式三: 也可以在方式二的基础上,不指定字段
    # courses = fields.Nested(lambda : CourseSchema(many=True))

    # 方式四:使用Pluck字段可以用单个字段来替换嵌套的数据,返回列表数据
    courses = fields.Pluck(CourseSchema,'name', many=True)


@app.route('/index1')
def index1():
    """一对一的嵌套序列化"""
    author = Author(
        name = 'jia',
        email= 'jia@qq.com'
    )

    book = Book(
        title='小米手机1',
        author=author
    )

    bs = BookSchema()
    res1 = bs.dump(book) # 字典
    res2 = bs.dumps(book) # json
    print(res1)
    # {'title': '小米手机1', 'author': {'books': [], 'email': 'jia@qq.com', 'name': 'jia'}}
    print(res2)

    return 'hello world!!'

@app.route('/index2')
def index2():
    """一对多的嵌套序列化"""
    author = Author(
        name = 'jia',
        email= 'jia@qq.com'
    )

    author.books = [
        Book(
            title='小米手机1',
            author=author
        ),
        Book(
            title='小米手机2',
            author=author
        ),
        Book(
            title='小米手机3',
            author=author
        )
    ]

    aus = AuthorSchema()
    res1 = aus.dump(author)
    res2 = aus.dumps(author)
    print(res1)
    # {'name': 'jia', 'books': [{'title': '小米手机1'}, {'title': '小米手机2'}, {'title': '小米手机3'}], 'email': 'jia@qq.com'}
    print(res2)

    return 'hello world!!'


@app.route('/')
def index():
    """多对多的嵌套序列化,就是两个一对多嵌套"""
    author1 = Author(
        name = 'jia',
        email= 'jia@qq.com'
    )

    course1 = Course(name='散文小说')

    # 作者-写作课程 一对多嵌套
    author1.courses = [
        course1,
        Course(name='都市小说'),
        Course(name='玄幻小说'),
    ]

    # 写作课程-作者 一对多嵌套
    course1.authors = [
        author1,
        Author(
            name='xiao',
            email='xiao@qq.com'
        ),
        Author(
            name='dada',
            email='dada@qq.com'
        ),
    ]

    """调用构造器"""
    # 作者构造器
    aus = AuthorSchema2()
    res1 = aus.dump(author1)
    print(res1)
# {'courses': [{'name': '散文小说'}, {'name': '都市小说'}, {'name': '玄幻小说'}], 'email': 'jia@qq.com', 'name': 'jia'}


    # 写作课程构造器
    cs = CourseSchema()
    res2 = cs.dump(course1)
    print(res2)
# {'authors': [{'email': 'jia@qq.com', 'name': 'jia'}, {'email': 'xiao@qq.com', 'name': 'xiao'}, {'email': 'dada@qq.com', 'name': 'dada'}], 'name': '散文小说'}

    return 'hello world!!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=6100, debug=True)


自关联 - 自嵌套序列化

​ 一张数据表中存在主外键的情况,就是自关联。

当前,我们要在构造器/序列化器中完成这种子关联的序列化,我们可以称之为自嵌套序列化

id name parent/pid
1 河北省 0
2 广东省 0
3 石家庄 1
4 深圳市 2
5 南山区 4
6 裕华区 3

把上面的表可以拆分成2张或3张虚拟的表

id name
1 河北省
2 广东省
id name parent/pid
3 石家庄 1
4 深圳市 2
5 南山区 4
6 裕华区 3

自嵌套序列化 代码:

from flask import Flask
from flask_marshmallow import Marshmallow
from marshmallow import Schema, fields

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

ms = Marshmallow(app)

"""模拟模型类"""
# 作者类
class Area(object):
    def __init__(self, id, name):
        self.name = name
        self.id = id
        self.subs = []   # 自关联外键

"""创建构造器"""
# 地区自嵌套
class AreaSchema(Schema):
    name = fields.String()
    id = fields.Integer()

    # # 方式一:通用嵌套
    # subs = fields.List(fields.Nested(lambda : AreaSchema(exclude=['subs'])))

    # # 方式二:使用自身构造器作为外键的方式,并指定序列化的字段
    # subs = fields.Nested('self', only=('name','id'), many=True)

    # # 方式三: 也可以在方式二的基础上,不指定字段
    # subs = fields.Nested(lambda : AreaSchema(many=True))

    # 方式四:使用Pluck字段可以用单个字段来替换嵌套的数据,返回列表数据
    subs = fields.Pluck("self",'name', many=True)



@app.route('/')
def index():
    """自嵌套序列化"""
    area1 = Area( id=1, name="广东省" )
    area2 = Area( id=2, name="广州市" )
    area3 = Area( id=3, name="白云区" )
    area4 = Area( id=4, name="荔湾区" )

    area1.subs = [area2]
    area2.subs = [area3,area4]

    # 调用构造器
    ars = AreaSchema()
    res1 = ars.dump(area1)
    res2 = ars.dump(area2)
    print(res1)
    print(res2)

    return 'hello world!!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=6100, debug=True)


基于Schema完成数据反序列化转换

通常使用, 代码:

import json

from flask import Flask
from flask_marshmallow import Marshmallow
from marshmallow import Schema, fields, validate

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

ms = Marshmallow(app)

"""创建构造器"""
# 反序列化 - 主要用于客户端传来的数据进行校验
class UserSchema(Schema):
    # 定义需要校验的字段及校验规则,反序列化required必须填写的数据
    name = fields.Str(required=True, validate=validate.Length(min=2)) 
    age = fields.Integer(validate=validate.Range(min=15, max=30))
    permission = fields.Str(validate=validate.OneOf(['read', 'write']))


@app.route('/')
def index():
    us = UserSchema()
    data = {
        "name":'jia',
        'age': 16,
        'permission': 'read'
    }
    res1 = us.load(data) # 返回字典
    res2 = us.loads(json.dumps(data)) # 参数是json数据,返回字典
    print(type(res1), res1)
    # <class 'dict'> {'age': 16, 'name': 'jia', 'permission': 'read'}
    print(type(res2), res2)
    # <class 'dict'> {'age': 16, 'name': 'jia', 'permission': 'read'}


    return 'hello world!!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=6100, debug=True)

反序列化时转换/忽略部分数据

from flask import Flask
from flask_marshmallow import Marshmallow
from marshmallow import Schema, fields, validate

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

ms = Marshmallow(app)

"""创建构造器"""
# 反序列化 - 主要用于客户端传来的数据进行校验
class UserSchema(Schema):
    # 定义需要校验的字段及校验规则,反序列化required必须填写的数据
    name = fields.Str(required=True, validate=validate.Length(min=2))
    age = fields.Integer(validate=validate.Range(min=15, max=30))
    permission = fields.Str(validate=validate.OneOf(['read', 'write']))


@app.route('/')
def index():
    us = UserSchema()
    data = {
        'age': 16,
        'permission': 'read'
    }
    # 反序列化忽略name必填项, partial后是元组数据
    res1 = us.load(data, partial=('name',)) # 返回字典
    print(type(res1), res1)
    # <class 'dict'> {'age': 16, 'permission': 'read'}

    return 'hello world!!'

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=6100, debug=True)


设置字段只在序列化或反序列化阶段才启用

class UserSchema(Schema):
    name = fields.Str()
    # load_only 只在反序列化校验时使用, dump_only 只在序列化输出时使用
    password = fields.Str(load_only=True) # 相当于只写字段 "write-only"
    created_time = fields.DateTime(dump_only=True) # 相当于只读字段 "read-only"

反序列化阶段的钩子方法

marshmallow一共提供了4个钩子方法:

pre_dump([fn,pass_many]) 注册要在序列化对象之前调用的方法,它会在序列化对象之前被调用。
pre_load([fn,pass_many]) 在反序列化对象之前,注册要调用的方法,它会在验证数据之前调用。

post_dump([fn,pass_many,pass_original]) 注册要在序列化对象后调用的方法,它会在对象序列化后被调用。
post_load([fn,pass_many,pass_original]) 注册反序列化对象后要调用的方法,它会在验证数据之后被调用。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import Schema, fields, validate, pre_load, pre_dump, post_load,post_dump
from datetime import datetime
from werkzeug.security import generate_password_hash


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)
ms = Marshmallow(app)

"""构建模型类"""
class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), index=True, comment="用户名")
    age = db.Column(db.Integer, comment="年龄")
    password = db.Column(db.String(255), comment="登录密码")
    mobile = db.Column(db.String(20), comment="手机号")
    created_time = db.Column(db.DateTime, default=datetime.now, comment="创建时间")

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self.name)

"""创建构造器"""
# 反序列化 - 主要用于客户端传来的数据进行校验
class UserSchema(Schema):
    name = fields.String(validate=validate.Length(min=2))
    age = fields.Integer(required=True)
    password = fields.String(load_only=True)
    mobile = fields.String()
    created_time = fields.DateTime(format='%Y-%m-%d %H:%M:%S')

    @pre_load
    def pre_load(self,data,**kwargs):
        """反序列化的前置钩子,会在数据验证之前执行"""
        print(f"kwargs={kwargs}") # kwargs={'many': False, 'partial': False}
        # 反序列化之前添加了created_time字段值
        data["created_time"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        # 必须返回数据
        return data

    @post_load
    def post_load(self,data,**kwargs):
        """反序列化的后置钩子,会在数据验证之后执行"""
        # 在进入数据库之前,密码加密
        data['password'] = generate_password_hash(data['password'])
        
        # 在进入数据库之前,还可以删除不必要的字段,例如验证码,确认密码
		# 删除掉不必要的字段
        # del data['password2']
        # data.pop('sms_code')
          
        # 把数据存入数据库
        instance = User(**data)
        db.session.add(instance)
        db.session.commit()

        return instance

    # 序列化前后执行差别不太大
    @pre_dump
    def pre_dump(self,data,**kwargs):
        """序列化的前置钩子,会在数据转换之前执行"""
        # 隐藏手机号,不让客户段得到完整号码
        data.mobile = data.mobile[:3] + '****' + data.mobile[-4:]
        return data

    @post_dump
    def post_dump(self,data,**kwargs):
        """序列化的后置钩子,会在数据转换之后执行"""
        data["age"] = f"{data['age']}岁"
        return data

@app.route('/')
def index():
    us = UserSchema()
    data = {
        'name':'jia',
        'age': 20,
        'password':'123456',
        'mobile':'13312345678'
    }
    # 反序列化
    instance = us.load(data)
    print(f'instance = {instance}') # instance = <User: jia>
	# 序列化
    res = us.dump(instance) # 序列化输出时没有了password
    print(f'res = {res}')
# res = {'name': 'jia', 'mobile': '133****5678', 'age': '20岁', 'created_time': '2021-06-04 16:11:29'}
    
    return 'hello world!!'

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', port=6100, debug=True)


反序列化阶段对数据进行验证

基于内置验证器进行数据验证

内置验证器 描述
validate.Email(*, error) 邮箱验证
validate.Equal(comparable, *, error) 判断值是否相等
validate.Length(min, max, *, equal, error) 值长度/大小验证
validate.OneOf(choices, labels, *, error) 选项验证(给定几个值范围校验)
validate.Range([min, max],error) 范围验证
validate.Regexp(regex, bytes, Pattern][, flags], error) 正则验证
validate.URL(*, relative, schemes, Set[str]]] = None,error …) 验证是否为URL

内置验证器主要写在字段选项中,代码:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import Schema, fields, validate
from datetime import datetime
from werkzeug.security import generate_password_hash


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)
ms = Marshmallow(app)

"""构建模型类"""
class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), index=True, comment="用户名")
    age = db.Column(db.Integer, comment="年龄")
    password = db.Column(db.String(255), comment="登录密码")
    mobile = db.Column(db.String(20), comment="手机号")
    created_time = db.Column(db.DateTime, default=datetime.now, comment="创建时间")

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self.name)

"""创建构造器"""
# 反序列化 - 主要用于客户端传来的数据进行校验
class UserSchema(Schema):
    name = fields.String(validate=validate.Length(min=2))
    age = fields.Integer(required=True, validate=validate.Range(min=16,max=100))
    password = fields.String(load_only=True, validate=validate.Length(min=6,max=16))
    mobile = fields.String(validate=validate.Regexp('^1[3-9]\d{9}$', error='手机号码格式不正确!'))
    created_time = fields.DateTime(format='%Y-%m-%d %H:%M:%S')

@app.route('/')
def index():
    us = UserSchema()
    data = {
        'name':'jia',
        'age': 20,
        'password':'123456',
        'mobile':'1331234567' # {'mobile': ['手机号码格式不正确!']}
    }
    # 反序列化
    instance = us.load(data)
    print(f'instance = {instance}')


    return 'hello world!!'

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', port=6100, debug=True)


自定义验证方法

通过context或者构造器实例对象进行参数传递

import random

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import Schema, fields, validate, validates, validates_schema, ValidationError,post_load


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)
ms = Marshmallow(app)

"""构建模型类"""
class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), index=True, comment="用户名")
    email = db.String(db.String(255))
    age = db.Column(db.Integer, comment="年龄")
    password = db.Column(db.String(255), comment="登录密码")
    mobile = db.Column(db.String(20), comment="手机号")

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self.name)

"""创建构造器"""
# 反序列化 - 主要用于客户端传来的数据进行校验
class UserSchema(Schema):
    name = fields.String()
    age = fields.Integer()
    email = fields.String()
    password = fields.String() # 密码
    password2 = fields.String() # 确认密码
    mobile = fields.String(validate=validate.Regexp("^1[3-9]\d{9}$",error="手机号格式不正确!"))
    """
    返回错误信息格式:键是字段名(固定了),值是错误信息列表
    {'mobile': ['手机号码格式不正确!']}
    """


    # 针对单个指定字段的值进行验证 - 手机号码验证是否被注册
    @validates('mobile')
    def validate_mobile(self, mobile):
        user = User.query.filter(User.mobile == mobile).first()
        if(user):
            raise ValidationError('手机号码已经被注册!!')
            """
            返回错误信息格式:键是字段名(固定了),值是错误信息列表
            {'mobile': ['手机号码已经被注册!!']}
            """

        return mobile

    # 针对多个字段的验证
    @validates_schema
    def validate_many(self, data, **kwargs):
        if(data['password'] != data['password2']):
             # 注意:验证失败以后,一定是raise抛出异常!!!不能是return
             # 指定抛出错误信息的 键和值
            raise ValidationError(field_name='password2', message='两次密码不一致')
            """
            返回错误信息格式:键是field_name自定义的,值是错误信息列表
             {'password2': ['两次密码不一致']}
            """
        return data


    # 反序列化后的钩子方法
    @post_load
    def post_load(self,data,**kwargs):
        print("num=%s" % self.num) # num=70
        print(self.context) # {'num': 70}

        # 删除掉不必要的字段
        # del data['password2']
        data.pop('password2')

        # 把校验过的数据储存到数据库中
        instance = User(**data)
        db.session.add(instance)
        db.session.commit()

        return instance # 返回用户对象


@app.route('/')
def index():
    num = random.randint(1, 100)

    # 1. 如果将来在开发中有部分数据需要传递到构造器中进行调用,在实例化构造器时通过context传递参数,在构造器内部通过self.context调用
    us = UserSchema(context={'num':num})
    # 2. 如果将来在开发中有部分数据需要传递到构造器中进行调用,可以作为构造器对象的属性进行传递,在构造器内部通过self.num调用
    us.num = num

    data = {
        'name':'jia',
        'age': 20,
        'password':'123456',
        'password2':'123456',
        'mobile':'13312345677'
    }
    instance = us.load(data)
    print(f'instance = {instance}') # instance = <User: jia>


    return 'hello world!!'

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', port=6100, debug=True)

模型构造器 - 常用

官方文档:https://github.com/marshmallow-code/marshmallow-sqlalchemy

https://marshmallow-sqlalchemy.readthedocs.io/en/latest/

注意:flask_marshmallow在0.12.0版本以后已经移除了ModelSchema和TableSchema这两个模型构造器类,官方转而推荐了使用SQLAlchemyAutoSchema和SQLAlchemySchema这2个类,前后两者用法类似。

模型构造器实际上是Schema的子类,所以前面所学习的自定义构造器字段,验证器,验证方法,钩子方法等等都可以写进来一起使用。

基于SQLAlchemySchema创建模型构造器

代码:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import fields, validate, validates, validates_schema, ValidationError,post_load
# 引入模型构造器
from marshmallow_sqlalchemy import SQLAlchemySchema, auto_field

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)
ms = Marshmallow(app)

"""构建模型类"""
class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), index=True, comment="用户名")
    email = db.Column(db.String(255), comment='邮箱')
    age = db.Column(db.Integer, comment="年龄")
    password = db.Column(db.String(255), comment="登录密码")
    mobile = db.Column(db.String(20), comment="手机号")

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self.name)

"""创建模型构造器"""
# 反序列化 - 主要用于客户端传来的数据进行校验
class UserSchema(SQLAlchemySchema):
    # auto_field 一般可以称之为继承字段,它会自动到对应的模型中继承同名的字段声明和约束
    id = auto_field() # 主键会被自动设置为默认只读[dump_only]
    name = auto_field()
    age = auto_field()
    email = auto_field()
    password = auto_field()# 密码

    # 如果模型中没有声明的字段,则还是按照之前自定义构造器的方式自己填写
    password2 = fields.String() # 确认密码

    # 除了可以复制模型对应字段的信息和数据类型以外,我们也可以增加补充说明
    mobile = fielda.String(validate=validate.Regexp("^1[3-9]\d{9}$",error="手机号格式不正确!"))

    class Meta:
        # 指定要校验模型类
        model = User
        # 反序列化阶段时,True会直接返回模型对象,False会返回字典
        load_instance = True
        # 输出模型对象时同时对外键,是否也一并进行处理
        include_relationships = True
        # 序列化阶段是否也一并返回主键,也就是是否返回ID
        include_fk = True
		# 当前数据库连接的session会话对象 - 实例化模型时就不用再传入session参数了
        sqla_session = db.session

    # 针对单个指定字段的值进行验证 - 手机号码验证是否被注册
    @validates('mobile')
    def validate_mobile(self, mobile):
        user = User.query.filter(User.mobile == mobile).first()
        if(user):
            raise ValidationError('手机号码已经被注册!!')
            """
            返回错误信息格式:键是字段名(固定了),值是错误信息列表
            {'mobile': ['手机号码已经被注册!!']}
            """

        return mobile

    # 针对多个字段的验证
    @validates_schema
    def validate_many(self, data, **kwargs):
        if(data['password'] != data['password2']):
             # 注意:验证失败以后,一定是raise抛出异常!!!不能是return
             # 指定抛出错误信息的 键和值
            raise ValidationError(field_name='password2', message='两次密码不一致')
            """
            返回错误信息格式:键是field_name自定义的,值是错误信息列表
             {'password2': ['两次密码不一致']}
            """
        return data


    # 反序列化后的钩子方法
    # 根据Meta中的load_instance = True才知道,instance是字典数据还是模型对象
    @post_load
    def post_load(self,instance,**kwargs):
        # 把数据添加到数据库
        db.session.add(instance)
        db.session.commit()

        return instance # 返回用户对象


@app.route('/')
def index():

    data = {
        'name':'熊大',
        'age': 22,
        'email': 'xiongda@qq.com',
        'password':'123456',
        'password2':'123456',
        'mobile':'13312345678'
    }
    # 实例化模型构造器,必须传入session参数 或者在Meta中指定session数据
    # us = UserSchema(session=db.session)
    us = UserSchema()
    # 反序列化校验
    instance = us.load(data)
    print(f'instance = {instance}') # instance = <User: 熊大>

    return 'hello world!!'

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', port=6100, debug=True)



基于SQLAlchemyAutoSchema创建模型构造器

用法:

class 构造器类名(SQLAlchemyAutoSchema):
    class Meta:
        model = 模型类名    # table = models.Album.__table__
        include_relationships = True  # 输出模型对象时同时对外键,是否也一并进行处理
        include_fk = True # 序序列阶段是否也一并返回主键
        load_instance = True  # 反序列化阶段时,直接返回模型对象
        sqla_session = db.session # 数据库连接会话对象
        # fields= ["id","name"] # 启动的字段列表
        exclude = ["id","name"] # 排除字段列表

代码:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_marshmallow import Marshmallow
from marshmallow import fields, validate, validates, validates_schema, ValidationError,post_load
# 引入模型构造器
from marshmallow_sqlalchemy import SQLAlchemyAutoSchema

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/db101?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db = SQLAlchemy(app)
ms = Marshmallow(app)

"""构建模型类"""
class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), index=True, comment="用户名")
    email = db.Column(db.String(255), comment='邮箱')
    age = db.Column(db.Integer, comment="年龄")
    password = db.Column(db.String(255), comment="登录密码")
    mobile = db.Column(db.String(20), comment="手机号")

    def __repr__(self):
        return "<%s: %s>" % (self.__class__.__name__, self.name)

"""创建模型构造器"""
# 反序列化 - 主要用于客户端传来的数据进行校验
class UserSchema(SQLAlchemyAutoSchema):
    # 根据项目需要定义字段规则,没定义会默认继承模型类中的字段约束
    # 如果模型中没有声明的字段,则还是按照之前自定义构造器的方式自己填写
    password2 = fields.String() # 确认密码
    mobile = fields.String(validate=validate.Regexp("^1[3-9]\d{9}$",error="手机号格式不正确!"))

    class Meta:
        # 指定要校验模型类
        model = User

        # 白名单字段,序列化器中使用的字段都要在这里声明 - 列表形式
        fields = ['id','name','password','password2','email','age','mobile']

        # # 黑名单字段,序列化器中不使用的字段,则剩余字段就是全部使用的了
        # exclude = ['age']

        # 反序列化阶段时,True会直接返回模型对象,False会返回字典
        load_instance = True
        # 输出模型对象时同时对外键,是否也一并进行处理
        include_relationships = True
        # 序列化阶段是否也一并返回主键,也就是是否返回ID
        include_fk = True
        # 当前数据库连接的session会话对象 - 实例化模型时就不用再传入session参数了
        sqla_session = db.session


    # 针对单个指定字段的值进行验证 - 手机号码验证是否被注册
    @validates('mobile')
    def validate_mobile(self, mobile):
        user = User.query.filter(User.mobile == mobile).first()
        if(user):
            raise ValidationError('手机号码已经被注册!!')
            """
            返回错误信息格式:键是字段名(固定了),值是错误信息列表
            {'mobile': ['手机号码已经被注册!!']}
            """

        return mobile

    # 针对多个字段的验证
    @validates_schema
    def validate_many(self, data, **kwargs):
        if(data['password'] != data['password2']):
             # 注意:验证失败以后,一定是raise抛出异常!!!不能是return
             # 指定抛出错误信息的 键和值
            raise ValidationError(field_name='password2', message='两次密码不一致')
            """
            返回错误信息格式:键是field_name自定义的,值是错误信息列表
             {'password2': ['两次密码不一致']}
            """
        return data


    # 反序列化后的钩子方法
    # 根据Meta中的load_instance = True才知道,instance是字典数据还是模型对象
    @post_load
    def post_load(self,instance,**kwargs):
        # 把数据添加到数据库
        db.session.add(instance)
        db.session.commit()

        return instance # 返回用户对象


@app.route('/')
def index():

    data = {
        'name':'熊大',
        'age': 22,
        'email': 'xiongda@qq.com',
        'password':'123456',
        'password2':'1234567',
        'mobile':'13312345676'
    }
    # 实例化模型构造器,必须传入session参数 或者在Meta中指定session数据
    # us = UserSchema(session=db.session)
    us = UserSchema()
    instance = us.load(data)
    print(f'instance = {instance}') # instance = <User: 熊大>

    return 'hello world!!'

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(host='0.0.0.0', port=6100, debug=True)


posted @ 2021-06-04 19:33  十九分快乐  阅读(1228)  评论(0编辑  收藏  举报