marshmallow文档
marshmallow
marshmallow是一个用来将复杂的orm对象与python原生数据类型之间相互转换的库,简而言之,就是实现object -> dict
, objects -> list
, string -> dict
和 string -> 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 -> dict
,dumps()
方法实现 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
,例如email
和URL
都有内建的验证器。
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
构造器或者class
的Meta
参数里,则仅会在传入无效数据是报错。可以使用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方法中的PUT
或PATCH
方法,使用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,而Book
对Author
也有多对一的关系。
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文档相关推荐
- Marshmallow 快速文档
Marshmallow Todo: 正文 1. 声明schema 2. 序列化对象 -- dumping 2.1 过滤输出 -- only 3. 反序列化 -- load 4. 处理对象集合(多个对象 ...
- 导出swagger2生成的文档
百度了好多篇用法,没法用.特此记录一下 一.下载项目 下载https://github.com/Swagger2Markup/spring-swagger2markup-demo下的项目,保存,注意文 ...
- README 规范和项目文档规范
1. README 规范 我们直接通过一个 README 模板,来看一下 README 规范中的内容: # 项目名称<!-- 写一段简短的话描述项目 -->## 功能特性<!-- 描 ...
- FastAPI 自动生成的docs文档没法使用
FastAPI 自动生成的docs文档没法使用,当展开路径时候一直在转圈,具体就是这样 这个是由于swagger-ui 3.30.1 中的bug导致,具体bug可以看这里 我们可以通过在FastAPI ...
- 【软件工程】VB版机房文档总结
前言: 软工视频+软工文档+UML视频+UML图的学习过程图! 这部分的知识很厚,只是知道了个大概!最开始 慢悠悠的像个老爷爷走进度,后来遇到点什么事,妈呀,管不了那么多了,赶紧弄完在说,拖了多久了都 ...
- 智能文档理解:通用文档预训练模型
预训练模型到底是什么,它是如何被应用在产品里,未来又有哪些机会和挑战? 预训练模型把迁移学习很好地用起来了,让我们感到眼前一亮.这和小孩子读书一样,一开始语文.数学.化学都学,读书.网上游戏等,在脑子 ...
- 基于javaGUI的文档识别工具制作
基于javaGUI的文档识别工具制作 对于某些文本,其中富含了一些标志,需要去排除,以及去获得段落字数,以下是我个人写的一个比较简单的文档识别工具,含导入文件.导出文件以及一个简单的识别功能. 1.功 ...
- 从单一图像中提取文档图像:ICCV2019论文解读
从单一图像中提取文档图像:ICCV2019论文解读 DewarpNet: Single-Image Document Unwarping With Stacked 3D and 2D Regressi ...
- 函数小知识点(文档字符串,闭包等)
1 文档字符串(Documentation Strings) 一般被称为docstring,一款你应当使用的重要工具,它能够帮助你更好地记录程序并让其更加易于理解.令人惊叹的是,当程序实际运行时,我们 ...
- Spring Boot 集成Swagger2生成RESTful API文档
Swagger2可以在写代码的同时生成对应的RESTful API文档,方便开发人员参考,另外Swagger2也提供了强大的页面测试功能来调试每个RESTful API. 使用Spring Boot可 ...
最新文章
- 团队项目第一阶段冲刺站立会议11(4月28日)
- JavaScript中innerHTML与innerText,createTextNode的区别
- PHP ServerPush (推送) 技术的探讨
- join和countDownLatch原理及区别详解
- Android 使用jtds远程访问数据库
- Jeston TX2安装Ubuntu系统
- 讲讲你理解的服务治理
- 单例模式 java 实现_单例模式
- CCIE Security考试升级了
- TR外汇黑平台资金盘深度揭秘,谨防上当受骗,迟早会跑路
- 虚拟机下安装MSDN简体中文版win98SC_WIN98SE.exe
- 光驱放入空盘,出现无法访问函数不正确(收藏)
- 集团化企业的电子印章管理模式
- linux多核cpu 优化,Ubuntu是否针对多核CPU进行了优化?
- mac 使用的小技巧
- 运用matlab求身高质量指数BMI值
- 二十一世纪大学英语读写教程(第二册)学习笔记(原文)——7 - Thinking: A Neglected Art(思考——被忽视的艺术)
- 周纪五 赧王下四十三年(己丑、前272)——摘要
- var与varchar
- python将英文翻译为中文_Python使用百度翻译开发平台实现英文翻译为中文功能示例...