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. 华为云服务器为Tomcat配置SSL

    近期由于开发小程序需要在云服务器上配置https访问协议,也遇到了一点小问题,把配置过程记录一下:SSL 证书申请下来之后会有 .jks .crt .pfx .pem为后缀的文件(如何申请SSL证书这 ...

  2. UE4物理动画使用

    Rigid Body Body的创建. 对重要骨骼创建Body,保证Body控制的是表现和变化比较大的骨骼. 需要对Root创建Body并绑定,设置为Kinematic且不启用物理.原因是UPrimi ...

  3. NUC970开发板烧录

    本次烧录的采用新塘公司官方的NuWriter软件进行烧录,现在我们首先来讲解如何将uboot,Linux内核,根文件系统烧录到开发板上. 过程中所需文件链接: 链接:https://pan.baidu ...

  4. PowerBI

    1.官方PowerBI实例:https://docs.microsoft.com/zh-cn/power-bi/sample-tutorial-connect-to-the-samples 2.配置计 ...

  5. Linux安装gcc/g++

    直接使用yum安装 yum install gcc yum -y install gcc-c++ 如果为RedHat  yum需要注册 可以参考更换yum源 https://www.cnblogs.c ...

  6. Node.js 开发指南

    1.Node.js 简介 Node.js 其实就是借助谷歌的 V8 引擎,将桌面端的 js 带到了服务器端,它的出现我将其归结为两点: V8 引擎的出色: js 异步 io 与事件驱动给服务器带来极高 ...

  7. jQuery 购物车

    html代码 <!--shoppingCar start-->  <table id="TB">   <tr>    <td colspa ...

  8. TeeChart For VCL/FMX V2017使用教程:第一章-准备开始

    https://blog.csdn.net/vbfgm/article/details/79338775 第一章 准备开始-构建图表和填充数据序列 1.1 简介 通过代码或Dataset(数据集)访问 ...

  9. 【webpack】-- 入门与解析

    每次学新东西总感觉自己是不是变笨了,看了几个博客,试着试着就跑不下去,无奈只有去看官方文档. webpack是基于node的.先安装最新的node. 1.初始化 安装node后,新建一个目录,比如ht ...

  10. python爬虫学习之正则表达式的基本使用

    一.正则表达式 1. 正则表达式是字符串处理的有力工具和技术. 2. 正则表达式使用某种预定义的模式去匹配一类具有共同特征的字符串,主要用于处理字符串,可以快速.准确地完成复杂的查找.替换等处理要求, ...