Marshmallow 官方文档 2024-01 全译全测

Marshmallow 官方文档全译全测通 2024-01

python 复制代码
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"

Marshmallow Quickstart 快速开始 ¶

This guide will walk you through the basics of creating schemas for serializing and deserializing data. 本指南将引导您了解创建序列化和反序列化数据模式的基础知识。

Declaring Schemas 声明模式 ¶

Let's start with a basic user "model". 让我们从基本的用户"模型"开始。

python 复制代码
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)

Create a schema by defining a class with variables mapping attribute names to Field objects. 通过定义一个类来创建一个架构,该类包含将属性名称映射到 Field 对象的变量。

python 复制代码
from marshmallow import Schema, fields


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

See also 也可以看看

For a full reference on the available field classes, see the API Docs. 有关可用字段类的完整参考,请参阅 API 文档。

Creating Schemas From Dictionaries 从字典创建模式 ¶

You can create a schema from a dictionary of fields using the from_dict method. 您可以使用 from_dict 方法从字段字典创建架构。

python 复制代码
from marshmallow import Schema, fields

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

from_dict is especially useful for generating schemas at runtime. from_dict 对于在运行时生成模式特别有用。

Serializing Objects ("Dumping") 序列化对象("转储") ¶

Serialize objects by passing them to your schema's dump method, which returns the formatted result. 通过将对象传递给架构的 dump 方法来序列化对象,该方法返回格式化结果。

python 复制代码
from pprint import pprint

user = User(name="Monty", email="monty@python.org")
schema = UserSchema()
result = schema.dump(user)
pprint(result)
# {"name": "Monty",
#  "email": "monty@python.org",
#  "created_at": "2014-08-17T14:54:16.049594+00:00"}
arduino 复制代码
{'created_at': '2024-01-12T10:46:25.007952',
 'email': 'monty@python.org',
 'name': 'Monty'}

You can also serialize to a JSON-encoded string using dumps. 您还可以使用 dumps 序列化为 JSON 编码的字符串。

python 复制代码
json_result = schema.dumps(user)
pprint(json_result)
# '{"name": "Monty", "email": "monty@python.org", "created_at": "2014-08-17T14:54:16.049594+00:00"}'
css 复制代码
('{"name": "Monty", "email": "monty@python.org", "created_at": '
 '"2024-01-12T10:46:25.007952"}')

Filtering Output 过滤输出 ¶

You may not need to output all declared fields every time you use a schema. You can specify which fields to output with the only parameter. 您可能不需要每次使用模式时都输出所有声明的字段。您可以使用 only 参数指定要输出的字段。

python 复制代码
summary_schema = UserSchema(only=("name", "email"))
summary_schema.dump(user)
# {"name": "Monty", "email": "monty@python.org"}
arduino 复制代码
{'name': 'Monty', 'email': 'monty@python.org'}

You can also exclude fields by passing in the exclude parameter. 您还可以通过传入 exclude 参数来排除字段。

Deserializing Objects ("Loading") 反序列化对象("加载") ¶

The reverse of the dump method is load, which validates and deserializes an input dictionary to an application-level data structure. dump 方法的反向方法是 load ,它验证输入字典并将其反序列化为应用程序级数据结构。

By default, load will return a dictionary of field names mapped to deserialized values (or raise a ValidationError with a dictionary of validation errors, which we'll revisit later). 默认情况下, load 将返回映射到反序列化值的字段名称字典(或引发带有验证错误字典的 ValidationError ,我们稍后会重新讨论)。

python 复制代码
from pprint import pprint

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)},
css 复制代码
{'created_at': datetime.datetime(2014, 8, 11, 5, 26, 3, 869245),
 'email': 'ken@yahoo.com',
 'name': 'Ken'}

Notice that the datetime string was converted to a datetime object. 请注意,日期时间字符串已转换为 datetime 对象。

Deserializing to Objects 反序列化为对象 ¶

In order to deserialize to an object, define a method of your Schema and decorate it with post_load. The method receives a dictionary of deserialized data. 为了反序列化为对象,请定义 Schema 的方法并用 post_load 装饰它。该方法接收反序列化数据的字典。

python 复制代码
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)

Now, the load method return a User instance. 现在, load 方法返回一个 User 实例。

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

Handling Collections of Objects 处理对象集合 ¶

Set many=True when dealing with iterable collections of objects. 在处理可迭代对象集合时设置 many=True

python 复制代码
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'}]
css 复制代码
[{'created_at': '2024-01-12T10:46:25.061700',  'email': 'mick@stones.com',  'name': 'Mick'}, {'created_at': '2024-01-12T10:46:25.062697',  'email': 'keith@stones.com',  'name': 'Keith'}]

Validation 验证 ¶

Schema.load() (and its JSON-decoding counterpart, Schema.loads()) raises a ValidationError error when invalid data are passed in. You can access the dictionary of validation errors from the ValidationError.messages attribute. The data that were correctly deserialized are accessible in ValidationError.valid_data. Some fields, such as the Email and URL fields, have built-in validation. 当传入无效数据时, Schema.load() (及其 JSON 解码对应项 Schema.loads() )会引发 ValidationError 错误。您可以从 ValidationError.messages 属性。可以在 ValidationError.valid_data 中访问正确反序列化的数据。某些字段(例如 EmailURL 字段)具有内置验证。

python 复制代码
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"}
arduino 复制代码
{'email': ['Not a valid email address.']}
{'name': 'John'}

When validating a collection, the errors dictionary will be keyed on the indices of invalid items. 验证集合时,错误字典将基于无效项目的索引。

python 复制代码
from pprint import pprint

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.']}}
css 复制代码
{1: {'email': ['Not a valid email address.']},
 3: {'name': ['Missing data for required field.']}}

You can perform additional validation for a field by passing the validate argument. There are a number of built-in validators in the marshmallow.validate module. 您可以通过传递 validate 参数对字段执行附加验证。 marshmallow.validate 模块中有许多内置验证器。

python 复制代码
from pprint import pprint

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.']}
arduino 复制代码
{'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.']}

You may implement your own validators. A validator is a callable that accepts a single argument, the value to validate. If validation fails, the callable should raise a ValidationError with a useful error message or return False (for a generic error message). 您可以实现自己的验证器。验证器是一个可调用的函数,它接受单个参数,即要验证的值。如果验证失败,可调用函数应引发一个带有有用错误消息的 ValidationError 或返回 False (对于一般错误消息)。

python 复制代码
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.']}
arduino 复制代码
{'quantity': ['Quantity must not be greater than 30.']}

You may also pass a collection (list, tuple, generator) of callables to validate. 您还可以将可调用对象的集合(列表、元组、生成器)传递给 validate

Warning 警告

Validation occurs on deserialization but not on serialization. To improve serialization performance, data passed to Schema.dump() are considered valid. 验证在反序列化时进行,但不在序列化时进行。为了提高序列化性能,传递给 Schema.dump() 的数据被视为有效。

See also 也可以看看

You can register a custom error handler function for a schema by overriding the handle_error method. See the Extending Schemas page for more info. 您可以通过重写 handle_error 方法为架构注册自定义错误处理程序函数。有关详细信息,请参阅扩展架构页面。

See also 也可以看看

Need schema-level validation? See the Extending Schemas page. 需要模式级验证吗?请参阅扩展架构页面。

Field Validators as Methods 字段验证器作为方法 ¶

It is sometimes convenient to write validators as methods. Use the validates decorator to register field validator methods. 有时将验证器编写为方法很方便。使用 validates 装饰器注册字段验证器方法。

python 复制代码
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.")

Required Fields 必填字段 ¶

Make a field required by passing required=True. An error will be raised if the the value is missing from the input to Schema.load(). 通过传递 required=True 使字段成为必填字段。如果 Schema.load() 的输入中缺少该值,则会引发错误。

To customize the error message for required fields, pass a dict with a required key as the error_messages argument for the field. 要自定义必填字段的错误消息,请传递带有 required 键的 dict 作为字段的 error_messages 参数。

python 复制代码
from pprint import pprint

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:
    pprint(err.messages)
    # {'age': ['Age is required.'],
    # 'city': {'code': 400, 'message': 'City required'},
    # 'name': ['Missing data for required field.']}
css 复制代码
{'age': ['Age is required.'],
 'city': {'code': 400, 'message': 'City required'},
 'name': ['Missing data for required field.']}

Partial Loading 部分加载 ¶

When using the same schema in multiple places, you may only want to skip required validation by passing partial. 当在多个地方使用相同的架构时,您可能只想通过传递 partial 来跳过 required 验证。

python 复制代码
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}
arduino 复制代码
{'age': 42}

You can ignore missing fields entirely by setting partial=True. 您可以通过设置 partial=True 完全忽略缺失的字段。

python 复制代码
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}
arduino 复制代码
{'age': 42}

Specifying Defaults 指定默认值 ¶

load_default specifies the default deserialization value for a field. Likewise, dump_default specifies the default serialization value. load_default 指定字段的默认反序列化值。同样, dump_default 指定默认序列化值。

