marshmallow

marshmallow是一个用来将复杂的orm对象与python原生数据类型之间相互转换的库,简而言之,就是实现object -> dictobjects -> liststring -> dictstring -> list

要用到marshmallow,首先需要一个用于序列化和反序列化的类:

import datetime as dtclass User(object):def __init__(self, name, email):self.name = nameself.email = emailself.created_at = dt.datetime.now()def __repr__(self):return '<User(name={self.name!r})>'.format(self=self)

Schema

要对一个类或者一个json数据实现相互转换(即序列化和反序列化,序列化的意思是将数据转化为可存储或可传输的数据类型),需要一个中间载体,这个载体就是Schema。除了转换以外,Schema还可以用来做数据校验。每个需要转换的类,都需要一个对应的Schema:

from marshmallow import Schema, fieldsclass UserSchema(Schema):name = fields.Str()email = fields.Email()created_at = fields.DateTime()

Serializing(序列化)

序列化使用schema中的dump()dumps()方法,其中,dump() 方法实现obj -> dictdumps()方法实现 obj -> string,由于Flask能直接序列化dict(使用jsonify),而且你肯定还会对dict进一步处理,没必要现在转化成string,所以通常Flask与Marshmallow配合序列化时,用 dump()方法即可:

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

过滤输出

当然你不需要每次都输出对象中所有字段,可以使用only参数来指定你需要输出的字段,这个在实际场景中很常见:

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

你也可以使用exclude字段来排除你不想输出的字段。

Deserializing(反序列化)

相对dump()的方法就是load()了,可以将字典等类型转换成应用层的数据结构,即orm对象:

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

对反序列化而言,将传入的dict变成object更加有意义。在Marshmallow中,dict -> object的方法需要自己实现,然后在该方法前面加上一个decoration:post_load即可,即:

from marshmallow import Schema, fields, post_loadclass UserSchema(Schema):name = fields.Str()email = fields.Email()created_at = fields.DateTime()@post_loaddef make_user(self, data):return User(**data)

这样每次调用load()方法时,会按照make_user的逻辑,返回一个User类对象:

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

tips: 相对于dumps(),也存在loads()方法,用于string -> object,有些简单场景可以用。

Objects <-> List

上面的序列化和反序列化,是针对一个object而言的,对于objects的处理,只需在schema中增加一个参数:many=True,即:

user1 = User(name="Mick", email="mick@stones.com")
user2 = User(name="Keith", email="keith@stones.com")
users = [user1, user2]# option 1:
schema = UserSchema(many=True)
result = schema.dump(users)# Option 2:
schema = UserSchema()
result = schema.dump(users, many=True)
result.data# [{'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'}]

Validation

Schema.load()loads()方法会在返回值中加入验证错误的dictionary,例如emailURL都有内建的验证器。

data, errors = UserSchema().load({'email': 'foo'})
errors  # => {'email': ['"foo" is not a valid email address.']}
# OR, equivalently
result = UserSchema().load({'email': 'foo'})
result.errors  # => {'email': ['"foo" is not a valid email address.']}

当验证一个集合时,返回的错误dictionary会以错误序号对应错误信息的key:value形式保存:

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"
]result = BandMemberSchema(many=True).load(user_data)
result.errors
# {1: {'email': ['"invalid" is not a valid email address.']},
#  3: {'name': ['Missing data for required field.']}}

你可以向内建的field中传入validate 参数来定制验证的逻辑,validate的值可以是函数,匿名函数lambda,或者是定义了__call__的对象:

class ValidatedUserSchema(UserSchema):# NOTE: This is a contrived example.# You could use marshmallow.validate.Range instead of an anonymous function hereage = fields.Number(validate=lambda n: 18 <= n <= 40)in_data = {'name': 'Mick', 'email': 'mick@stones.com', 'age': 71}
result = ValidatedUserSchema().load(in_data)
result.errors  # => {'age': ['Validator <lambda>(71.0) is False']}

如果你传入的函数中定义了ValidationError,当它触发时,错误信息会得到保存:

from marshmallow import Schema, fields, ValidationErrordef 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}
result, errors = ItemSchema().load(in_data)
errors  # => {'quantity': ['Quantity must not be greater than 30.']}

