欢迎来到JIA的博客

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
pip install -U flask-sqlalchemy -i https://pipy.douban.com/simple
pip install -U flask-marshmallow

 

 

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/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(app)

class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    username = db.Column(db.String(255), index=True, comment="用户名")
    password = db.Column(db.String(255), comment="登录密码")
    mobile = db.Column(db.String(15), index=True, comment="手机号码")
    sex = db.Column(db.Boolean, default=True, comment="性别")
    email = db.Column(db.String(255), index=True, comment="邮箱")
    created_time = db.Column(db.DateTime, default=datetime.now, comment="创建时间")
    updated_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 ""

if __name__ == '__main__':
    with app.app_context():
        db.drop_all()
        db.create_all()
    app.run(debug=True,port=6000)

 

三、基本构造器(BaseSchema)

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

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

from datetime import datetime

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

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

ma = Marshmallow()
ma.init_app(app)

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

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

# marshmallow转换数据格式主要通过架构转换类来完成.
# 在marshmallow使用过程中所有的架构转换类必须直接或间接继承于Schema基类
from marshmallow import Schema,fields
class UserSchema(Schema):
    username = fields.String()
    mobile = fields.String()
    # sex = fields.Boolean()
    email = fields.Email()
    created_time = fields.DateTime()
    # updated_time = fields.DateTime()

@app.route("/")
def index():
    user = User(
        username="xiaoming",
        mobile="13312345677",
        sex=True,
        email="133123456@qq.com",
        created_time=datetime.now(),
        updated_time=datetime.now()
    )
    # print(user)
    # 调用marsh把模型转换成python基本数据格式[字典/列表]
    us = UserSchema() # 序列化多个数据,可以使用many=True
    ret1 = us.dump(user)  # 格式化输出成字典
    ret2 = us.dumps(user)  # 格式化输出成json字符串
    print(">>>> us.dump(user)  --> 字典")
    print(ret1)
    print(">>>> us.dumps(user) --> json字符串")
    print(ret2)
    """运行结果:
    >>>> us.dump(user)  --> 字典
{'created_time': '2021-03-02T11:07:45.520209', 'updated_time': '2021-03-02T11:07:45.520221', 'username': 'xiaoming', 'email': '133123456@qq.com', 'sex': True, 'mobile': '13312345677'}

>>>> us.dumps(user) --> json字符串
{"created_time": "2021-03-02T11:07:45.520209", "updated_time": "2021-03-02T11:07:45.520221", "username": "xiaoming", "email": "133123456@qq.com", "sex": true, "mobile": "13312345677"}
    """
    print(type(ret1), type(ret2))

    user1 = User(
        username="xiaoming1号",
        mobile="13312345677",
        sex=True,
        email="133123456@qq.com",
        created_time=datetime.now(),
        updated_time=datetime.now()
    )

    user2 = User(
        username="xiaoming2号",
        mobile="13312345677",
        sex=True,
        email="133123456@qq.com",
        created_time=datetime.now(),
        updated_time=datetime.now()
    )

    user_list = [user,user1,user2]
    us = UserSchema()
    data_list = us.dump(user_list,many=True)
    print(data_list)
    """运行结果:
    [{'mobile': '13312345677', 'created_time': '2021-03-02T11:12:50.128294', 'email': '133123456@qq.com', 'username': 'xiaoming'}, {'mobile': '13312345677', 'created_time': '2021-03-02T11:12:50.129576', 'email': '133123456@qq.com', 'username': 'xiaoming1号'}, {'mobile': '13312345677', 'created_time': '2021-03-02T11:12:50.129642', 'email': '133123456@qq.com', 'username': 'xiaoming2号'}]
    """
    return "基本使用:模型序列化"

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

 

 

4.1 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[[], …) 外键类型

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

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

 

4.3 构造器嵌套使用

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

 

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


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(app)


# 仿模型的一个类
class Author(object):
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.created_time = datetime.now()
        self.books = []    # 1对多的外键
        self.courses = []  # 多对多的外键

class Book(object):
    def __init__(self, title, author):
        self.title = title
        self.author = author # 用来代替MySQL中的外键关系

