0x00 内容概览

  1. 响应编组
    1. 基本使用
    2. 重命名属性
    3. 默认值
    4. 自定义字段及多值情况
    5. Url及其他具体字段
    6. 复杂结构
    7. 列表字段
    8. 嵌套字段
    9. api.model()工厂
    10. clone实现复制
    11. api.inherit实现多态
    12. 自定义字段
    13. 跳过值为None的字段
    14. 跳过嵌套字段中的None字段
    15. 使用JSON Schema定义模型
  2. 参考链接

0x01 响应编组(Response marshalling)

Flask-RESTPlus提供了一种便捷的方式来控制你在响应中实际渲染的数据,以及在输入载荷(payload)中所期望的数据。利用fields模块,你可以在响应中使用任何对象(ORM模块、自定义类等等)。另外,利用fields也能够实现格式化和过滤响应,这样我们就无需担心暴露内部数据结构的问题。

此外,还有一点好处是,可以很清晰地从你的代码中知道将会渲染什么数据,以及这些数据的格式、结构是怎样的。

1、基本使用

我们可以定义字段的一个字典或者有序字典,其中字典中的key为欲渲染对象的属性名或key,而对应的value则是一个将为该字段格式化并返回值的类。如下面代码所示,该例子中包含三个字段:两个是String类型、一个是格式化为ISO 8601时间字符串(也支持RFC 822)的DateTime类型,如下:

from flask_restplus import Resource, fields

model = api.model('Model', {
'name': fields.String,
'address': fields.String,
'date_updated': fields.DateTime(dt_format='rfc822'),
}) @api.route('/todo')
class Todo(Resource):
@api.marshal_with(model, envelope='resource')
def get(self, **kwargs):
return db_get_todo() # db_get_todo()为某个查询数据的函数

该例子假设你有一个自定义的数据库对象(todo),该对象拥有属性name、address和date_updated。而该对象的其他属性都是私有类型的,且不会在输出中进行渲染。另外,可选参数envelope用来指定封装输出结果。

装饰器marshal_with()接受你的数据对象,并对其按照model格式进行字段过滤。编组(marshalling)可以作用于单个对象、字典或者对象的列表。

注意:marshal_with()是一个很便捷的装饰器,它的作用等价于下面代码:

class Todo(Resource):
def get(self, **kwargs):
return marshal(db_get_todo(), model), 200

而@api.marshal_with装饰器则增加了swagger文档化功能。

2、重命名属性

大多数情况下,你的共有字段名与你内部的字段名都是不相同的。为了实现这一映射关系的配置,我们可以使用attribute参数:

model = {
'name': fields.String(attribute='private_name'),
'address': fields.String,
}

另外,attribute参数的值也可以指定为lambda表达式或者其他可调用的语句:

model = {
'name': fields.String(attribute=lambda x: x._private_name),
'address': fields.String,
}

此外,还可以利用attribute来访问嵌套的属性:

model = {
'name': fields.String(attribute='people_list.0.person_dictionary.name'),
'address': fields.String,
}

3、默认值

如果因为某个原因,你的数据对象中并不包含字段列表中的某个属性,那么我们就可以为该字段指定一个默认的返回值,从而避免返回None:

model = {
'name': fields.String(default='Anonymous User'),
'address': fields.String,
}

4、自定义字段及多值情况

有时候我们也有自定义格式的需求,此时我们可以让我们的类继承类fields.Raw,并实现format方法。当某个属性存储了多个片段的信息时,这一功能尤其方便。例如,一个bit字段的单个位能够代表不同的值。此时,你可以使用字段来乘以某个属性来来得到多个输出值。

下面示例假设flags属性中的第1个bit用来区分“Normal”和“Urgent”项,而第2个bit则用来区分“Read”和“Unread”。虽然这些项很容易存储在一个bit字段中,但是考虑到输出为了便于人们阅读,将它们分别转换成独立的字符串字段则更加优雅友好:

class UrgentItem(fields.Raw):
def format(self, value):
return "Urgent" if value & 0x01 else "Normal" class UnreadItem(fields.Raw):
def format(self, value):
return "Unread" if value & 0x02 else "Read" model = {
'name': fields.String,
'priority': UrgentItem(attribute='flags'),
'status': UnreadItem(attribute='flags'),
}

5、Url及其他具体字段

Flask-RESTPlus包含一个特殊字段fields.Url,它会为正被请求的资源生成一个URI。在为响应添加数据对象中不存在的数据时,这一点也是一个不错的示例:

class RandomNumber(fields.Raw):
def output(self, key, obj):
return random.random() model = {
'name': fields.String,
# todo_resource是我们调用api.route()时为某个资源指定的端点名
'uri': fields.Url('todo_resource'),
'random': RandomNumber,
}

默认情况下,fields.Url返回的是一个相对于根路径的相对URI。而为了生成包含schema(协议)、主机名和端口号的绝对URI,我们只需在字段声明中传入absolute=True的参数项。为了覆盖默认的schema,我们可以传入schema参数:

model = {
'uri': fields.Url('todo_resource', absolute=True)
'https_uri': fields.Url('todo_resource', absolute=True, scheme='https')
}

6、复杂结构

你可以提供一个扁平的结构,而marshal()则会按照定义的规则将其转换成一个嵌套结构:

>>> from flask_restplus import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String}
>>> resource_fields['address'] = {}
>>> resource_fields['address']['line 1'] = fields.String(attribute='addr1')
>>> resource_fields['address']['line 2'] = fields.String(attribute='addr2')
>>> resource_fields['address']['city'] = fields.String
>>> resource_fields['address']['state'] = fields.String
>>> resource_fields['address']['zip'] = fields.String
>>> data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': ''}
>>> json.dumps(marshal(data, resource_fields))
'{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}'

注意:上述示例中的address字段其实并不存在于数据对象中,但是任何子字段都能够直接从对象中访问该属性,就像它们并不是嵌套关系一样。

7、列表字段(List Field)

你也可以将字段解组成列表:

>>> from flask_restplus import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String, 'first_names': fields.List(fields.String)}
>>> data = {'name': 'Bougnazal', 'first_names' : ['Emile', 'Raoul']}
>>> json.dumps(marshal(data, resource_fields))
>>> '{"first_names": ["Emile", "Raoul"], "name": "Bougnazal"}'

8、嵌套字段(Nested Field)

既然嵌套字段使用字典可以将一个扁平数据对象转换成一个嵌套响应,那么你也可以使用Nested来将嵌套的数据结构解组,并对其进行适当的渲染:

>>> from flask_restplus import fields, marshal
>>> import json
>>>
>>> address_fields = {}
>>> address_fields['line 1'] = fields.String(attribute='addr1')
>>> address_fields['line 2'] = fields.String(attribute='addr2')
>>> address_fields['city'] = fields.String(attribute='city')
>>> address_fields['state'] = fields.String(attribute='state')
>>> address_fields['zip'] = fields.String(attribute='zip')
>>>
>>> resource_fields = {}
>>> resource_fields['name'] = fields.String
>>> resource_fields['billing_address'] = fields.Nested(address_fields)
>>> resource_fields['shipping_address'] = fields.Nested(address_fields)
>>> address1 = {'addr1': '123 fake street', 'city': 'New York', 'state': 'NY', 'zip': ''}
>>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': ''}
>>> data = {'name': 'bob', 'billing_address': address1, 'shipping_address': address2}
>>>
>>> json.dumps(marshal(data, resource_fields))
'{"billing_address": {"line 1": "123 fake street", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}, "name": "bob", "shipping_address": {"line 1": "555 nowhere", "line 2": null, "state": "NY", "zip": "10468", "city": "New York"}}'

该示例使用两个Nested字段。Nested构造函数接受一个字段组成的字典,然后将其渲染成一个子fields.input对象。Nested构造函数和嵌套字典(上个例子)之间的重要不同点是:属性的上下文环境。在本例中,billing_address是一个复杂的对象,它拥有自己的字段,而传入到嵌套字段中的上下文环境是子对象,而不是原始的data对象。也就是说:data.billing_address.addr1处于该范围,而在前一示例中,data.addr1则是位置属性。记住:Nested和List对象为属性创建了一个新的作用范围。

默认情况下,当子对象为None时,将会为嵌套字段生成一个包含默认值的对象,而不是null值。可以通过传入allow_null参数来修改这一点,查看Nested构造函数以了解更多信息。

使用Nested和List来编组更复杂对象的列表:

user_fields = api.model('User', {
'id': fields.Integer,
'name': fields.String,
}) user_list_fields = api.model('UserList', {
'users': fields.List(fields.Nested(user_fields)),
})

9、api.model()工厂

model()工厂允许我们实例化并注册模型到我们的API和命名空间(Namespace)中。如下所示:

my_fields = api.model('MyModel', {
'name': fields.String,
'age': fields.Integer(min=0)
}) # 等价于
my_fields = Model('MyModel', {
'name': fields.String,
'age': fields.Integer(min=0)
})
api.models[my_fields.name] = my_fields