注意:
如果你需要执行多个验证,你应该传入可调用的验证器的集合(list, tuple, generator)

注意2:
Schema.dump() 也会返回错误信息dictionary,也会包含序列化时的所有ValidationErrors。但是required, allow_none, validate, @validates, 和 @validates_schema 只用于反序列化,即Schema.load()

Field Validators as Methods

把生成器写成方法可以提供极大的便利。使用validates 装饰器就可以注册一个验证方法:

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.')

strict Mode

如果将strict=True传入Schema构造器或者classMeta参数里,则仅会在传入无效数据是报错。可以使用ValidationError.messages变量来获取验证错误的dictionary

Required Fields

你可以在field中传入required=True.当Schema.load()的输入缺少某个字段时错误会记录下来。
如果需要定制required fields的错误信息,可以传入一个error_messages参数,参数的值为以required为键的键值对。

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()data, errors = UserSchema().load({'email': 'foo@bar.com'})
errors
# {'name': ['Missing data for required field.'],
#  'age': ['Age is required.'],
#  'city': {'message': 'City required', 'code': 400}}

Partial Loading

按照RESTful架构风格的要求,更新数据使用HTTP方法中的PUTPATCH方法,使用PUT方法时,需要把完整的数据全部传给服务器,使用PATCH方法时,只需把需要改动的部分数据传给服务器即可。因此,当使用PATCH方法时,由于之前设定的required,传入数据存在无法通过Marshmallow 数据校验的风险,为了避免这种情况,需要借助Partial Loading功能。

实现Partial Loadig只要在schema构造器中增加一个partial参数即可:

class UserSchema(Schema):name = fields.String(required=True)age = fields.Integer(required=True)data, errors = UserSchema().load({'age': 42}, partial=('name',))
# OR UserSchema(partial=('name',)).load({'age': 42})
data, errors  # => ({'age': 42}, {})

Schema.validate

如果你只是想用Schema验证数据,而不生成对象,可以使用Schema.validate().

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

Specifying Attribute Names

Schemas默认会编列传入对象和自身定义的fields相同的属性,然而你也会有需求使用不同的fields和属性名。在这种情况下,你需要明确定义这个fields将从什么属性名取值:

class UserSchema(Schema):name = fields.String()email_addr = fields.String(attribute="email")date_created = fields.DateTime(attribute="created_at")user = User('Keith', email='keith@stones.com')
ser = UserSchema()
result, errors = ser.dump(user)
pprint(result)
# {'name': 'Keith',
#  'email_addr': 'keith@stones.com',
#  'date_created': '2014-08-17T14:58:57.600623+00:00'}

Specifying Deserialization Keys

Schemas默认会反编列传入字典和输出字典中相同的字段名。如果你觉得数据不匹配你的schema,你可以传入load_from参数指定需要增加load的字段名(原字段名也能load,且优先load原字段名):

class UserSchema(Schema):name = fields.String()email = fields.Email(load_from='emailAddress')data = {'name': 'Mike','emailAddress': 'foo@bar.com'
}
s = UserSchema()
result, errors = s.load(data)
#{'name': u'Mike',
# 'email': 'foo@bar.com'}

Specifying Serialization Keys

如果你需要编列一个field成一个不同的名字时,可以使用dump_to,逻辑和load_from类似:

class UserSchema(Schema):name = fields.String(dump_to='TheName')email = fields.Email(load_from='CamelCasedEmail', dump_to='CamelCasedEmail')data = {'name': 'Mike','email': 'foo@bar.com'
}
s = UserSchema()
result, errors = s.dump(data)
#{'TheName': u'Mike',
# 'CamelCasedEmail': 'foo@bar.com'}

“Read-only” and “Write-only” Fields

可以指定某些字段只能够dump()load():

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)

Nesting Schemas

当你的模型含有外键,那这个外键的对象在Schemas如何定义。举个例子,Blog就具有User对象作为它的外键:


Use a Nested field to represent the relationship, passing in a nested schema class.
import datetime as dtclass User(object):def __init__(self, name, email):self.name = nameself.email = emailself.created_at = dt.datetime.now()self.friends = []self.employer = Noneclass Blog(object):def __init__(self, title, author):self.title = titleself.author = author  # A User object