class Course(object):
    """写作课程"""
    def __init__(self,name):
        self.name = name
        self.students = []

class Book2Schema(Schema):
    title = fields.String()

class AuthorSchema(Schema):
    name = fields.String()
    email = fields.String()
    # 双向嵌套,例如找出所有属于该作者的所有书籍。注意:在大数据量时,数据返回将非常慢
    # books = fields.List(fields.Nested(lambda: BookSchema(exclude=["author"])))
    books = fields.List(fields.Nested(lambda: Book2Schema()))

class BookSchema(Schema):
    title = fields.String()
    # 使用lambda匿名函数,使只有在使用author时才会找AuthorSchema对象,不然可能出现先后调用,导致对象找不到的情况出现
    # 用来序列化外键对象,可以将外键对象按照指定的格式进行序列化,并且外键指定的格式可以完成任何支持的marshmallow操作
    author = fields.Nested(lambda: AuthorSchema())

class CourseSchema(Schema):
    name = fields.Str() # 等同于 fields.String()
    students = fields.List(fields.Nested(lambda: Author3Schema(exclude=["courses"])))

class Author3Schema(Schema):
    name = fields.String()
    email = fields.String()
    # 方式一:通用嵌套
    # courses = fields.List(fields.Nested(lambda: CourseSchema(exclude=["students"])))
    # 方式二:使用自身构造器作为外键的方式,并指定序列化的字段
    # courses = fields.Nested(CourseSchema, only=("name",), many=True)
    # 方式三:也可以在方式二的基础上,不指定字段
    # courses = fields.Nested(CourseSchema(many=True))
    # 方式四:使用Pluck字段可以用单个值来替换嵌套的数据,返回列表数据,只有name作为成员,这里的name就是外键模型中的字段,也可以是别的字段名,name仅仅是举例
    courses = fields.Pluck(CourseSchema, "name", many=True)

@app.route("/index1")
def index1():
    """多对1的嵌套序列化"""
    author = Author(
        name="小明",
        email="xiaomnig@qq.com",
    )

    book = Book(
        title = "小明历险记",
        author = author,
    )


    bs = BookSchema()
    book_dict1 = bs.dump(book)
    book_dict2 = bs.dumps(book)
    print(book_dict1)
    print(book_dict2)

    return "基本使用:嵌套序列化"

@app.route("/index2")
def index2():
    """1对多的嵌套序列化"""
    author = Author(
        name="小明",
        email="xiaomnig@qq.com"
    )

    author.books = [
        Book(
            title="小明历险记第1部",
            author=author,
        ),Book(
            title="小明历险记第2部",
            author=author,
        ),Book(
            title="小明历险记第3部",
            author=author,
        ),Book(
            title="小明历险记第4部",
            author=author,
        ),
    ]

    aus = AuthorSchema()
    ret1 = aus.dump(author)
    ret2 = aus.dumps(author)
    print(ret1)
    print(ret2)

    return "基本使用:嵌套序列化"

@app.route("/index3")
def index3():
    """多对多的嵌套序列化"""

    author1 = Author(
        name="xiaoming",
        email="xiaomnig@qq.com"
    )

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

    author1.courses = [
        course1,
        Course(name="言情小说"),
        Course(name="武侠小说"),
    ]

    course1.students = [
        author1,
        Author(
            name="xiaoli",
            email="xiaoli@qq.com"
        )
    ]

    aus = Author3Schema()
    ret1 = aus.dump(author1)
    print(ret1)


    cs = CourseSchema()
    ret2 = cs.dump(course1)
    print(ret2)

    return "基本使用:嵌套序列化"

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

 

 4.4 自关联

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

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

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

 

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

idname 
1 河北省  
2 广东省

 

idnameparent/pid
3 石家庄 1
4 深圳市 2
5 南山区 4
6 裕华区 3

 

代码:

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


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(app)

class Area(object):
    def __init__(self, id, name):
        self.id = id
        self.name = name
        self.sub = []