10、clone实现复制

Model.clone()方法使得我们可以实例化一个增强模型,它能够省去我们复制所有字段的麻烦:

parent = Model('Parent', {
'name': fields.String
}) child = parent.clone('Child', {
'age': fields.Integer
})

Api/Namespace.clone也会将其注册到API。如下:

parent = api.model('Parent', {
'name': fields.String
}) child = api.clone('Child', parent, {
'age': fields.Integer
})

11、api.inherit实现多态

Model.inherit()方法允许我们以“Swagger”方式扩展模型,并开始解决多态问题:

parent = api.model('Parent', {
'name': fields.String,
'class': fields.String(discriminator=True)
}) child = api.inherit('Child', parent, {
'extra': fields.String
})

Api/Namespace.clone会将parent和child都注册到Swagger模型定义中:

parent = Model('Parent', {
'name': fields.String,
'class': fields.String(discriminator=True)
}) child = parent.inherit('Child', {
'extra': fields.String
})

本例中的class字段只有在其不存在于序列化对象中时,才会以序列化的模型名称进行填充。

Polymorph字段允许你指定Python类和字段规范的映射关系:

mapping = {
Child1: child1_fields,
Child2: child2_fields,
} fields = api.model('Thing', {
owner: fields.Polymorph(mapping)
})

12、自定义字段

自定义输出字段使得我们可以在无需直接修改内部对象的情况下,进行自定义的输出结果格式化操作。我们只需让类继承Raw,并实现format()方法:

class AllCapsString(fields.Raw):
def format(self, value):
return value.upper() # 使用示例
fields = {
'name': fields.String,
'all_caps_name': AllCapsString(attribute='name'),
}

也可以使用__schema_format__、__schema_type__和__schema_example__来指定生成的类型和例子:

class MyIntField(fields.Integer):
__schema_format__ = 'int64' class MySpecialField(fields.Raw):
__schema_type__ = 'some-type'
__schema_format__ = 'some-format' class MyVerySpecialField(fields.Raw):
__schema_example__ = 'hello, world'

13、跳过值为None的字段

我们可以跳过值为None的字段,而无需将这些字段编组为JSON值null。当你拥有很多值可能会为None的字段,而到底哪个字段的值为None又不可预测时,此时该特性在减小响应大小方面的优势就凸显出来了。

下面例子中,我们将可选参数skip_none设置为True:

>>> from flask_restplus import Model, fields, marshal_with
>>> import json
>>> model = Model('Model', {
... 'name': fields.String,
... 'address_1': fields.String,
... 'address_2': fields.String
... })
>>> @marshal_with(model, skip_none=True)
... def get():
... return {'name': 'John', 'address_1': None}
...
>>> get()
{'name', 'John'}

可以看到,address_1和address_2被marshal_with()跳过了。address_1被跳过是因为它的值为None,而address_2被跳过是因为get()返回的字典中并不包含address_2这个key。

14、跳过嵌套字段中的None字段

如果你的模型使用了fields.Nested,那么你需要传递skip_none=True参数到fields.Nested中,只有这样该Nested字段中的子字段为None时才会被跳过:

>>> from flask_restplus import Model, fields, marshal_with
>>> import json
>>> model = Model('Model', {
... 'name': fields.String,
... 'location': fields.Nested(location_model, skip_none=True)
... })

15、使用JSON Schema定义模型

我们可以使用JSON Schema(Draft v4)来定义模型:

address = api.schema_model('Address', {
'properties': {
'road': {
'type': 'string'
},
},
'type': 'object'
}) person = address = api.schema_model('Person', {
'required': ['address'],
'properties': {
'name': {
'type': 'string'
},
'age': {
'type': 'integer'
},
'birthdate': {
'type': 'string',
'format': 'date-time'
},
'address': {
'$ref': '#/definitions/Address',
}
},
'type': 'object'
})

0x02 参考链接