使用Nested field表示外键对象:

from marshmallow import Schema, fields, pprintclass UserSchema(Schema):name = fields.String()email = fields.Email()created_at = fields.DateTime()class BlogSchema(Schema):title = fields.String()author = fields.Nested(UserSchema)

这样序列化blog就会带上user信息了:

user = User(name="Monty", email="monty@python.org")
blog = Blog(title="Something Completely Different", author=user)
result, errors = 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'}}

如果field 是多个对象的集合,定义时可以使用many参数:

collaborators = fields.Nested(UserSchema, many=True)

如果外键对象是自引用,则Nested里第一个参数为'self'

Specifying Which Fields to Nest

如果你想指定外键对象序列化后只保留它的几个字段,可以使用Only参数:

class BlogSchema2(Schema):title = fields.String()author = fields.Nested(UserSchema, only=["email"])schema = BlogSchema2()
result, errors = schema.dump(blog)
pprint(result)
# {
#     'title': u'Something Completely Different',
#     'author': {'email': u'monty@python.org'}
# }

如果需要选择外键对象的字段层次较多,可以使用"."操作符来指定:

class SiteSchema(Schema):blog = fields.Nested(BlogSchema2)schema = SiteSchema(only=['blog.author.email'])
result, errors = schema.dump(site)
pprint(result)
# {
#     'blog': {
#         'author': {'email': u'monty@python.org'}
#     }
# }

Note

如果你往Nested是多个对象的列表,传入only可以获得这列表的指定字段。

class UserSchema(Schema):name = fields.String()email = fields.Email()friends = fields.Nested('self', only='name', many=True)
# ... create ``user`` ...
result, errors = UserSchema().dump(user)
pprint(result)
# {
#     "name": "Steve",
#     "email": "steve@example.com",
#     "friends": ["Mike", "Joe"]
# }

这种情况,也可以使用exclude 去掉你不需要的字段。同样这里也可以使用"."操作符。

Two-way Nesting

如果有两个对象需要相互包含,可以指定Nested对象的类名字符串,而不需要类。这样你可以包含一个还未定义的对象:

class AuthorSchema(Schema):# Make sure to use the 'only' or 'exclude' params# to avoid infinite recursionbooks = fields.Nested('BookSchema', many=True, exclude=('author', ))class Meta:fields = ('id', 'name', 'books')class BookSchema(Schema):author = fields.Nested(AuthorSchema, only=('id', 'name'))class Meta:fields = ('id', 'title', 'author')

举个例子,Author类包含很多books,而BookAuthor也有多对一的关系。

from marshmallow import pprint
from mymodels import Author, Bookauthor = Author(name='William Faulkner')
book = Book(title='As I Lay Dying', author=author)
book_result, errors = BookSchema().dump(book)
pprint(book_result, indent=2)
# {
#   "id": 124,
#   "title": "As I Lay Dying",
#   "author": {
#     "id": 8,
#     "name": "William Faulkner"
#   }
# }author_result, errors = AuthorSchema().dump(author)
pprint(author_result, indent=2)
# {
#   "id": 8,
#   "name": "William Faulkner",
#   "books": [
#     {
#       "id": 124,
#       "title": "As I Lay Dying"
#     }
#   ]
# }

Nesting A Schema Within Itself

如果需要自引用,"Nested"构造时传入"self" (包含引号)即可:

class UserSchema(Schema):name = fields.String()email = fields.Email()friends = fields.Nested('self', many=True)# Use the 'exclude' argument to avoid infinite recursionemployer = fields.Nested('self', exclude=('employer', ), default=None)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.data, 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": []
#     }
# }

作者:杨酥饼
链接:https://www.jianshu.com/p/594865f0681b
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