class AreaSchema(Schema):
    id = fields.Integer()
    name = fields.String()

    # 方式一:通用嵌套
    # sub = fields.List(fields.Nested(lambda: AreaSchema(exclude=["sub"])))
    # 方式二:使用自身构造器作为外键的方式,并指定序列化的字段
    # sub = fields.Nested("self", only=("id","name",), many=True)
    # 方式三:也可以在方式二的基础上,不指定字段
    # sub = fields.Nested(lambda: AreaSchema(many=True))
    # 方式四:使用Pluck字段可以用单个值来替换嵌套的数据,返回列表数据,只有name作为成员,这里的name就是外键模型中的字段,也可以是别的字段名,name仅仅是举例
    sub = 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="荔湾区" )

    area5 = Area( id=5, name="河北省" )
    area6 = Area( id=6, name="石家庄市" )
    area7 = Area( id=7, name="桥东区" )
    area8 = Area( id=8, name="桥西区" )

    area1.sub = [area2]
    area2.sub = [area3,area4]
    area5.sub = [area6]
    area6.sub = [area7,area8]

    ars = AreaSchema()
    ret1 = ars.dump(area1)
    ret2 = ars.dump(area2)
    print(ret1)
    print(ret2)

    return "基本使用:自嵌套序列化"

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

 

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

 代码:

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


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(app)

class UserSchema(Schema):
    name = fields.Str(validate=validate.Length(min=1))
    email = fields.Email()
    permission = fields.Str(validate=validate.OneOf(["read", "write", "admin"]))
    age = fields.Int(validate=validate.Range(min=18, max=40))

@app.route("/")
def index():
    us = UserSchema()
    user_data = {"email": "ronnie@stones.com","permission":"admin"}
    ret1 = us.load(user_data)
    print(ret1)
    return "基本使用:模型序反列化"

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

 

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

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


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(app)

class UserSchema(Schema):
    name = fields.Str(required=True, validate=validate.Length(min=1))
    email = fields.Email()
    permission = fields.Str(validate=validate.OneOf(["read", "write", "admin"]))
    age = fields.Int(validate=validate.Range(min=18, max=40))

@app.route("/")
def index():
    us = UserSchema()
    user_data = {"email": "ronnie@stones.com","permission":"admin"}
    ret1 = us.load(user_data,partial=("name",))
    print(ret1)
    return "基本使用:模型序反列化"

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

 

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

class UserSchema(Schema):
    name = fields.Str()
    # password is 
    password = fields.Str(load_only=True) # 相当于只写字段 "write-only"
    created_time = fields.DateTime(dump_only=True) # 相当于只读字段 "read-only"

代码:

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


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(app)

class User(object):
    def __init__(self,name,email,age,password,permission):
        self.name=name
        self.email=email
        self.age=age
        self.password=password
        self.permission=permission
        self.created_time = datetime.now()

class UserSchema(Schema):
    name = fields.Str(required=True, validate=validate.Length(min=1))
    email = fields.Email()
    age = fields.Int(validate=validate.Range(min=18, max=40))
    password = fields.Str(required=True, load_only=True)
    permission = fields.Str(validate=validate.OneOf(["read", "write", "admin"]))
    created_time = fields.DateTime(dump_only=True)

@app.route("/")
def index():
    us = UserSchema()
    user_data = {"email": "ronnie@stones.com","permission":"admin","password":"123456"}
    ret1 = us.load(user_data,partial=("name",))
    print(ret1)

    user = User(
     name="xiaoming",
     age=18,
     **user_data,
    )
    ret2 = us.dump(user)
    print(ret2)
    return "基本使用:模型序反列化"

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

 

5.3 反序列化阶段的钩子方法

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 werkzeug.security import generate_password_hash
from marshmallow import Schema,fields,validate,pre_load,pre_dump,post_dump,post_load
from datetime import datetime