【Flask-RESTPlus系列】Part2:响应编组的更多相关文章

  1. Flask入门系列(转载)

    一.入门系列: Flask入门系列(一)–Hello World 项目开发中,经常要写一些小系统来辅助,比如监控系统,配置系统等等.用传统的Java写,太笨重了,连PHP都嫌麻烦.一直在寻找一个轻量级 ...

  2. Flask开发系列之数据库操作

    Flask开发系列之数据库操作 Python数据库框架 我们可以在Flask中使用MySQL.Postgres.SQLite.Redis.MongoDB 或者 CouchDB. 还有一些数据库抽象层代 ...

  3. Flask开发系列之Web表单

    Flask开发系列之Web表单 简单示例 from flask import Flask, request, render_template app = Flask(__name__) @app.ro ...

  4. Flask开发系列之模板

    Flask开发系列之模板 本文对<FlaskWeb开发:基于python的Web应用开发实战>模板一节做的总结. Jinja2模板引擎 模板 模板是一个包含响应文本的文件,其中包含用占位变 ...

  5. Flask开发系列之快速入门

    Flask开发系列之快速入门 文档 一个最小的应用 调试模式 路由 变量规则 构造 URL HTTP 方法 静态文件 模板渲染 访问请求数据 环境局部变量 请求对象 文件上传 Cookies 重定向和 ...

  6. Flask开发系列之初体验

    Flask开发初探 介绍 在日常开发中,如果需要开发一个小型应用或者Web接口,一般我是极力推崇Flask的,主要是因为其简洁.扩展性高. 从这篇文章开始,我会写一个关于Flask的系列文章,通过多个 ...

  7. Flask的请求与响应

    Flask的请求与响应 1 请求相关信息 request.method # 请求方法 request.args # get 请求的参数 request.form # post请求的参数 request ...

  8. 《Vue 进阶系列之响应式原理及实现》

    https://www.bilibili.com/video/av51444410/?p=5 https://github.com/amandakelake/blog/issues/63 https: ...

  9. Flask框架 请求与响应 & 模板语法

    目录 Flask框架 请求与响应 & 模板语法 简单了解Flask框架 Flask 框架 与 Django 框架对比 简单使用Flask提供服务 Flask 中的 Response(响应) F ...

随机推荐

  1. widnow 下配置php开发环境

    首先下载Php 和 Nginx php 下载链接 nginx 下载链接 下载完成之后 解压到对应目录 (我这里存放在e盘) 解压之后 进入nginx目录 直接双击nginx.exe(一闪而过); 之后 ...

  2. apt-get update 问题

    W: 仓库 “cdrom://Ubuntu 16.04.1 LTS _Xenial Xerus_ - Release amd64 (20160719) xenial Release” 没有 Relea ...

  3. JAVA中执行JavaScript代码并获取返回值

    JAVA中执行JavaScript代码并获取返回值 场景描述 实现思路 技术要点 代码实现 测试方法 运行结果 改进空间 场景描述 今天在CSDN上偶然看到一个帖子对于一段字符串 “var p=‘xx ...

  4. c语言构建哈希表

    /*哈希查找 *哈希函数的构造方法常用的有5种.分别是: *数字分析法 *平方取中法 *分段叠加 *伪随机数 *除留取余法 *这里面除留取余法比较常用 *避免哈希冲突常用的方法有4种: *开放定址法( ...

  5. <笔记>Apache+PHP+MYSQL配置

    (1)Apache的the requested operation has failed错误: cmd—输入netstat –ano,可看到80端口已被进程占用,PID为4 打开任务管理器—〉查看—〉 ...

  6. lightoj 1074

    这题怎么说呢,负环上的点都不行 网上也有很多解法 我用dfs的spfa解的 我发现网上别人的代码都是用bfs的spfa写的,我就用的dfs的,快了好多 代码还看的别人的,只有中间的spfa是自己写的 ...

  7. Objective-C iOS纯代码布局 一堆代码可以放这里!

    前言: 最近写的文章都是创业类,好吧,今天好好写写技术类的文章! 不过分享的不是IOS相关的文章,毕竟这几天在速成IOS,看的是objective-c,由于速成的很快,好累! 好在现在基本已经入了点门 ...

  8. UnityTips:使用反射调用内部方法拓展编辑器

    大家都知道Unity是一个C/C++的游戏引擎,C#只是Unity提供的脚本层.因此大部分功能都是通过C#来调用底层的C++代码的.而一些朋友可能不知道的是,其实Unity的C#代码中也有很多方法是我 ...

  9. 【sql注入】浅谈JSP安全开发之SQL注入

    [sql注入]浅谈JSP安全开发之SQL注入 本文转自:i春秋社区 前言不管是用什么语言编写WEB应用程序,他们都或多或少有一些地方存在漏洞.如果你想知道漏洞的运行原理,和防御方案,那么请看完本篇文章 ...

  10. Java实现链表的常见操作算法

    链表分为单链表,双向链表和循环链表,是一种链式存储结构,由一个个结点链式构成,结点包含数据域和指针域,其中单链表是只有一个指向后驱结点的指针,双向链表除头结点和尾结点外,每个结点都有一个前驱指针和一个 ...