marshmallow——快速入门

本文介绍了Python库marshmallow的快速入门,包括定义schema、序列化与反序列化、异常处理、自定义验证规则等内容。讲解了如何通过字典定义序列化规则,使用Loading进行反序列化,以及如何处理未知字段和指定默认值。此外,还提到了如何自定义异常,处理序列化和反序列化的key映射,以及如何确保序列化输出的顺序。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

定义一个 schema

import datetime as dt


class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.created_at = dt.datetime.now()

    def __repr__(self):
        return "<User(name={self.name!r})>".format(self=self)

首先我们定义了一个 User 来代替数据库中的模型,然后根据这个模型来定义对应的序列化分序列化规则:

常用方式

from marshmallow import Schema, fields

class UserSchema(Schema):
    name = fields.Str()
    email = fields.Email()
    created_at = fields.DateTime()

通过字典来进行定义

from marshmallow import Schema, fields

UserSchema = Schema.from_dict(
    {
     	"name": fields.Str(), 
     	"email": fields.Email(), 
     	"created_at": fields.DateTime()
    }
)

序列化

通过 Loading 来反序列化

默认情况下,load 方法会将传入字典当中的 value 转为为指定的数据类型:

user_data = {
    "created_at": "2014-08-11T05:26:03.869245",
    "email": "ken@yahoo.com",
    "name": "Ken",
}
schema = UserSchema()
result = schema.load(user_data)
pprint(result)
# {'name': 'Ken',
#  'email': 'ken@yahoo.com',
#  'created_at': datetime.datetime(2014, 8, 11, 5, 26, 3, 869245)}

从上面我们可以看出,时间的字符串被转化为了 datatime.datetime

通过 validate 参数进行限制

当前端传入内容满足对应的数据类型时,如果我们还希望加入一些内容的限制时,此时我们就可以通过 **validate **参数来进行更多的要求。

from marshmallow import Schema, fields, validate, ValidationError


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


in_data = {"name": "", "permission": "invalid", "age": 71}
try:
    UserSchema().load(in_data)
except ValidationError as err:
    pprint(err.messages)
    # {'age': ['Must be greater than or equal to 18 and less than or equal to 40.'],
    #  'name': ['Shorter than minimum length 1.'],
    #  'permission': ['Must be one of: read, write, admin.']}

有关自定义异常条件的书写方式,详见下文:自定义异常

注意: validate 的限制只会在反序列化的时候才会触发。而序列化的内容默认都是有效的。

Required 参数

通过传递required = True 来对指定的序列化字段进行条件的限制。

**说明:**该限制只在序列化的时候触发

同时,我们也可以对 required 条件不满足的时候抛出的异常进行第自定义:

from marshmallow import Schema, fields, ValidationError


class UserSchema(Schema):
    name = fields.String(required=True)
    age = fields.Integer(required=True, error_messages={
        "required": "Age is required."
    })
    city = fields.String(
        required=True,
        error_messages={"required": 
                        {"message": "City required", "code": 400}
                       },
    )
    email = fields.Email()

try:
    result = UserSchema().load({"email": "foo@bar.com"})
except ValidationError as err:
    print(err.messages)
    # {'age': ['Age is required.'],
    # 'city': {'code': 400, 'message': 'City required'},
    # 'name': ['Missing data for required field.']}

partial 参数

对应 Required 参数,如果我们在 Schema 中指定了 required=True,但是希望前端没有传入对应字段的时候,不进行 required 异常的检出,即跳过对应的字段:

1. 指定跳过缺失的字段

class UserSchema(Schema):
    name = fields.String(required=True)
    age = fields.Integer(required=True)


result = UserSchema().load({"age": 42}, partial=("name",))
# OR UserSchema(partial=('name',)).load({'age': 42})
print(result)  # => {'age': 42}

2. 跳过所有缺失字段

class UserSchema(Schema):
    name = fields.String(required=True)
    age = fields.Integer(required=True)


result = UserSchema().load({"age": 42}, partial=True)
# OR UserSchema(partial=True).load({'age': 42})
print(result)  # => {'age': 42}

处理未知的字段

默认的,如果接收的参数当中有 Schema 对象没有定义的字段时,默认将会抛出ValidationError。但是我们也是可以自定义它的行为。

通过 unknown 选项,其接收未知参数的处理方式将有以下三种:

  • RAISE(default):如果传入的参数没有定义的话,抛出一个异常。
  • EXCLUDE:将未指定的字段排除在外。
  • INCLUDE: 接收未定义的字段。