app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(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=1))
    age = fields.Integer(required=True)
    password = fields.Str(load_only=True) # 相当于只写字段 "write-only"
    # created_time = fields.DateTime(dump_only=True) # 相当于只读字段 "read-only"
    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}")
        data["created_time"] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
        # 必须返回数据
        return data

    @post_load
    def post_load(self,data,**kwargs):
        """反序列化的后置钩子,会在数据验证之后执行"""
        # print(f"kwargs={kwargs}")
        # 在进入数据库之前,密码加密
        data["password"] = generate_password_hash(data["password"])
        # 在进入数据库之前,删除不必要的字段,例如验证码,确认密码

        return User(**data) # 也可以在此手动把字典转换成模型对象
        # 注意,此处没有提交数据库,所以这里仅仅是实例化了一个模型对象,并没有完成添加数据库操作
        # 如果要在反序列化后同步到数据库,则代码如下:
        # instance = User(**data)
        # db.session.add(instance)
        # db.session.commit()
        # return instance

    @pre_dump
    def pre_dump(self,data,**kwargs):
        """序列化的前置钩子,会在数据转换之前执行"""
        data.mobile = "130____0001"
        return data

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

@app.route("/")
def index():
    us = UserSchema()
    instance = us.load({"name":"xiaomingf","age":19,"password":"123456","mobile":"13000000001"})
    data = us.dump(instance)
    print(data)
    return "基本使用:钩子方法的调用"

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

 

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

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

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

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

 

from datetime import datetime
from flask import Flask
from flask_marshmallow import Marshmallow
from marshmallow import Schema,fields,validate,post_dump,post_load,pre_dump,pre_load
from flask_sqlalchemy import SQLAlchemy
from werkzeug.security import generate_password_hash
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofangapp?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
db.init_app(app)

ma = Marshmallow()
ma.init_app(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(validate=validate.Length(min=1))
    age = fields.Integer(required=True,validate=validate.Range(min=16,max=100))
    email = fields.String(validate=validate.Email())
    password = fields.Str(load_only=True,validate=validate.Length(min=6,max=16)) # 相当于只写字段 "write-only"
    mobile = fields.String(validate=validate.Regexp("^1[3-9]\d{9}$",error="手机号格式不正确!"))

@app.route("/")
def index():
    user_data = {"email":"xiaoming@qq.com","name": "小明", "age": 20,"password":"123456","mobile":"13312345678"}  # 报错
    us = UserSchema()
    # 反序列化
    instance = us.load(user_data)
    print(instance)
    return "hello"

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

 

 

6.2 自定义验证方法

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

 

import random
from datetime import datetime
from flask import Flask
from flask_marshmallow import Marshmallow
from marshmallow import Schema,fields,validate,validates,validates_schema,ValidationError,post_load,pre_load
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofangapp?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
db.init_app(app)

ma = Marshmallow()
ma.init_app(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.Str() # 密码
    password2 = fields.String() # 确认密码
    mobile = fields.String(validate=validate.Regexp("^1[3-9]\d{9}$",error="手机号格式不正确!"))

    # 针对单个指定字段的值进行验证
    @validates("mobile")
    def validate_mobile(self,mobile):
        if(mobile == "13312345678"):
            raise ValidationError("手机号已经被注册!!")
        return mobile

    # 针对多个字段的验证
    @validates_schema
    def validate(self,data,**kwargs):
        if(data["password"] != data["password2"]):
            # 注意:验证失败以后,一定是raise抛出异常!!!不能是return!!!!
            raise ValidationError(field_name="password2",message="密码和确认密码不一致!")
        return data

    @post_load
    def post_load(self,data,**kwargs):
        """反序列化验证后的钩子方法"""
        print("num=%s" % self.num)
        print(self.context)
        del data["password2"] # 删除掉不必要的字段
        return User(**data)

@app.route("/")
def index():
    user_data = {"email":"xiaoming@qq.com","name": "小明", "age": 20,"password":"123456","mobile":"13312345671","password2": "123456"}

    num = random.randint(1,100)
    # 如果将来在开发中有部分数据需要传递到构造器中进行调用,可以在实例化构造器时通过context传递参数进行,在构造器内部通过self.context调用
    us = UserSchema(context={"num":num})
    # 如果将来在开发中有部分数据需要传递到构造器中进行调用,可以作为构造器对象的属性进行传递
    us.num = num
    # 反序列化
    instance = us.load(user_data)
    print(instance)
    return "hello"

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

 

 

七、模型构造器(ModelSchema)

官方文档: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的子类,所以前面所学习的自定义构造器字段,验证器,验证方法,钩子方法等等都可以写进来一起使用。

7.1 基于SQLAlchemySchema创建模型构造器

代码:

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

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(app)


class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), nullable=False, index=True, comment="用户名")
    email = db.Column(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)

