flask restplus marshal-响应编组

Flask-RESTPlus 提供了一种简单的方法来控制您在响应中实际呈现的数据或期望在输入负载中的数据。使用该fields模块,您可以在资源中使用所需的任何对象(ORM 模型/自定义类/等)。 fields还允许您格式化和过滤响应,因此您不必担心暴露内部数据结构。

在查看您的代码时,也非常清楚将呈现哪些数据以及将如何格式化。

基本用法

model 参数可以定义一个 dict 或 OrderedDict 字段,其键是要呈现的对象上的属性或键的名称,其值是一个将格式化并返回该字段值的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from flask_restplus import Resource, fields, Api

api = Api()
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() # Some function that queries the db

此示例假定您有一个自定义数据库对象 (todo),该对象 () 具有属性nameaddressdate_updated。对象上的任何其他属性都被视为私有属性,不会在输出中呈现。指定一个可选的envelope关键字参数来包装结果输出。

装饰器marshal_with()实际上是获取您的数据对象并应用字段过滤。他可以处理单个对象、字典或对象列表。

marshal_with()是一个便利装饰器,在功能上等同于:

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

@api.marshal_with(model, 302) 装饰器添加了招摇的文档能力。还可以使用显式表达式返回 200 以外的 HTTP 状态代码以及成功响应。

重命名属性

通常,您面向公众的字段名称与您的内部字段名称不同。要配置此映射,请使用attribute关键字参数。

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

lambda(或任何可调用的)也可以指定为attribute

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

嵌套属性也可以通过以下方式访问attribute

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

默认值

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

自定义字段和多个值

有时您有自己的自定义格式需求。您可以子类化fields.Raw该类并实现格式功能。这在属性存储多条信息时特别有用。例如,一个位域,其各个位代表不同的值。您可以使用字段将单个属性多路复用到多个输出值。

此示例假定flags属性中的第 1 位表示“正常”或“紧急”项目,第 2 位表示“已读”或“未读”。这些项目可能很容易存储在位域中,但对于人类可读的输出,最好将它们转换为单独的字符串字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
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'),
}

网址和其他具体字段

Flask-RESTPlus 包含一个特殊字段 ,fields.Url它为所请求的资源合成一个 uri。这也是一个很好的例子,说明如何将数据添加到您的响应中,而这些数据实际上并不存在于您的数据对象中。

1
2
3
4
5
6
7
8
9
10
class RandomNumber(fields.Raw):
def output(self, key, obj):
return random.random()

model = {
'name': fields.String,
# todo_resource is the endpoint name when you called api.route()
'uri': fields.Url('todo_resource'),
'random': RandomNumber,
}

默认情况下fields.Url返回一个相对 uri。要生成包含方案、主机名和端口的绝对 uri,请absolute=True在字段声明中传递关键字参数。要覆盖默认方案,请传递scheme关键字参数:

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

复杂结构

您可以拥有一个marshal()将转换为嵌套结构的平面结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from flask_restplus import fields, marshal
import json


resource_fields = {
'name': fields.String,
'address': {
'line 1': fields.String(attribute='addr1'),
'line 2': fields.String(attribute='addr2'),
'city': fields.String,
'state': fields.String,
'zip': fields.String,
}
}