python 复制代码
import uuid
class UserSchema(Schema):

    id = fields.UUID(load_default=uuid.uuid1)

    birthdate = fields.DateTime(dump_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'}
css 复制代码
{'id': UUID('c6f538d2-b0f4-11ee-a7cc-001a7dda7111')}






{'birthdate': '2017-09-29T00:00:00'}

Handling Unknown Fields 处理未知字段 ¶

By default, load will raise a ValidationError if it encounters a key with no matching Field in the schema. 默认情况下,如果 load 在架构中遇到没有匹配 Field 的键,则会引发 ValidationError

This behavior can be modified with the unknown option, which accepts one of the following: 可以使用 unknown 选项修改此行为,该选项接受以下其中一项:

  • RAISE (default): raise a ValidationError if there are any unknown fields RAISE (默认):如果有任何未知字段,则引发 ValidationError
  • EXCLUDE: exclude unknown fields EXCLUDE :排除未知字段
  • INCLUDE: accept and include the unknown fields INCLUDE :接受并包含未知字段

You can specify unknown in the class Meta of your Schema, 您可以在 Schema 的 Meta 类中指定 unknown

python 复制代码
from marshmallow import Schema, INCLUDE


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

at instantiation time, 在实例化时,

python 复制代码
schema = UserSchema(unknown=INCLUDE)

or when calling load. 或者当调用 load 时。

python 复制代码
UserSchema().load(data={}, unknown=INCLUDE)
{}

The unknown option value set in load will override the value applied at instantiation time, which itself will override the value defined in the class Meta . load 中设置的 unknown 选项值将覆盖实例化时应用的值,该值本身将覆盖类 Meta 中定义的值。

This order of precedence allows you to change the behavior of a schema for different contexts. 这种优先顺序允许您更改不同上下文的架构行为。

Validation Without Deserialization 无需反序列化的验证 ¶

If you only need to validate input data (without deserializing to an object), you can use Schema.validate(). 如果您只需要验证输入数据(无需反序列化为对象),则可以使用 Schema.validate()

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

"Read-only" and "Write-only" Fields "只读"和"只写"字段 ¶

In the context of a web API, the dump_only and load_only parameters are conceptually equivalent to "read-only" and "write-only" fields, respectively. 在 Web API 上下文中, dump_onlyload_only 参数在概念上分别相当于"只读"和"只写"字段。

python 复制代码
class UserSchema(Schema):
    name = fields.Str()
    # password is "write-only"
    password = fields.Str(load_only=True)
    # created_at is "read-only"
    created_at = fields.DateTime(dump_only=True)

Warning 警告

When loading, dump-only fields are considered unknown. If the unknown option is set to INCLUDE, values with keys corresponding to those fields are therefore loaded with no validation. 加载时,仅转储字段被视为未知。如果 unknown 选项设置为 INCLUDE ,则带有与这些字段对应的键的值将在不进行验证的情况下加载。

Specifying Serialization/Deserialization Keys 指定序列化/反序列化键 ¶

Schemas will (de)serialize an input dictionary from/to an output dictionary whose keys are identical to the field names. If you are consuming and producing data that does not match your schema, you can specify the output keys via the data_key argument. 模式将从/反序列化输入字典到输出字典,其键与字段名称相同。如果您正在使用和生成与您的架构不匹配的数据,您可以通过 data_key 参数指定输出键。

python 复制代码
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'}

Implicit Field Creation 隐式字段创建 ¶

When your model has many attributes, specifying the field type for every attribute can get repetitive, especially when many of the attributes are already native Python datatypes. 当您的模型具有许多属性时,为每个属性指定字段类型可能会重复,尤其是当许多属性已经是本机 Python 数据类型时。

The fields option allows you to specify implicitly-created fields. Marshmallow will choose an appropriate field type based on the attribute's type. fields 选项允许您指定隐式创建的字段。 Marshmallow 将根据属性的类型选择适当的字段类型。

Let's refactor our User schema to be more concise. 让我们重构我们的用户模式以使其更加简洁。

python 复制代码
class UserSchema(Schema):
    uppername = fields.Function(lambda obj: obj.name.upper())

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

Note that name will be automatically formatted as a String and created_at will be formatted as a DateTime. 请注意, name 将自动格式化为 Stringcreated_at 将自动格式化为 DateTime

Note 笔记

If instead you want to specify which field names to include in addition to the explicitly declared fields, you can use the additional option. 如果除了显式声明的字段之外您还想指定要包含哪些字段名称,则可以使用 additional 选项。

The schema below is equivalent to above: 下面的架构与上面的架构等效:

python 复制代码
class UserSchema(Schema):
    uppername = fields.Function(lambda obj: obj.name.upper())

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

Next Steps 下一步 ¶

  • Need to represent relationships between objects? See the Nesting Schemas page. 需要表示对象之间的关系?请参阅嵌套架构页面。
  • Want to create your own field type? See the Custom Fields page. 想要创建您自己的字段类型吗?请参阅自定义字段页面。
  • Need to add schema-level validation, post-processing, or error handling behavior? See the Extending Schemas page. 需要添加模式级验证、后处理或错误处理行为?请参阅扩展架构页面。
  • For example applications using marshmallow, check out the Examples page. 有关使用棉花糖的示例应用程序,请查看示例页面。

Nesting Schemas 嵌套模式 ¶

Schemas can be nested to represent relationships between objects (e.g. foreign key relationships). For example, a Blog may have an author represented by a User object. 模式可以嵌套来表示对象之间的关系(例如外键关系)。例如, Blog 可能有一个由 User 对象表示的作者。

python 复制代码
import datetime as dt


class User:
    def __init__(self, name, email):
        self.name = name
        self.email = email
        self.created_at = dt.datetime.now()
        self.friends = []
        self.employer = None


class Blog:
    def __init__(self, title, author):
        self.title = title
        self.author = author  # A User object

Use a Nested field to represent the relationship, passing in a nested schema. 使用 Nested 字段来表示关系,并传入嵌套架构。

python 复制代码
from marshmallow import Schema, fields


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


class BlogSchema(Schema):
    title = fields.String()
    author = fields.Nested(UserSchema)

The serialized blog will have the nested user representation. 序列化的博客将具有嵌套的用户表示。

python 复制代码
from pprint import pprint

user = User(name="Monty", email="monty@python.org")
blog = Blog(title="Something Completely Different", author=user)
result = BlogSchema().dump(blog)
pprint(result)
# {'title': u'Something Completely Different',
#  'author': {'name': u'Monty',
#             'email': u'monty@python.org',
#             'created_at': '2014-08-17T14:58:57.600623+00:00'}}
arduino 复制代码
{'author': {'created_at': '2024-01-12T10:46:25.250833',
            'email': 'monty@python.org',
            'name': 'Monty'},
 'title': 'Something Completely Different'}

Note 笔记

If the field is a collection of nested objects, pass the Nested field to List. 如果该字段是嵌套对象的集合,请将 Nested 字段传递给 List

python 复制代码
collaborators = fields.List(fields.Nested(UserSchema))

Specifying Which Fields to Nest 指定要嵌套的字段 ¶

You can explicitly specify which attributes of the nested objects you want to (de)serialize with the only argument to the schema. 您可以使用架构的 only 参数显式指定要序列化(反序列化)的嵌套对象的哪些属性。

python 复制代码
class BlogSchema2(Schema):
    title = fields.String()
    author = fields.Nested(UserSchema(only=("email",)))


schema = BlogSchema2()
result = schema.dump(blog)
pprint(result)
# {
#     'title': u'Something Completely Different',
#     'author': {'email': u'monty@python.org'}
# }
arduino 复制代码
{'author': {'email': 'monty@python.org'},
 'title': 'Something Completely Different'}

Dotted paths may be passed to only and exclude to specify nested attributes. 点线路径可以传递给 onlyexclude 以指定嵌套属性。

python 复制代码
class Site:
    def __init__(self, blog, url=None):
        self.blog = blog
    
python 复制代码
class SiteSchema(Schema):
    blog = fields.Nested(BlogSchema2)


site = Site(blog={
    'author': {'email': u'monty@python.org'},
    'title': 'Something Completely Different'
    })

schema = SiteSchema(only=("blog.author.email",))
result = schema.dump(site)
pprint(result)
# {
#     'blog': {
#         'author': {'email': u'monty@python.org'}
#     }
# }
arduino 复制代码
{'blog': {'author': {'email': 'monty@python.org'}}}

You can replace nested data with a single value (or flat list of values if many=True) using the Pluck field. 您可以使用 Pluck 字段将嵌套数据替换为单个值(如果 many=True 则为平面值列表)。

python 复制代码
class UserSchema(Schema):
    name = fields.String()
    email = fields.Email()
    friends = fields.Pluck("self", "name", many=True)


# create ``user`` ...
serialized_data = UserSchema().dump(user)
pprint(serialized_data)
# {
#     "name": "Steve",
#     "email": "steve@example.com",
#     "friends": ["Mike", "Joe"]
# }
deserialized_data = UserSchema().load(serialized_data)
pprint(deserialized_data)
# {
#     "name": "Steve",
#     "email": "steve@example.com",
#     "friends": [{"name": "Mike"}, {"name": "Joe"}]
# }
arduino 复制代码
{'email': 'monty@python.org', 'friends': [], 'name': 'Monty'}
{'email': 'monty@python.org', 'friends': [], 'name': 'Monty'}

Partial Loading 部分加载 ¶

Nested schemas also inherit the partial parameter of the parent load call. 嵌套架构还继承父 load 调用的 partial 参数。

python 复制代码
class UserSchemaStrict(Schema):
    name = fields.String(required=True)
    email = fields.Email()
    created_at = fields.DateTime(required=True)


class BlogSchemaStrict(Schema):
    title = fields.String(required=True)
    author = fields.Nested(UserSchemaStrict, required=True)


schema = BlogSchemaStrict()
blog = {"title": "Something Completely Different", "author": {}}
result = schema.load(blog, partial=True)
pprint(result)
# {'author': {}, 'title': 'Something Completely Different'}
arduino 复制代码
{'author': {}, 'title': 'Something Completely Different'}

You can specify a subset of the fields to allow partial loading using dot delimiters. 您可以指定字段的子集以允许使用点分隔符进行部分加载。

python 复制代码
author = {"name": "Monty"}
blog = {"title": "Something Completely Different", "author": author}
result = schema.load(blog, partial=("title", "author.created_at"))
pprint(result)
# {'author': {'name': 'Monty'}, 'title': 'Something Completely Different'}
arduino 复制代码
{'author': {'name': 'Monty'}, 'title': 'Something Completely Different'}

Two-way Nesting 双向嵌套 ¶

If you have two objects that nest each other, you can pass a callable to Nested. This allows you to resolve order-of-declaration issues, such as when one schema nests a schema that is declared below it. 如果您有两个相互嵌套的对象,则可以将可调用对象传递给 Nested 。这使您可以解决声明顺序问题,例如当一个模式嵌套在其下方声明的模式时。

For example, a representation of an Author model might include the books that have a many-to-one relationship to it. Correspondingly, a representation of a Book will include its author representation. 例如, Author 模型的表示可能包括与其具有多对一关系的书籍。相应地, Book 的表示将包括其作者表示。

python 复制代码
import random
from pprint import pprint


class BookSchema(Schema):
    id = fields.Int(dump_only=True)
    title = fields.Str()

    # Make sure to use the 'only' or 'exclude'
    # to avoid infinite recursion
    author = fields.Nested(lambda: AuthorSchema(only=("id", "name")))


class AuthorSchema(Schema):
    id = fields.Int(dump_only=True)
    name = fields.Str()

    books = fields.List(fields.Nested(BookSchema(exclude=("author",))))

# from mymodels import Author, Book
class Author:
    def __init__(self, name):
        self.id = random.randint(1, 100)
        self.name = name
        self.books = []

class Book:
    def __init__(self, title, author):
        self.id = random.randint(1, 100)
        self.title = title
        self.author = author

author = Author(name="William Faulkner")
book = Book(title="As I Lay Dying", author=author)
author.books.append(book)

book_result = BookSchema().dump(book)
pprint(book_result, indent=2)
# {
#   "id": 124,
#   "title": "As I Lay Dying",
#   "author": {
#     "id": 8,
#     "name": "William Faulkner"
#   }
# }

author_result = AuthorSchema().dump(author)
pprint(author_result, indent=2)
# {
#   "id": 8,
#   "name": "William Faulkner",
#   "books": [
#     {
#       "id": 124,
#       "title": "As I Lay Dying"
#     }
#   ]
# }
arduino 复制代码
{ 'author': {'id': 20, 'name': 'William Faulkner'},
  'id': 56,
  'title': 'As I Lay Dying'}
{ 'books': [{'id': 56, 'title': 'As I Lay Dying'}],
  'id': 20,
  'name': 'William Faulkner'}

You can also pass a class name as a string to Nested. This is useful for avoiding circular imports when your schemas are located in different modules. 您还可以将类名作为字符串传递给 Nested 。当您的架构位于不同的模块中时,这对于避免循环导入非常有用。

python 复制代码
# books.py
from marshmallow import Schema, fields


class BookSchema(Schema):
    id = fields.Int(dump_only=True)
    title = fields.Str()

    author = fields.Nested("AuthorSchema", only=("id", "title"))
# authors.py
from marshmallow import Schema, fields


class AuthorSchema(Schema):
    id = fields.Int(dump_only=True)
    title = fields.Str()

    books = fields.List(fields.Nested("BookSchema", exclude=("author",)))

Note 笔记

If you have multiple schemas with the same class name, you must pass the full, module-qualified path. 如果您有多个具有相同类名的架构,则必须传递完整的模块限定路径。

python 复制代码
author = fields.Nested("authors.BookSchema", only=("id", "title"))

Nesting A Schema Within Itself 在其自身内嵌套模式 ¶

If the object to be marshalled has a relationship to an object of the same type, you can nest the Schema within itself by passing a callable that returns an instance of the same schema. 如果要编组的对象与相同类型的对象有关系,则可以通过传递返回相同架构实例的可调用函数来将 Schema 嵌套在其自身内。

python 复制代码
class UserSchema(Schema):
    name = fields.String()
    email = fields.Email()
    # Use the 'exclude' argument to avoid infinite recursion
    employer = fields.Nested(lambda: UserSchema(exclude=("employer",)))
    friends = fields.List(fields.Nested(lambda: UserSchema()))


user = User("Steve", "steve@example.com")
user.friends.append(User("Mike", "mike@example.com"))
user.friends.append(User("Joe", "joe@example.com"))
user.employer = User("Dirk", "dirk@example.com")
result = UserSchema().dump(user)
pprint(result, indent=2)
# {
#     "name": "Steve",
#     "email": "steve@example.com",
#     "friends": [
#         {
#             "name": "Mike",
#             "email": "mike@example.com",
#             "friends": [],
#             "employer": null
#         },
#         {
#             "name": "Joe",
#             "email": "joe@example.com",
#             "friends": [],
#             "employer": null
#         }
#     ],
#     "employer": {
#         "name": "Dirk",
#         "email": "dirk@example.com",
#         "friends": []
#     }
# }
python 复制代码
{ 'email': 'steve@example.com',
  'employer': {'email': 'dirk@example.com', 'friends': [], 'name': 'Dirk'},
  'friends': [ { 'email': 'mike@example.com',
                 'employer': None,
                 'friends': [],
                 'name': 'Mike'},
               { 'email': 'joe@example.com',
                 'employer': None,
                 'friends': [],
                 'name': 'Joe'}],
  'name': 'Steve'}

Next Steps 下一步 ¶

  • Want to create your own field type? See the Custom Fields page. 想要创建您自己的字段类型吗?请参阅自定义字段页面。
  • Need to add schema-level validation, post-processing, or error handling behavior? See the Extending Schemas page. 需要添加模式级验证、后处理或错误处理行为?请参阅扩展架构页面。
  • For example applications using marshmallow, check out the Examples page. 有关使用棉花糖的示例应用程序,请查看示例页面。

Custom Fields 自定义字段 ¶

There are three ways to create a custom-formatted field for a Schema: 可以通过三种方式为 Schema 创建自定义格式的字段:

  • Create a custom Field class 创建自定义 Field
  • Use a Method field 使用 Method 字段
  • Use a Function field 使用 Function 字段

The method you choose will depend on the manner in which you intend to reuse the field. 您选择的方法将取决于您打算重用该字段的方式。

Creating A Field Class 创建字段类 ¶

To create a custom field class, create a subclass of marshmallow.fields.Field and implement its _serialize and/or _deserialize methods. 要创建自定义字段类,请创建 marshmallow.fields.Field 的子类并实现其 _serialize 和/或 _deserialize 方法。

python 复制代码
from marshmallow import fields, ValidationError


class PinCode(fields.Field):
    """Field that serializes to a string of numbers and deserializes
    to a list of numbers.
    """

    def _serialize(self, value, attr, obj, **kwargs):
        if value is None:
            return ""
        return "".join(str(d) for d in value)

    def _deserialize(self, value, attr, data, **kwargs):
        try:
            return [int(c) for c in value]
        except ValueError as error:
            raise ValidationError("Pin codes must contain only digits.") from error


class UserSchema(Schema):
    name = fields.String()
    email = fields.String()
    created_at = fields.DateTime()
    pin_code = PinCode()

Method Fields 方法字段 ¶

A Method field will serialize to the value returned by a method of the Schema. The method must take an obj parameter which is the object to be serialized. Method 字段将序列化为架构方法返回的值。该方法必须采用 obj 参数,该参数是要序列化的对象。

python 复制代码
class UserSchema(Schema):
    name = fields.String()
    email = fields.String()
    created_at = fields.DateTime()
    since_created = fields.Method("get_days_since_created")

    def get_days_since_created(self, obj):
        return dt.datetime.now().day - obj.created_at.day

Function Fields 函数字段 ¶

A Function field will serialize the value of a function that is passed directly to it. Like a Method field, the function must take a single argument obj. Function 字段将序列化直接传递给它的函数的值。与 Method 字段一样,该函数必须采用单个参数 obj

python 复制代码
class UserSchema(Schema):
    name = fields.String()
    email = fields.String()
    created_at = fields.DateTime()
    uppername = fields.Function(lambda obj: obj.name.upper())

Method and Function field deserialization MethodFunction 字段反序列化 ¶

Both Function and Method receive an optional deserialize argument which defines how the field should be deserialized. The method or function passed to deserialize receives the input value for the field. FunctionMethod 都接收一个可选的 deserialize 参数,该参数定义应如何反序列化字段。传递给 deserialize 的方法或函数接收字段的输入值。

python 复制代码
class UserSchema(Schema):
    # `Method` takes a method name (str), Function takes a callable
    balance = fields.Method("get_balance", deserialize="load_balance")

    def get_balance(self, obj):
        return obj.income - obj.debt

    def load_balance(self, value):
        return float(value)


schema = UserSchema()
result = schema.load({"balance": "100.00"})
result["balance"]  # => 100.0
100.0

Adding Context to Method and Function Fields 将上下文添加到 MethodFunction 字段 ¶

A Function or Method field may need information about its environment to know how to serialize a value. FunctionMethod 字段可能需要有关其环境的信息才能知道如何序列化值。

In these cases, you can set the context attribute (a dictionary) of a Schema. Function and Method fields will have access to this dictionary. 在这些情况下,您可以设置 Schemacontext 属性(字典)。 FunctionMethod 字段将有权访问此字典。

As an example, you might want your UserSchema to output whether or not a User is the author of a Blog or whether a certain word appears in a Blog's title. 例如,您可能希望 UserSchema 输出 User 是否是 Blog 的作者,或者某个单词是否出现在 Blog's 标题。

python 复制代码
class UserSchema(Schema):
    name = fields.String()
    # Function fields optionally receive context argument
    is_author = fields.Function(lambda user, context: user == context["blog"].author)
    likes_bikes = fields.Method("writes_about_bikes")

    def writes_about_bikes(self, user):
        return "bicycle" in self.context["blog"].title.lower()


schema = UserSchema()

user = User("Freddie Mercury", "fred@queen.com")
blog = Blog("Bicycle Blog", author=user)

schema.context = {"blog": blog}
result = schema.dump(user)
result["is_author"]  # => True
result["likes_bikes"]  # => True
python 复制代码
True






True

Customizing Error Messages 自定义错误消息 ¶

Validation error messages for fields can be configured at the class or instance level. 字段的验证错误消息可以在类或实例级别配置。

At the class level, default error messages are defined as a mapping from error codes to error messages. 在类级别,默认错误消息被定义为从错误代码到错误消息的映射。

python 复制代码
from marshmallow import fields


class MyDate(fields.Date):
    default_error_messages = {"invalid": "Please provide a valid date."}

Note 笔记

A Field's default_error_messages dictionary gets merged with its parent classes' default_error_messages dictionaries. Field's default_error_messages 字典与其父类的 default_error_messages 字典合并。

Error messages can also be passed to a Field's constructor. 错误消息也可以传递给 Field's 构造函数。

python 复制代码
from marshmallow import Schema, fields


class UserSchema(Schema):
    name = fields.Str(
        required=True, error_messages={"required": "Please provide a name."}
    )

Next Steps 下一步 ¶

  • Need to add schema-level validation, post-processing, or error handling behavior? See the Extending Schemas page. 需要添加模式级验证、后处理或错误处理行为?请参阅扩展架构页面。
  • For example applications using marshmallow, check out the Examples page. 有关使用棉花糖的示例应用程序,请查看示例页面。

Extending Schemas 扩展模式 ¶

Pre-processing and Post-processing Methods 预处理和后处理方法 ¶

Data pre-processing and post-processing methods can be registered using the pre_load, post_load, pre_dump, and post_dump decorators. 数据预处理和后处理方法可以使用 pre_loadpost_loadpre_dumppost_dump 装饰器注册。

python 复制代码
from marshmallow import Schema, fields, post_load


class UserSchema(Schema):
    name = fields.Str()
    slug = fields.Str()

    @post_load
    def slugify_name(self, in_data, **kwargs):
        in_data["slug"] = in_data["slug"].lower().strip().replace(" ", "-")
        return in_data


schema = UserSchema()
result = schema.load({"name": "Steve", "slug": "Steve Loria "})
result["slug"]  # => 'steve-loria'
arduino 复制代码
'steve-loria'

Passing "many" 传递"很多"¶

By default, pre- and post-processing methods receive one object/datum at a time, transparently handling the many parameter passed to the Schema's dump()/load() method at runtime. 默认情况下,预处理和后处理方法一次接收一个对象/数据,透明地处理传递给 Schemadump()many 参数 /运行时的 load() 方法。

In cases where your pre- and post-processing methods needs to handle the input collection when processing multiple objects, add pass_many=True to the method decorators. 如果您的预处理和后处理方法在处理多个对象时需要处理输入集合,请将 pass_many=True 添加到方法装饰器中。

Your method will then receive the input data (which may be a single datum or a collection, depending on the dump/load call). 然后,您的方法将接收输入数据(可能是单个数据或集合,具体取决于转储/加载调用)。

Example: Enveloping 示例:包络 ¶

One common use case is to wrap data in a namespace upon serialization and unwrap the data during deserialization. 一种常见的用例是在序列化时将数据包装在命名空间中,并在反序列化期间解开数据。

python 复制代码
from marshmallow import Schema, fields, pre_load, post_load, post_dump


class BaseSchema(Schema):
    # Custom options
    __envelope__ = {"single": None, "many": None}
    __model__ = User

    def get_envelope_key(self, many):
        """Helper to get the envelope key."""
        key = self.__envelope__["many"] if many else self.__envelope__["single"]
        assert key is not None, "Envelope key undefined"
        return key

    @pre_load(pass_many=True)
    def unwrap_envelope(self, data, many, **kwargs):
        key = self.get_envelope_key(many)
        return data[key]

    @post_dump(pass_many=True)
    def wrap_with_envelope(self, data, many, **kwargs):
        key = self.get_envelope_key(many)
        return {key: data}

    @post_load
    def make_object(self, data, **kwargs):
        return self.__model__(**data)


class UserSchema(BaseSchema):
    __envelope__ = {"single": "user", "many": "users"}
    __model__ = User
    name = fields.Str()
    email = fields.Email()


user_schema = UserSchema()

user = User("Mick", email="mick@stones.org")
user_data = user_schema.dump(user)
# {'user': {'email': 'mick@stones.org', 'name': 'Mick'}}

users = [
    User("Keith", email="keith@stones.org"),
    User("Charlie", email="charlie@stones.org"),
]
users_data = user_schema.dump(users, many=True)
# {'users': [{'email': 'keith@stones.org', 'name': 'Keith'},
#            {'email': 'charlie@stones.org', 'name': 'Charlie'}]}

user_objs = user_schema.load(users_data, many=True)
# [<User(name='Keith Richards')>, <User(name='Charlie Watts')>]

Raising Errors in Pre-/Post-processor Methods 在预处理器/后处理器方法中引发错误 ¶

Pre- and post-processing methods may raise a ValidationError. By default, errors will be stored on the "_schema" key in the errors dictionary. 预处理和后处理方法可能会引发 ValidationError 。默认情况下,错误将存储在错误字典中的 "_schema" 键上。

python 复制代码
from marshmallow import Schema, fields, ValidationError, pre_load


class BandSchema(Schema):
    name = fields.Str()

    @pre_load
    def unwrap_envelope(self, data, **kwargs):
        if "data" not in data:
            raise ValidationError('Input data must have a "data" key.')
        return data["data"]


sch = BandSchema()
try:
    sch.load({"name": "The Band"})
except ValidationError as err:
    err.messages
# {'_schema': ['Input data must have a "data" key.']}
rust 复制代码
{'_schema': ['Input data must have a "data" key.']}

If you want to store and error on a different key, pass the key name as the second argument to ValidationError. 如果您想在不同的键上存储和出错,请将键名称作为第二个参数传递给 ValidationError

python 复制代码
from marshmallow import Schema, fields, ValidationError, pre_load


class BandSchema(Schema):
    name = fields.Str()

    @pre_load
    def unwrap_envelope(self, data, **kwargs):
        if "data" not in data:
            raise ValidationError(
                'Input data must have a "data" key.', "_preprocessing"
            )
        return data["data"]


sch = BandSchema()
try:
    sch.load({"name": "The Band"})
except ValidationError as err:
    err.messages
# {'_preprocessing': ['Input data must have a "data" key.']}
rust 复制代码
{'_preprocessing': ['Input data must have a "data" key.']}

Pre-/Post-processor Invocation Order 前置/后处理器调用顺序 ¶

In summary, the processing pipeline for deserialization is as follows: 综上,反序列化的处理流程如下:

  1. @pre_load(pass_many=True) methods @pre_load(pass_many=True) 方法
  2. @pre_load(pass_many=False) methods @pre_load(pass_many=False) 方法
  3. load(in_data, many) (validation and deserialization) load(in_data, many) (验证和反序列化)
  4. @validates methods (field validators) @validates 方法(字段验证器)
  5. @validates_schema methods (schema validators) @validates_schema 方法(模式验证器)
  6. @post_load(pass_many=True) methods @post_load(pass_many=True) 方法
  7. @post_load(pass_many=False) methods @post_load(pass_many=False) 方法

The pipeline for serialization is similar, except that the pass_many=True processors are invoked after the pass_many=False processors and there are no validators. 序列化管道类似,只是 pass_many=True 处理器在 pass_many=False 处理器之后调用,并且没有验证器。

  1. @pre_dump(pass_many=False) methods @pre_dump(pass_many=False) 方法
  2. @pre_dump(pass_many=True) methods @pre_dump(pass_many=True) 方法
  3. dump(obj, many) (serialization) dump(obj, many) (序列化)
  4. @post_dump(pass_many=False) methods @post_dump(pass_many=False) 方法
  5. @post_dump(pass_many=True) methods @post_dump(pass_many=True) 方法

Warning 警告

You may register multiple processor methods on a Schema. Keep in mind, however, that the invocation order of decorated methods of the same type is not guaranteed. If you need to guarantee order of processing steps, you should put them in the same method. 您可以在一个架构上注册多个处理器方法。但请记住,不能保证相同类型的修饰方法的调用顺序。如果需要保证处理步骤的顺序,则应该将它们放在同一个方法中。

python 复制代码
from marshmallow import Schema, fields, pre_load


# YES
class MySchema(Schema):
    field_a = fields.Field()

    @pre_load
    def preprocess(self, data, **kwargs):
        step1_data = self.step1(data)
        step2_data = self.step2(step1_data)
        return step2_data

    def step1(self, data):
        do_step1(data)

    # Depends on step1
    def step2(self, data):
        do_step2(data)


# NO
class MySchema(Schema):
    field_a = fields.Field()

    @pre_load
    def step1(self, data, **kwargs):
        do_step1(data)

    # Depends on step1
    @pre_load
    def step2(self, data, **kwargs):
        do_step2(data)

Schema-level Validation 模式级验证 ¶

You can register schema-level validation functions for a Schema using the marshmallow.validates_schema decorator. By default, schema-level validation errors will be stored on the _schema key of the errors dictionary. 您可以使用 marshmallow.validates_schema 装饰器为 Schema 注册架构级验证函数。默认情况下,模式级验证错误将存储在错误字典的 _schema 键上。

python 复制代码
from marshmallow import Schema, fields, validates_schema, ValidationError


class NumberSchema(Schema):
    field_a = fields.Integer()
    field_b = fields.Integer()

    @validates_schema
    def validate_numbers(self, data, **kwargs):
        if data["field_b"] >= data["field_a"]:
            raise ValidationError("field_a must be greater than field_b")


schema = NumberSchema()
try:
    schema.load({"field_a": 1, "field_b": 2})
except ValidationError as err:
    err.messages["_schema"]
# => ["field_a must be greater than field_b"]
css 复制代码
['field_a must be greater than field_b']

Storing Errors on Specific Fields 存储特定字段的错误 ¶

It is possible to report errors on fields and subfields using a dict. 可以使用 dict 报告字段和子字段的错误。

When multiple schema-leval validator return errors, the error structures are merged together in the ValidationError raised at the end of the validation. 当多个模式级验证器返回错误时,错误结构将在验证结束时引发的 ValidationError 中合并在一起。

python 复制代码
from marshmallow import Schema, fields, validates_schema, ValidationError


class NumberSchema(Schema):
    field_a = fields.Integer()
    field_b = fields.Integer()
    field_c = fields.Integer()
    field_d = fields.Integer()

    @validates_schema
    def validate_lower_bound(self, data, **kwargs):
        errors = {}
        if data["field_b"] <= data["field_a"]:
            errors["field_b"] = ["field_b must be greater than field_a"]
        if data["field_c"] <= data["field_a"]:
            errors["field_c"] = ["field_c must be greater than field_a"]
        if errors:
            raise ValidationError(errors)

    @validates_schema
    def validate_upper_bound(self, data, **kwargs):
        errors = {}
        if data["field_b"] >= data["field_d"]:
            errors["field_b"] = ["field_b must be lower than field_d"]
        if data["field_c"] >= data["field_d"]:
            errors["field_c"] = ["field_c must be lower than field_d"]
        if errors:
            raise ValidationError(errors)


schema = NumberSchema()
try:
    schema.load({"field_a": 3, "field_b": 2, "field_c": 1, "field_d": 0})
except ValidationError as err:
    err.messages
# => {
#     'field_b': [
#         'field_b must be greater than field_a',
#         'field_b must be lower than field_d'
#     ],
#     'field_c': [
#         'field_c must be greater than field_a',
#         'field_c must be lower than field_d'
#     ]
#    }
arduino 复制代码
{'field_b': ['field_b must be greater than field_a',
  'field_b must be lower than field_d'],
 'field_c': ['field_c must be greater than field_a',
  'field_c must be lower than field_d']}

Using Original Input Data 使用原始输入数据 ¶

If you want to use the original, unprocessed input, you can add pass_original=True to post_load or validates_schema. 如果您想使用原始的、未处理的输入,可以将 pass_original=True 添加到 post_loadvalidates_schema

python 复制代码
from marshmallow import Schema, fields, post_load, ValidationError, EXCLUDE, INCLUDE


class MySchema(Schema):
    foo = fields.Int()
    bar = fields.Int()

    @post_load(pass_original=True)
    def add_baz_to_bar(self, data, original_data, **kwargs):
        baz = original_data.get("baz")
        if baz:
            data["bar"] = data["bar"] + baz
        return data


schema = MySchema()
schema.load({"foo": 1, "bar": 2, "baz": 3}, unknown=EXCLUDE)
# {'foo': 1, 'bar': 5}
arduino 复制代码
{'foo': 1, 'bar': 5}

See also 也可以看看

The default behavior for unspecified fields can be controlled with the unknown option, see Handling Unknown Fields for more information. 可以使用 unknown 选项控制未指定字段的默认行为,有关详细信息,请参阅处理未知字段。

Overriding How Attributes Are Accessed 重写属性的访问方式 ¶

By default, marshmallow uses utils.get_value to pull attributes from various types of objects for serialization. This will work for most use cases. 默认情况下,marshmallow 使用 utils.get_value 从各种类型的对象中提取属性以进行序列化。这适用于大多数用例。

However, if you want to specify how values are accessed from an object, you can override the get_attribute method. 但是,如果您想指定如何从对象访问值,则可以重写 get_attribute 方法。

python 复制代码
class UserDictSchema(Schema):
    name = fields.Str()
    email = fields.Email()

    # If we know we're only serializing dictionaries, we can
    # use dict.get for all input objects
    def get_attribute(self, obj, key, default):
        return obj.get(key, default)

Custom Error Handling 自定义错误处理 ¶

By default, Schema.load() will raise a ValidationError if passed invalid data. 默认情况下,如果传递无效数据, Schema.load() 将引发 ValidationError

You can specify a custom error-handling function for a Schema by overriding the handle_error method. The method receives the ValidationError and the original input data to be deserialized. 您可以通过重写 handle_error 方法为 Schema 指定自定义错误处理函数。该方法接收 ValidationError 和要反序列化的原始输入数据。

python 复制代码
import logging
from marshmallow import Schema, fields


class AppError(Exception):
    pass


class UserSchema(Schema):
    email = fields.Email()

    def handle_error(self, exc, data, **kwargs):
        """Log and raise our custom exception when (de)serialization fails."""
        logging.error(exc.messages)
        raise AppError("An error occurred with input: {0}".format(data))


schema = UserSchema()
try:
    schema.load({"email": "invalid-email"})  # raises AppError
except AppError:
    print("AppError raised")
css 复制代码
ERROR:root:{'email': ['Not a valid email address.']}


AppError raised

Custom "class Meta" Options 自定义"类元"选项 ¶

class Meta options are a way to configure and modify a Schema's behavior. See the API docs for a listing of available options. class Meta 选项是配置和修改 Schema's 行为的一种方法。有关可用选项的列表,请参阅 API docs

You can add custom class Meta options by subclassing SchemaOpts. 您可以通过子类化 SchemaOpts 来添加自定义 class Meta 选项。

Example: Enveloping, Revisited 示例:重新审视信封 ¶

Let's build upon the example above for adding an envelope to serialized output. This time, we will allow the envelope key to be customizable with class Meta options. 让我们以上面的示例为基础,向序列化输出添加信封。这次,我们将允许使用 class Meta 选项自定义信封键。

python 复制代码
# Example outputs
{
    'user': {
        'name': 'Keith',
        'email': 'keith@stones.com'
    }
}
# List output
{
    'users': [{'name': 'Keith'}, {'name': 'Mick'}]
}
arduino 复制代码
{'user': {'name': 'Keith', 'email': 'keith@stones.com'}}






{'users': [{'name': 'Keith'}, {'name': 'Mick'}]}

First, we'll add our namespace configuration to a custom options class. 首先,我们将命名空间配置添加到自定义选项类中。

python 复制代码
from marshmallow import Schema, SchemaOpts


class NamespaceOpts(SchemaOpts):
    """Same as the default class Meta options, but adds "name" and
    "plural_name" options for enveloping.
    """

    def __init__(self, meta, **kwargs):
        SchemaOpts.__init__(self, meta, **kwargs)
        self.name = getattr(meta, "name", None)
        self.plural_name = getattr(meta, "plural_name", self.name)

Then we create a custom Schema that uses our options class. 然后我们创建一个使用我们的选项类的自定义 Schema

python 复制代码
class NamespacedSchema(Schema):
    OPTIONS_CLASS = NamespaceOpts

    @pre_load(pass_many=True)
    def unwrap_envelope(self, data, many, **kwargs):
        key = self.opts.plural_name if many else self.opts.name
        return data[key]

    @post_dump(pass_many=True)
    def wrap_with_envelope(self, data, many, **kwargs):
        key = self.opts.plural_name if many else self.opts.name
        return {key: data}

Our application schemas can now inherit from our custom schema class. 我们的应用程序模式现在可以从我们的自定义模式类继承。

python 复制代码
class UserSchema(NamespacedSchema):
    name = fields.String()
    email = fields.Email()

    class Meta:
        name = "user"
        plural_name = "users"


ser = UserSchema()
user = User("Keith", email="keith@stones.com")
result = ser.dump(user)
result  # {"user": {"name": "Keith", "email": "keith@stones.com"}}
arduino 复制代码
{'user': {'name': 'Keith', 'email': 'keith@stones.com'}}

Using Context 使用上下文 ¶

The context attribute of a Schema is a general-purpose store for extra information that may be needed for (de)serialization. It may be used in both Schema and Field methods. Schemacontext 属性是通用存储,用于存储序列化(反)序列化可能需要的额外信息。它可以在 SchemaField 方法中使用。

schema = UserSchema()

Make current HTTP request available to

custom fields, schema methods, schema validators, etc.

schema.context["request"] = request schema.dump(user)

Custom Error Messages 自定义错误消息 ¶

To customize the schema-level error messages that load and loads use when raising a ValidationError, override the error_messages class variable: 要自定义 loadloads 在引发 ValidationError 时使用的架构级错误消息,请覆盖 error_messages 类变量:

python 复制代码
class MySchema(Schema):
    error_messages = {
        "unknown": "Custom unknown field error message.",
        "type": "Custom invalid type error message.",
    }

Field-level error message defaults can be set on Field.default_error_messages. 字段级错误消息默认值可以在 Field.default_error_messages 上设置。

python 复制代码
from marshmallow import Schema, fields

fields.Field.default_error_messages["required"] = "You missed something!"


class ArtistSchema(Schema):
    name = fields.Str(required=True)
    label = fields.Str(required=True, error_messages={"required": "Label missing."})


print(ArtistSchema().validate({}))
# {'label': ['Label missing.'], 'name': ['You missed something!']}
less 复制代码
{'name': ['You missed something!'], 'label': ['Label missing.']}

Examples 例子 ¶

Validating package.json 验证 package.json

marshmallow can be used to validate configuration according to a schema. Below is a schema that could be used to validate package.json files. This example demonstrates the following features: marshmallow 可用于根据模式验证配置。下面是可用于验证 package.json 文件的架构。该示例演示了以下功能:

  • Validation and deserialization using Schema.load() 使用 Schema.load() 进行验证和反序列化
  • Custom fields 自定义字段
  • Specifying deserialization keys using data_key 使用 data_key 指定反序列化键
  • Including unknown keys using unknown = INCLUDE 使用 unknown = INCLUDE 包含未知密钥
python 复制代码
import sys
import json
from packaging import version
from pprint import pprint

from marshmallow import Schema, fields, INCLUDE, ValidationError


class Version(fields.Field):
    """Version field that deserializes to a Version object."""

    def _deserialize(self, value, *args, **kwargs):
        try:
            return version.Version(value)
        except version.InvalidVersion as e:
            raise ValidationError("Not a valid version.") from e

    def _serialize(self, value, *args, **kwargs):
        return str(value)


class PackageSchema(Schema):
    name = fields.Str(required=True)
    version = Version(required=True)
    description = fields.Str(required=True)
    main = fields.Str(required=False)
    homepage = fields.URL(required=False)
    scripts = fields.Dict(keys=fields.Str(), values=fields.Str())
    license = fields.Str(required=True)
    dependencies = fields.Dict(keys=fields.Str(), values=fields.Str(), required=False)
    dev_dependencies = fields.Dict(
        keys=fields.Str(),
        values=fields.Str(),
        required=False,
        data_key="devDependencies",
    )

    class Meta:
        # Include unknown fields in the deserialized output
        unknown = INCLUDE


if __name__ == "__main__":
    # pkg = json.load(sys.stdin)
    pkg = {
        "name": "dunderscore",
        "version": "1.2.3",
        "description": "The Pythonic JavaScript toolkit",
        "devDependencies": {
            "pest": "^23.4.1"
        },
        "main": "index.js",
        "scripts": {
            "test": "pest"
        },
        "license": "MIT"
    }
    try:
        pprint(PackageSchema().load(pkg))
    except ValidationError as error:
        print("ERROR: package.json is invalid")
        pprint(error.messages)
        sys.exit(1)
css 复制代码
{'description': 'The Pythonic JavaScript toolkit',
 'dev_dependencies': {'pest': '^23.4.1'},
 'license': 'MIT',
 'main': 'index.js',
 'name': 'dunderscore',
 'scripts': {'test': 'pest'},
 'version': <Version('1.2.3')>}

Given the following package.json file... 给定以下 package.json 文件...

{ "name": "dunderscore", "version": "1.2.3", "description": "The Pythonic JavaScript toolkit", "devDependencies": { "pest": "^23.4.1" }, "main": "index.js", "scripts": { "test": "pest" }, "license": "MIT" }

We can validate it using the above script. 我们可以使用上面的脚本来验证它。

$ python examples/package_json_example.py < package.json {'description': 'The Pythonic JavaScript toolkit', 'dev_dependencies': {'pest': '^23.4.1'}, 'license': 'MIT', 'main': 'index.js', 'name': 'dunderscore', 'scripts': {'test': 'pest'}, 'version': <Version('1.2.3')>}

Notice that our custom field deserialized the version string to a Version object. 请注意,我们的自定义字段将版本字符串反序列化为 Version 对象。

But if we pass an invalid package.json file... 但是如果我们传递一个无效的 package.json 文件......

{ "name": "dunderscore", "version": "INVALID", "homepage": "INVALID", "description": "The Pythonic JavaScript toolkit", "license": "MIT" }

We see the corresponding error messages. 我们看到相应的错误信息。

$ python examples/package_json_example.py < invalid_package.json ERROR: package.json is invalid {'homepage': ['Not a valid URL.'], 'version': ['Not a valid version.']}

Text Analysis API (Bottle + TextBlob) 文本分析 API (Bottle + TextBlob) ¶

Here is a very simple text analysis API using Bottle and TextBlob that demonstrates how to declare an object serializer. 这是一个使用 Bottle 和 TextBlob 的非常简单的文本分析 API,演示了如何声明对象序列化器。

Assume that TextBlob objects have polarity, subjectivity, noun_phrase, tags, and words properties. 假设 TextBlob 对象具有 polaritysubjectivitynoun_phrasetagswords 属性。

python -m textblob.download_corpora

python 复制代码
from bottle import route, request, run
from textblob import TextBlob
from marshmallow import Schema, fields


class BlobSchema(Schema):
    polarity = fields.Float()
    subjectivity = fields.Float()
    chunks = fields.List(fields.String, attribute="noun_phrases")
    tags = fields.Raw()
    discrete_sentiment = fields.Method("get_discrete_sentiment")
    word_count = fields.Function(lambda obj: len(obj.words))

    def get_discrete_sentiment(self, obj):
        if obj.polarity > 0.1:
            return "positive"
        elif obj.polarity < -0.1:
            return "negative"
        else:
            return "neutral"


blob_schema = BlobSchema()


@route("/api/v1/analyze", method="POST")
def analyze():
    blob = TextBlob(request.json["text"])
    return blob_schema.dump(blob)


run(reloader=True, port=5000)

Using The API 使用 API

First, run the app. 首先,运行应用程序。

$ python examples/textblob_example.py

Then send a POST request with some text with httpie (a curl-like tool) for testing the APIs. 然后使用 httpie(类似curl 的工具)发送带有一些文本的 POST 请求来测试 API。

<math xmlns="http://www.w3.org/1998/Math/MathML"> p i p i n s t a l l h t t p i e pip install httpie </math>pipinstallhttpie http POST :5000/api/v1/analyze text="Simple is better" HTTP/1.0 200 OK Content-Length: 189 Content-Type: application/json Date: Wed, 13 Nov 2013 08:58:40 GMT Server: WSGIServer/0.1 Python/2.7.5

{ "chunks": [ "simple" ], "discrete_sentiment": "positive", "polarity": 0.25, "subjectivity": 0.4285714285714286, "tags": [ [ "Simple", "NN" ], [ "is", "VBZ" ], [ "better", "JJR" ] ], "word_count": 3 }

Quotes API (Flask + SQLAlchemy) 报价 API (Flask + SQLAlchemy) ¶

Below is a full example of a REST API for a quotes app using Flask and SQLAlchemy with marshmallow. It demonstrates a number of features, including: 下面是使用 Flask 和 SQLAlchemy 以及 marshmallow 的报价应用程序的 REST API 的完整示例。它展示了许多功能,包括:

  • Custom validation 自定义验证
  • Nesting fields 嵌套字段
  • Using dump_only=True to specify read-only fields 使用 dump_only=True 指定只读字段
  • Output filtering using the only parameter 使用 only 参数进行输出过滤
  • Using @pre_load to preprocess input data. 使用 @pre_load 预处理输入数据。
python 复制代码
import datetime

from flask import Flask, request
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.exc import NoResultFound
from marshmallow import Schema, fields, ValidationError, pre_load

app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///D:\\foo.db"  # 先在D:\建个foo.db的空文件(文本文件)
db = SQLAlchemy(app)

##### MODELS #####


class Author(db.Model):  # type: ignore
    id = db.Column(db.Integer, primary_key=True)
    first = db.Column(db.String(80))
    last = db.Column(db.String(80))


class Quote(db.Model):  # type: ignore
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.String, nullable=False)
    author_id = db.Column(db.Integer, db.ForeignKey("author.id"))
    author = db.relationship(
        "Author", backref=db.backref("quotes", lazy="dynamic"))
    posted_at = db.Column(db.DateTime)