marshmallow文档相关推荐

  1. Marshmallow 快速文档

    Marshmallow Todo: 正文 1. 声明schema 2. 序列化对象 -- dumping 2.1 过滤输出 -- only 3. 反序列化 -- load 4. 处理对象集合(多个对象 ...

  2. 导出swagger2生成的文档

    百度了好多篇用法,没法用.特此记录一下 一.下载项目 下载https://github.com/Swagger2Markup/spring-swagger2markup-demo下的项目,保存,注意文 ...

  3. README 规范和项目文档规范

    1. README 规范 我们直接通过一个 README 模板,来看一下 README 规范中的内容: # 项目名称<!-- 写一段简短的话描述项目 -->## 功能特性<!-- 描 ...

  4. FastAPI 自动生成的docs文档没法使用

    FastAPI 自动生成的docs文档没法使用,当展开路径时候一直在转圈,具体就是这样 这个是由于swagger-ui 3.30.1 中的bug导致,具体bug可以看这里 我们可以通过在FastAPI ...

  5. 【软件工程】VB版机房文档总结

    前言: 软工视频+软工文档+UML视频+UML图的学习过程图! 这部分的知识很厚,只是知道了个大概!最开始 慢悠悠的像个老爷爷走进度,后来遇到点什么事,妈呀,管不了那么多了,赶紧弄完在说,拖了多久了都 ...

  6. 智能文档理解:通用文档预训练模型

    预训练模型到底是什么,它是如何被应用在产品里,未来又有哪些机会和挑战? 预训练模型把迁移学习很好地用起来了,让我们感到眼前一亮.这和小孩子读书一样,一开始语文.数学.化学都学,读书.网上游戏等,在脑子 ...

  7. 基于javaGUI的文档识别工具制作

    基于javaGUI的文档识别工具制作 对于某些文本,其中富含了一些标志,需要去排除,以及去获得段落字数,以下是我个人写的一个比较简单的文档识别工具,含导入文件.导出文件以及一个简单的识别功能. 1.功 ...

  8. 从单一图像中提取文档图像:ICCV2019论文解读

    从单一图像中提取文档图像:ICCV2019论文解读 DewarpNet: Single-Image Document Unwarping With Stacked 3D and 2D Regressi ...

  9. 函数小知识点(文档字符串,闭包等)

    1 文档字符串(Documentation Strings) 一般被称为docstring,一款你应当使用的重要工具,它能够帮助你更好地记录程序并让其更加易于理解.令人惊叹的是,当程序实际运行时,我们 ...

  10. Spring Boot 集成Swagger2生成RESTful API文档

    Swagger2可以在写代码的同时生成对应的RESTful API文档,方便开发人员参考,另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API. 使用Spring Boot可 ...

最新文章

  1. 团队项目第一阶段冲刺站立会议11(4月28日)
  2. JavaScript中innerHTML与innerText,createTextNode的区别
  3. PHP ServerPush (推送) 技术的探讨
  4. join和countDownLatch原理及区别详解
  5. Android 使用jtds远程访问数据库
  6. Jeston TX2安装Ubuntu系统
  7. 讲讲你理解的服务治理
  8. 单例模式 java 实现_单例模式
  9. CCIE Security考试升级了
  10. TR外汇黑平台资金盘深度揭秘,谨防上当受骗,迟早会跑路
  11. 虚拟机下安装MSDN简体中文版win98SC_WIN98SE.exe
  12. 光驱放入空盘,出现无法访问函数不正确(收藏)
  13. 集团化企业的电子印章管理模式
  14. linux多核cpu 优化,Ubuntu是否针对多核CPU进行了优化?
  15. mac 使用的小技巧
  16. 运用matlab求身高质量指数BMI值
  17. 二十一世纪大学英语读写教程(第二册)学习笔记(原文)——7 - Thinking: A Neglected Art(思考——被忽视的艺术)
  18. 周纪五 赧王下四十三年(己丑、前272)——摘要
  19. var与varchar
  20. python将英文翻译为中文_Python使用百度翻译开发平台实现英文翻译为中文功能示例...

热门文章

  1. 利用matlab来设计FIR滤波器参数
  2. android CircleIndicator 实现引导页
  3. plotting matlab,MATLAB - Plotting
  4. 信息安全作业1_等保2.0
  5. python怎么编写口算题_用Python开发小学二年级口算自动出题程序
  6. target triplets
  7. subscript下标
  8. python爬虫程序
  9. 推荐 10 个实用但偏执的 Java 编程技巧
  10. 2015阿里巴巴校招网上笔试