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
中访问正确反序列化的数据。某些字段(例如 Email
和 URL
字段)具有内置验证。
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 aValidationError
if there are any unknown fieldsRAISE
(默认):如果有任何未知字段,则引发ValidationError
EXCLUDE
: exclude unknown fieldsEXCLUDE
:排除未知字段INCLUDE
: accept and include the unknown fieldsINCLUDE
:接受并包含未知字段
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_only
和 load_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
将自动格式化为 String
, created_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. 点线路径可以传递给 only
和 exclude
以指定嵌套属性。
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 Method
和 Function
字段反序列化 ¶
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. Function
和 Method
都接收一个可选的 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 将上下文添加到 Method
和 Function
字段 ¶
A Function
or Method
field may need information about its environment to know how to serialize a value. Function
或 Method
字段可能需要有关其环境的信息才能知道如何序列化值。
In these cases, you can set the context
attribute (a dictionary) of a Schema
. Function
and Method
fields will have access to this dictionary. 在这些情况下,您可以设置 Schema
的 context
属性(字典)。 Function
和 Method
字段将有权访问此字典。
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_load
、 post_load
、 pre_dump
和 post_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. 默认情况下,预处理和后处理方法一次接收一个对象/数据,透明地处理传递给 Schema
的 dump()
的 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: 综上,反序列化的处理流程如下:
@pre_load(pass_many=True)
methods@pre_load(pass_many=True)
方法@pre_load(pass_many=False)
methods@pre_load(pass_many=False)
方法load(in_data, many)
(validation and deserialization)load(in_data, many)
(验证和反序列化)@validates
methods (field validators)@validates
方法(字段验证器)@validates_schema
methods (schema validators)@validates_schema
方法(模式验证器)@post_load(pass_many=True)
methods@post_load(pass_many=True)
方法@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
处理器之后调用,并且没有验证器。
@pre_dump(pass_many=False)
methods@pre_dump(pass_many=False)
方法@pre_dump(pass_many=True)
methods@pre_dump(pass_many=True)
方法dump(obj, many)
(serialization)dump(obj, many)
(序列化)@post_dump(pass_many=False)
methods@post_dump(pass_many=False)
方法@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_load
或 validates_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. Schema
的 context
属性是通用存储,用于存储序列化(反)序列化可能需要的额外信息。它可以在 Schema
和 Field
方法中使用。
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: 要自定义 load
和 loads
在引发 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
对象具有 polarity
、 subjectivity
、 noun_phrase
、 tags
和 words
属性。
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:[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
* Running on http://127.0.0.1:5000
INFO:werkzeug:[33mPress CTRL+C to quit[0m
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:[31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
* Running on http://127.0.0.1:5000
INFO:werkzeug:[33mPress CTRL+C to quit[0m
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'}