##### SCHEMAS #####


class AuthorSchema(Schema):
    id = fields.Int(dump_only=True)
    first = fields.Str()
    last = fields.Str()
    formatted_name = fields.Method("format_name", dump_only=True)

    def format_name(self, author):
        return f"{author.last}, {author.first}"


# Custom validator
def must_not_be_blank(data):
    if not data:
        raise ValidationError("Data not provided.")


class QuoteSchema(Schema):
    id = fields.Int(dump_only=True)
    author = fields.Nested(AuthorSchema, validate=must_not_be_blank)
    content = fields.Str(required=True, validate=must_not_be_blank)
    posted_at = fields.DateTime(dump_only=True)

    # Allow client to pass author's full name in request body
    # e.g. {"author': 'Tim Peters"} rather than {"first": "Tim", "last": "Peters"}
    @pre_load
    def process_author(self, data, **kwargs):
        author_name = data.get("author")
        if author_name:
            first, last = author_name.split(" ")
            author_dict = dict(first=first, last=last)
        else:
            author_dict = {}
        data["author"] = author_dict
        return data


author_schema = AuthorSchema()
authors_schema = AuthorSchema(many=True)
quote_schema = QuoteSchema()
quotes_schema = QuoteSchema(many=True, only=("id", "content"))

##### API #####