from marshmallow_sqlalchemy import SQLAlchemySchema,auto_field
from marshmallow import post_load,fields,validate
class UserSchema(SQLAlchemySchema):
    """模型构造器"""
    # 主键会被自动设置为默认自读[dump_only]
    id = auto_field()    # auto_field 一般可以称之为 继承字段,它会自动到对应的模型中继承同名的字段声明和约束
    name = auto_field()
    email = auto_field()
    age = auto_field()
    password = auto_field(validate=validate.Length(min=6,max=16))
    # # auto_field在使用过程中,除了可以复制模型对应字段的信息和数据类型以外,我们也可以增加补充说明
    mobile = auto_field(required=True,validate=validate.Regexp("^1[3-9]\d{9}$"))
    password2 = fields.String() # 如果模型中没有声明的字段,则还是按照之前自定义构造器的方式自己填写

    class Meta:
        model = User  # 模型类名 table = models.Album.__table__
        load_instance = True  # 反序列化阶段时,True会直接返回模型对象,False会返回字典
        include_relationships = True  # 输出模型对象时同时对外键,是否也一并进行处理
        include_fk = True  # 序列化阶段是否也一并返回主键,也就是是否返回ID
        
    @post_load
    def post_load(self,instance,**kwargs):
        """反序列化之后执行的钩子"""
        db.session.add(instance)
        db.session.commit()
        return instance

@app.route("/")
def index():
    user_data = {"name": "小明1", "mobile": "13312345676","age":18,"email":"133@qq.com","password":"123456","password2":"123456"}
    us = UserSchema(session=db.session)
    instance = us.load(user_data) # 注意:如果我们当前调用的是模型构造器,则必须传入session属性,属性是当前数据库的session会话对象
    print(instance)

    return "基本使用:反序列化阶段使用的验证器"

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

 

 

7.2 基于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

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"]="mysql://root:123@127.0.0.1:3306/mofang?charset=utf8mb4"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy()
ma = Marshmallow()
db.init_app(app)
ma.init_app(app)


class User(db.Model):
    __tablename__ = "tb_user"
    id = db.Column(db.Integer, primary_key=True, comment="主键ID")
    name = db.Column(db.String(255), nullable=False, index=True, comment="用户名")
    email = db.Column(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)

from marshmallow_sqlalchemy import SQLAlchemyAutoSchema,auto_field
from marshmallow import post_load,fields,validate
class UserSchema(SQLAlchemyAutoSchema):
    """模型构造器"""
    password2 = fields.String() # 如果模型中没有声明的字段,则还是按照之前自定义构造器的方式自己填写
    class Meta:
        model = User  # 模型类名
        # 白名单字段,序列化器中使用的字段都要在这里声明
        # fields = ["id","name","password2","email","age","mobile"]  # 字段列表

        # 黑名单字段,序列化器中不使用的字段,与白名单互斥,设置了该属性,则剩余字段就是全部使用的了
        exclude = ["age"]  # 构造器禁用的字段列表
        load_instance = True  # 反序列化阶段时,True会直接返回模型对象,False会返回字典
        include_relationships = True  # 输出模型对象时同时对外键,是否也一并进行处理
        include_fk = True  # 序列化阶段是否也一并返回主键,也就是是否返回ID
        sqla_session = db.session # 当前数据库连接的session会话对象

    @post_load
    def post_load(self,instance,**kwargs):
        """反序列化之后执行的钩子"""
        db.session.add(instance)
        db.session.commit()
        return instance

@app.route("/")
def index():
    user_data = {"name": "小明1", "mobile": "13312345676","email":"133@qq.com","password":"123456","password2":"123456"}
    us = UserSchema()
    instance = us.load(user_data) # 注意:如果我们当前调用的是模型构造器,则必须传入session属性,属性是当前数据库的session会话对象
    print(instance.id)

    return "基本使用:反序列化阶段使用的验证器"

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

 

 

 

 

 

 

 

posted @ 2021-06-16 16:47  讷言敏行~  阅读(1030)  评论(0)    收藏  举报