书写方式

在定义 Schema 时进行指定:

from marshmallow import Schema, INCLUDE

class UserSchema(Schema):
    class Meta:
        unknown = INCLUDE

在实例化Schema 对象时进行指定:

schema = UserSchema(unknown=INCLUDE)

在调用 load 方法时进行指定:

UserSchema().load(data, unknown=INCLUDE)

说明: 该三种指定方式的优先级从下到上,依次降低:

load 方式 > 实例对象传参 > Meta 当中指定

反序列化

通过 Dump 方法来进行序列化

user = User(name="Monty", email="monty@python.org")
schema = UserSchema()
result = schema.dump(user)
print(result)
# {"name": "Monty",
#  "email": "monty@python.org",
#  "created_at": "2014-08-17T14:54:16.049594+00:00"}

当然,我们也可以将数据序列化为 JSON 数据类型。

json_result = schema.dumps(user)
print(json_result)
# '{"name": "Monty", "email": "monty@python.org", "created_at": "2014-08-17T14:54:16.049594+00:00"}'

指定反序列化的内容

对于某个对象,我们并不想返回其所有的内容,这个时候我们就可以指定其返回的字段。这里我们通过 onlyexclude 参数来进行。

summary_schema = UserSchema(only=("name", "email"))
summary_schema.dump(user)
# {"name": "Monty", "email": "monty@python.org"}

反序列化为一个对象

from marshmallow import Schema, fields, post_load


class UserSchema(Schema):
    name = fields.Str()
    email = fields.Email()
    created_at = fields.DateTime()

    @post_load
    def make_user(self, data, **kwargs):
        return User(**data)

在 Schema 类当中定义被 @post_load装饰的实例方法,我们便可以将待反序列化的字段转化为对应的 User 对象。

user_data = {"name": "Ronnie", "email": "ronnie@stones.com"}
schema = UserSchema()
result = schema.load(user_data)
print(result)  # => <User(name='Ronnie')>

这个方式对于操作数据库十分方便,如果我们传入的对象反序列化之后,正好是一个 Model 类型的对象,这个时候就可以直接插入到数据库当中了。

一次性反序列化多个对象

我们可以通过给定 Schema 的实例对象传入 many = True,来表示一次性序列化多个对象。

user1 = User(name="Mick", email="mick@stones.com")
user2 = User(name="Keith", email="keith@stones.com")
users = [user1, user2]
schema = UserSchema(many=True)
result = schema.dump(users)  # OR UserSchema().dump(users, many=True)
pprint(result)
# [{'name': u'Mick',
#   'email': u'mick@stones.com',
#   'created_at': '2014-08-17T14:58:57.600623+00:00'}
#  {'name': u'Keith',
#   'email': u'keith@stones.com',
#   'created_at': '2014-08-17T14:58:57.600623+00:00'}]

指定默认值

默认值的设定分为序列化和反序列化两种情况:

  • 在对应字段中指定 missing 参数,其默认效果将在反序列化的时候触发,即后端接收前端参数的时候。

  • 在对应字段中指定default参数,其默认效果将在序列化的时候触发,即后端向前端返回数据的时候。

class UserSchema(Schema):
    id = fields.UUID(missing=uuid.uuid1)
    birthdate = fields.DateTime(default=dt.datetime(2017, 9, 29))


UserSchema().load({})
# {'id': UUID('337d946c-32cd-11e8-b475-0022192ed31b')}
UserSchema().dump({})
# {'birthdate': '2017-09-29T00:00:00+00:00'}

对序列化和反序列化的 key 进行指定

当我们序列化和反序列化之后返回内容中的 key 值不一致的时候,就需要data_key 这个参数来实现这一个需求。例如前端传入的字段为 emailAdress,而后端数据库当中模型的字段名为 email,这个时候我们就需要对这两者的 key 进行转化。

class UserSchema(Schema):
    name = fields.String()
    email = fields.Email(data_key="emailAddress")


s = UserSchema()

data = {"name": "Mike", "email": "foo@bar.com"}
result = s.dump(data)
# {'name': u'Mike',
# 'emailAddress': 'foo@bar.com'}

data = {"name": "Mike", "emailAddress": "foo@bar.com"}
result = s.load(data)
# {'name': u'Mike',
# 'email': 'foo@bar.com'}

异常

注意:

schema 类中定义的字段务必写成小写类型,否则容易出现奇怪的异常。

传入数据类型错误导致异常

例如我们将前端传入的数据进行反序列化,如果传入的 key-value 并不满足 Schema 当中定义的类型,此时将会抛出 Validation error

from marshmallow import ValidationError

try:
    result = UserSchema().load({"name": "John", "email": "foo"})
except ValidationError as err:
    print(err.messages)  # => {"email": ['"foo" is not a valid email address.']}
    print(err.valid_data)  # => {"name": "John"}

如果我们实例化 Schema 对象时指定 **many=True ** 的时候,此时我们传入一组待验证的数据,mashmallow 会分别对数据进行验证。并对不满足条件的数据指定编号并返回:

from marshmallow import Schema, fields, ValidationError


class BandMemberSchema(Schema):
    name = fields.String(required=True)
    email = fields.Email()


user_data = [
    {"email": "mick@stones.com", "name": "Mick"},
    {"email": "invalid", "name": "Invalid"},  # invalid email
    {"email": "keith@stones.com", "name": "Keith"},
    {"email": "charlie@stones.com"},  # missing "name"
]

try:
    BandMemberSchema(many=True).load(user_data)
except ValidationError as err:
    pprint(err.messages)
    # {1: {'email': ['Not a valid email address.']},
    #  3: {'name': ['Missing data for required field.']}}

自定义异常

当我们希望指定 validate,并根据传入的内容来进行异常的抛出的时候,就可以通过如下的方式:

from marshmallow import Schema, fields, ValidationError


def validate_quantity(n):
    if n < 0:
        raise ValidationError("Quantity must be greater than 0.")
    if n > 30:
        raise ValidationError("Quantity must not be greater than 30.")


class ItemSchema(Schema):
    quantity = fields.Integer(validate=validate_quantity)


in_data = {"quantity": 31}
try:
    result = ItemSchema().load(in_data)
except ValidationError as err:
    print(err.messages)  
    # => {'quantity': ['Quantity must not be greater than 30.']}

当然,我们也可以将 validate 函数直接写到指定的 Schema 类当中,并指定需要验证的字段。

from marshmallow import fields, Schema, validates, ValidationError


class ItemSchema(Schema):
    quantity = fields.Integer()

    @validates("quantity")
    def validate_quantity(self, value):
        if value < 0:
            raise ValidationError(
                "Quantity must be greater than 0.")
        if value > 30:
            raise ValidationError(
                "Quantity must not be greater than 30.")

边边角角

判断是否有效

有时候,我们并不想序列化传入的参数,而只是想看下该参数是否对应指定的类型,即查看其是否有效。这个时候,我们只需要调用 validate 方法即可。

errors = UserSchema().validate(
    {"name": "Ronnie", "email": "invalid-email"}
)
print(errors)  # {'email': ['Not a valid email address.']}

字段的隐式创建

fileds

如果传入的字段在 Python 当中已经有对应的内置数据类型,此时我们如果嫌麻烦,不想要再在 Schema 当中指定字段的类型,此时我们就可以通过 Meta当中的 fileds 参数:

class UserSchema(Schema):
    uppername = fields.Function(lambda obj: obj.name.upper())

    class Meta:
        fields = ("name", "email", "created_at", "uppername")

additions

还可以不用 fileds,而是使用 additions 来进行操作:

class UserSchema(Schema):
    uppername = fields.Function(lambda obj: obj.name.upper())

    class Meta:
        # No need to include 'uppername'
        additional = ("name", "email", "created_at")

有序输出

为了让序列化之后的数据顺序保持原状,此时我们可以通过 Meta 当中的 ordered 参数来进行指定。

from collections import OrderedDict
from pprint import pprint

from marshmallow import Schema, fields


class UserSchema(Schema):
    first_name = fields.String()
    last_name = fields.String()
    email = fields.Email()

    class Meta:
        ordered = True


u = User("Charlie", "Stones", "charlie@stones.com")
schema = UserSchema()
result = schema.dump(u)
assert isinstance(result, OrderedDict)
pprint(result, indent=2)
#  OrderedDict([('first_name', 'Charlie'),
#              ('last_name', 'Stones'),
#              ('email', 'charlie@stones.com')])

此时序列化出来的对象就不是 字典了,而是 OrderedDict。

下一篇:marshmallow——Nested

参考文章:Quickstart — marshmallow 3.12.1 documentation

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值