@app.route("/authors")
def get_authors():
    authors = Author.query.all()
    # Serialize the queryset
    result = authors_schema.dump(authors)
    return {"authors": result}


@app.route("/authors/<int:pk>")
def get_author(pk):
    try:
        author = Author.query.filter(Author.id == pk).one()
    except NoResultFound:
        return {"message": "Author could not be found."}, 400
    author_result = author_schema.dump(author)
    quotes_result = quotes_schema.dump(author.quotes.all())
    return {"author": author_result, "quotes": quotes_result}


@app.route("/quotes/", methods=["GET"])
def get_quotes():
    quotes = Quote.query.all()
    result = quotes_schema.dump(quotes, many=True)
    return {"quotes": result}


@app.route("/quotes/<int:pk>")
def get_quote(pk):
    try:
        quote = Quote.query.filter(Quote.id == pk).one()
    except NoResultFound:
        return {"message": "Quote could not be found."}, 400
    result = quote_schema.dump(quote)
    return {"quote": result}


@app.route("/quotes/", methods=["POST"])
def new_quote():
    json_data = request.get_json()
    if not json_data:
        return {"message": "No input data provided"}, 400
    # Validate and deserialize input
    try:
        data = quote_schema.load(json_data)
    except ValidationError as err:
        return err.messages, 422
    first, last = data["author"]["first"], data["author"]["last"]
    author = Author.query.filter_by(first=first, last=last).first()
    if author is None:
        # Create a new author
        author = Author(first=first, last=last)
        db.session.add(author)
    # Create new quote
    quote = Quote(
        content=data["content"], author=author, posted_at=datetime.datetime.utcnow()
    )
    db.session.add(quote)
    db.session.commit()
    result = quote_schema.dump(Quote.query.get(quote.id))
    return {"message": "Created new quote.", "quote": result}