data = {'name': 'bob', 'addr1': '123 fake street', 'addr2': '', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
json.dumps(marshal(data, resource_fields))
'{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}'

地址字段实际上并不存在于数据对象上,但任何子字段都可以直接从对象访问属性,就好像它们没有嵌套一样。

列表字段

您还可以将字段解组为列表

1
2
3
4
5
6
7
>>> 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"}'

通配符字段

如果您不知道要解组的字段的名称,可以使用Wildcard

1
2
3
4
5
6
7
8
>>> from flask_restplus import fields, marshal
>>> import json
>>>
>>> wild = fields.Wildcard(fields.String)
>>> wildcard_fields = {'*': wild}
>>> data = {'John': 12, 'bob': 42, 'Jane': '68'}
>>> json.dumps(marshal(data, wildcard_fields))
>>> '{"Jane": "68", "bob": "42", "John": "12"}'

你给你的名字Wildcard就像一个真正的球体,如下所示

1
2
3
4
5
6
7
8
>>> from flask_restplus import fields, marshal
>>> import json
>>>
>>> wild = fields.Wildcard(fields.String)
>>> wildcard_fields = {'j*': wild}
>>> data = {'John': 12, 'bob': 42, 'Jane': '68'}
>>> json.dumps(marshal(data, wildcard_fields))
>>> '{"Jane": "68", "John": "12"}'

重要的是你在你的模型之外定义你的模型(即你不能像这样使用它 :),因为它必须是有状态的,以跟踪它已经处理过的字段。Wildcard res_fields = {'*': fields.Wildcard(fields.String)}

glob 不是正则表达式,它只能处理简单的通配符,如 ‘*‘ 或 ‘?’。

为了避免出现意外行为,在Wildcard与其他字段混合时,您可能希望使用 anOrderedDict并使用 the Wildcard作为最后一个字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> from flask_restplus import fields, marshal
>>> from collections import OrderedDict
>>> import json
>>>
>>> wild = fields.Wildcard(fields.Integer)
>>> mod = OrderedDict()
>>> mod['zoro'] = fields.String
>>> mod['*'] = wild
>>> # you can use it in api.model like this:
>>> # some_fields = api.model('MyModel', mod)
>>>
>>> data = {'John': 12, 'bob': 42, 'Jane': '68', 'zoro': 72}
>>> json.dumps(marshal(data, mod))
>>> '{"zoro": "72", "Jane": 68, "bob": 42, "John": 12}'

嵌套字段 fields.Nested

虽然使用 dicts 嵌套字段可以将平面数据对象转换为嵌套响应,但您可以使用它Nested来解组嵌套数据结构并适当地呈现它们。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>>> 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': '10468'}
>>> address2 = {'addr1': '555 nowhere', 'city': 'New York', 'state': 'NY', 'zip': '10468'}
>>> 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 函数需要一个字段字典来呈现为 sub-fields.input。构造函数和嵌套字典(上一个示例)之间的重要区别在于Nested属性的上下文。在此示例中, billing_address是一个具有自己的字段的复杂对象,并且传递给嵌套字段的上下文是子对象而不是原始data对象。换句话说: data.billing_address.addr1在这里是在范围内,而在前面的例子data.addr1中是位置属性。记住:对象NestedList属性创建了一个新的范围。

默认情况下,当子对象为None时,将生成具有嵌套字段默认值的对象,而不是null。这可以通过传递allow_null参数来修改,Nested有关更多详细信息,请参阅构造函数。

使用NestedwithList来编组更复杂对象的列表:

1
2
3
4
5
6
7
8
user_fields = api.model('User', {
'id': fields.Integer,
'name': fields.String,
})

user_list_fields = api.model('UserList', {
'users': fields.List(fields.Nested(user_fields)),
})

api.model()工厂 _

model()工厂允许您将模型实例化并注册到您的APINamespace.

1
2
3
4
5
6
7
8
9
10
11
my_fields = api.model('MyModel', {
'name': fields.String,
'age': fields.Integer(min=0)
})

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

复制clone

Model.clone()方法允许您实例化增强模型。它可以节省您复制所有字段。

1
2
3
4
5
6
7
parent = Model('Parent', {
'name': fields.String
})

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

Api/Namespace.clone还可以在 API 上注册它。

1
2
3
4
5
6
7
parent = api.model('Parent', {
'name': fields.String
})

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

多态性api.inherit

Model.inherit()方法允许以“Swagger 方式”扩展模型并开始处理多态性。

1
2
3
4
5
6
7
8
parent = api.model('Parent', {
'name': fields.String,
'class': fields.String(discriminator=True)
})

child = api.inherit('Child', parent, {
'extra': fields.String
})

Api/Namespace.clone在 Swagger 模型定义中注册父级和子级。

1
2
3
4
5
6
7
8
parent = Model('Parent', {
'name': fields.String,
'class': fields.String(discriminator=True)
})

child = parent.inherit('Child', {
'extra': fields.String
})

class仅当序列化对象中不存在该属性时,才会使用序列化模型名称填充此示例中的字段。

Polymorph字段允许您指定 Python 类和字段规范之间的映射。

1
2
3
4
5
6
7
8
mapping = {
Child1: child1_fields,
Child2: child2_fields,
}

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

自定义字段

自定义输出字段允许您执行自己的输出格式,而无需直接修改内部对象。您所要做的就是子类Raw化并实现该format()方法:

1
2
3
4
5
6
7
8
9
10
class AllCapsString(fields.Raw):
def format(self, value):
return value.upper()


# example usage
fields = {
'name': fields.String,
'all_caps_name': AllCapsString(attribute='name'),
}

您还可以使用__schema_format__,__schema_type____schema_example__来指定生成的类型和示例:

1
2
3
4
5
6
7
8
9
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'

跳过值为 None 的字段

您可以跳过那些值的字段,None而不是使用 JSON 值 null 封送这些字段。None当您有很多字段的值可能为 None 但哪些字段是不可预测的时,此功能可用于减少响应的大小。

让我们考虑以下示例,其中一个可选的skip_none关键字参数设置为 True。

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> 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()
OrderedDict([('name', 'John')])

您可以看到address_1并被address_2跳过marshal_with()。 address_1被跳过,因为 value 是None。 address_2被跳过,因为字典返回的get()没有键,address_2。

在嵌套字段中跳过任何内容

如果您的模块使用fields.Nested,则需要将skip_none=True关键字参数传递给fields.Nested.

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

使用 JSON Schema 定义模型

您可以使用JSON Schema (Draft v4) 定义模型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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'
})

flask restplus marshal-响应编组
https://flepeng.github.io/021-Python-32-框架-Flask-flask-restplus-flask-restplus-marshal-响应编组/
作者
Lepeng
发布于
2021年3月31日
许可协议