if __name__ == "__main__":
    with app.app_context():
        db.create_all()
    app.run(port=5000)
vbnet 复制代码
 * Serving Flask app '__main__'
 * Debug mode: off


INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
INFO:werkzeug:Press CTRL+C to quit

Using The API 使用 API

Run the app. 运行应用程序。

<math xmlns="http://www.w3.org/1998/Math/MathML"> p i p i n s t a l l f l a s k f l a s k − s q l a l c h e m y pip install flask flask-sqlalchemy </math>pipinstallflaskflask−sqlalchemy python examples/flask_example.py

First we'll POST some quotes. 首先我们将发布一些报价。

<math xmlns="http://www.w3.org/1998/Math/MathML"> p i p i n s t a l l h t t p i e pip install httpie </math>pipinstallhttpie http POST :5000/quotes/ author="Tim Peters" content="Beautiful is better than ugly." <math xmlns="http://www.w3.org/1998/Math/MathML"> h t t p P O S T : 5000 / q u o t e s / a u t h o r = " T i m P e t e r s " c o n t e n t = " N o w i s b e t t e r t h a n n e v e r . " http POST :5000/quotes/ author="Tim Peters" content="Now is better than never." </math>httpPOST:5000/quotes/author="TimPeters"content="Nowisbetterthannever." http POST :5000/quotes/ author="Peter Hintjens" content="Simplicity is always better than functionality."

If we provide invalid input data, we get 400 error response. Let's omit "author" from the input data. 如果我们提供无效的输入数据,我们会收到 400 错误响应。让我们从输入数据中省略"作者"。

$ http POST :5000/quotes/ content="I have no author" { "author": [ "Data not provided." ] }

Now we can GET a list of all the quotes. 现在我们可以获得所有报价的列表。

$ http :5000/quotes/ { "quotes": [ { "content": "Beautiful is better than ugly.", "id": 1 }, { "content": "Now is better than never.", "id": 2 }, { "content": "Simplicity is always better than functionality.", "id": 3 } ] }

We can also GET the quotes for a single author. 我们还可以获取单个作者的引用。

$ http :5000/authors/1 { "author": { "first": "Tim", "formatted_name": "Peters, Tim", "id": 1, "last": "Peters" }, "quotes": [ { "content": "Beautiful is better than ugly.", "id": 1 }, { "content": "Now is better than never.", "id": 2 } ] }

ToDo API (Flask + Peewee) ToDo API(Flask + Peewee) ¶

This example uses Flask and the Peewee ORM to create a basic Todo application. 此示例使用 Flask 和 Peewee ORM 创建一个基本的 Todo 应用程序。

Here, we use Schema.load to validate and deserialize input data to model data. Also notice how pre_load is used to clean input data and post_load is used to add an envelope to response data. 在这里,我们使用 Schema.load 来验证输入数据并将其反序列化为模型数据。另请注意 pre_load 如何用于清理输入数据,以及 post_load 如何用于向响应数据添加信封。

python 复制代码
import datetime as dt
from functools import wraps

from flask import Flask, request, g, jsonify
import peewee as pw
from marshmallow import (
    Schema,
    fields,
    validate,
    pre_load,
    post_dump,
    post_load,
    ValidationError,
)

app = Flask(__name__)
db = pw.SqliteDatabase("D://todo.db")

###### MODELS #####


class BaseModel(pw.Model):
    """Base model class. All descendants share the same database."""

    class Meta:
        database = db


class User(BaseModel):
    email = pw.CharField(max_length=80, unique=True)
    password = pw.CharField()
    joined_on = pw.DateTimeField()


class Todo(BaseModel):
    content = pw.TextField()
    is_done = pw.BooleanField(default=False)
    user = pw.ForeignKeyField(User)
    posted_on = pw.DateTimeField()


def create_tables():
    db.connect()
    User.create_table(True)
    Todo.create_table(True)


##### SCHEMAS #####


class UserSchema(Schema):
    id = fields.Int(dump_only=True)
    email = fields.Str(
        required=True, validate=validate.Email(error="Not a valid email address")
    )
    password = fields.Str(
        required=True, validate=[validate.Length(min=6, max=36)], load_only=True
    )
    joined_on = fields.DateTime(dump_only=True)

    # Clean up data
    @pre_load
    def process_input(self, data, **kwargs):
        data["email"] = data["email"].lower().strip()
        return data

    # We add a post_dump hook to add an envelope to responses
    @post_dump(pass_many=True)
    def wrap(self, data, many, **kwargs):
        key = "users" if many else "user"
        return {key: data}


class TodoSchema(Schema):
    id = fields.Int(dump_only=True)
    done = fields.Boolean(attribute="is_done", missing=False)
    user = fields.Nested(UserSchema(
        exclude=("joined_on", "password")), dump_only=True)
    content = fields.Str(required=True)
    posted_on = fields.DateTime(dump_only=True)

    # Again, add an envelope to responses
    @post_dump(pass_many=True)
    def wrap(self, data, many, **kwargs):
        key = "todos" if many else "todo"
        return {key: data}

    # We use make_object to create a new Todo from validated data
    @post_load
    def make_object(self, data, **kwargs):
        if not data:
            return None
        return Todo(
            content=data["content"],
            is_done=data["is_done"],
            posted_on=dt.datetime.utcnow(),
        )


user_schema = UserSchema()
todo_schema = TodoSchema()
todos_schema = TodoSchema(many=True)

###### HELPERS ######


def check_auth(email, password):
    """Check if a username/password combination is valid."""
    try:
        user = User.get(User.email == email)
    except User.DoesNotExist:
        return False
    return password == user.password


def requires_auth(f):
    @wraps(f)
    def decorated(*args, **kwargs):
        auth = request.authorization
        if not auth or not check_auth(auth.username, auth.password):
            resp = jsonify({"message": "Please authenticate."})
            resp.status_code = 401
            resp.headers["WWW-Authenticate"] = 'Basic realm="Example"'
            return resp
        kwargs["user"] = User.get(User.email == auth.username)
        return f(*args, **kwargs)

    return decorated


# Ensure a separate connection for each thread
@app.before_request
def before_request():
    g.db = db
    g.db.connect()


@app.after_request
def after_request(response):
    g.db.close()
    return response


#### API #####


@app.route("/register", methods=["POST"])
def register():
    json_input = request.get_json()
    try:
        data = user_schema.load(json_input)
    except ValidationError as err:
        return {"errors": err.messages}, 422
    try:  # Use get to see if user already exists
        User.get(User.email == data["email"])
    except User.DoesNotExist:
        user = User.create(
            email=data["email"], joined_on=dt.datetime.now(), password=data["password"]
        )
        message = f"Successfully created user: {user.email}"
    else:
        return {"errors": "That email address is already in the database"}, 400

    data = user_schema.dump(user)
    data["message"] = message
    return data, 201


@app.route("/todos/", methods=["GET"])
def get_todos():
    todos = Todo.select().order_by(Todo.posted_on.asc())  # Get all todos
    return todos_schema.dump(list(todos))


@app.route("/todos/<int:pk>")
def get_todo(pk):
    todo = Todo.get(Todo.id == pk)
    if not todo:
        return {"errors": "Todo could not be find"}, 404
    return todo_schema.dump(todo)


@app.route("/todos/<int:pk>/toggle", methods=["POST", "PUT"])
def toggledone(pk):
    try:
        todo = Todo.get(Todo.id == pk)
    except Todo.DoesNotExist:
        return {"message": "Todo could not be found"}, 404
    status = not todo.is_done
    update_query = todo.update(is_done=status)
    update_query.execute()
    return todo_schema.dump(todo)


@app.route("/todos/", methods=["POST"])
@requires_auth
def new_todo(user):
    json_input = request.get_json()
    try:
        todo = todo_schema.load(json_input)
    except ValidationError as err:
        return {"errors": err.messages}, 422
    todo.user = user
    todo.save()
    return todo_schema.dump(todo)


if __name__ == "__main__":
    create_tables()
    app.run(port=5000)
vbnet 复制代码
 * Serving Flask app '__main__'
 * Debug mode: off


INFO:werkzeug:WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on http://127.0.0.1:5000
INFO:werkzeug:Press CTRL+C to quit

Using the API 使用API

Run the app. 运行应用程序。

<math xmlns="http://www.w3.org/1998/Math/MathML"> p i p i n s t a l l f l a s k p e e w e e pip install flask peewee </math>pipinstallflaskpeewee python examples/peewee_example.py

After registering a user and creating some todo items in the database, here is an example response. 注册用户并在数据库中创建一些待办事项后,以下是示例响应。

HTTPIE 工具使用入门 - 掘金 juejin.cn/post/703394...

认证 http --auth username:password mimvp.com http -a username:password mimvp.com http --auth-type=digest -a username:password mimvp.com

模拟Form的Post请求, Content-Type: application/x-www-form-urlencoded; charset=utf-8 http --form POST mimvp.com username='mimvp-user'

makefile 复制代码
$ http POST :5000/register email="john@example.com" password="secret"

HTTP/1.1 201 CREATED
Connection: close
Content-Length: 172
Content-Type: application/json
Date: Fri, 12 Jan 2024 02:25:16 GMT
Server: Werkzeug/3.0.0 Python/3.11.6

{
    "message": "Successfully created user: john@example.com",
    "user": {
        "email": "john@example.com",
        "id": 1,
        "joined_on": "2024-01-12T10:25:16.507427"
    }
}

$ http POST :5000/todos/ content="Install marshmallow" -a john@example.com:secret
HTTP/1.1 200 OK
Connection: close
Content-Length: 234
Content-Type: application/json
Date: Fri, 12 Jan 2024 02:35:34 GMT
Server: Werkzeug/3.0.0 Python/3.11.6

{
    "todo": {
        "content": "Install marshmallow",
        "done": false,
        "id": 1,
        "posted_on": "2024-01-12T02:35:34.110837",
        "user": {
            "user": {
                "email": "john@example.com",
                "id": 1
            }
        }
    }
}

$ http POST :5000/todos/ content="Learn Python" -a john@example.com:secret
$ http POST :5000/todos/ content="Refactor everything" -a john@example.com:secret
cmd 复制代码
$ http GET :5000/todos/
HTTP/1.1 200 OK
Connection: close
Content-Length: 754
Content-Type: application/json
Date: Fri, 12 Jan 2024 02:38:07 GMT
Server: Werkzeug/3.0.0 Python/3.11.6
json 复制代码
{
    "todos": [
        {
            "content": "Install marshmallow",
            "done": false,
            "id": 1,
            "posted_on": "2024-01-12T02:35:34.110837",
            "user": {
                "user": {
                    "email": "john@example.com",
                    "id": 1
                }
            }
        },
        {
            "content": "Learn Python",
            "done": false,
            "id": 2,
            "posted_on": "2024-01-12T02:37:54.222329",
            "user": {
                "user": {
                    "email": "john@example.com",
                    "id": 1
                }
            }
        },
        {
            "content": "Refactor everything",
            "done": false,
            "id": 3,
            "posted_on": "2024-01-12T02:38:01.419137",
            "user": {
                "user": {
                    "email": "john@example.com",
                    "id": 1
                }
            }
        }
    ]
}

Inflection (Camel-casing Keys) 变形(驼峰键) ¶

HTTP APIs will often use camel-cased keys for their input and output representations. This example shows how you can use the Schema.on_bind_field hook to automatically inflect keys. HTTP API 通常会使用驼峰式键来表示其输入和输出。此示例展示了如何使用 Schema.on_bind_field 挂钩自动改变键。

python 复制代码
from marshmallow import Schema, fields


def camelcase(s):
    parts = iter(s.split("_"))
    return next(parts) + "".join(i.title() for i in parts)


class CamelCaseSchema(Schema):
    """Schema that uses camel-case for its external representation
    and snake-case for its internal representation.
    """

    def on_bind_field(self, field_name, field_obj):
        field_obj.data_key = camelcase(field_obj.data_key or field_name)


# -----------------------------------------------------------------------------


class UserSchema(CamelCaseSchema):
    first_name = fields.Str(required=True)
    last_name = fields.Str(required=True)


schema = UserSchema()
loaded = schema.load({"firstName": "David", "lastName": "Bowie"})
print(loaded)  # => {'last_name': 'Bowie', 'first_name': 'David'}
dumped = schema.dump(loaded)
print(dumped)  # => {'lastName': 'Bowie', 'firstName': 'David'}
arduino 复制代码
{'first_name': 'David', 'last_name': 'Bowie'}
{'firstName': 'David', 'lastName': 'Bowie'}
相关推荐
NiNg_1_2341 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man3 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
customer084 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
Yaml45 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
小码编匠6 小时前
一款 C# 编写的神经网络计算图框架
后端·神经网络·c#