Flask 学习-47.Flask-RESTX 自定义响应内容marshal_with

前言

Flask-RESTX 提供了一种简单的方法来控制您在响应中实际呈现的数据或期望作为输入有效负载的数据。使用该fields模块,您可以在资源中使用所需的任何对象(ORM 模型/自定义类/等)。 fields还允许您格式化和过滤响应,因此您不必担心暴露内部数据结构。
在查看您的代码时,也非常清楚将呈现哪些数据以及将如何格式化。

基本用法

user模型

class Users(db.Model):
    __tablename__ = 'user'  # 数据库表名
    id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    username = db.Column(db.String(50), unique=True, nullable=False)
    password = db.Column(db.String(128), nullable=False)
    is_active = db.Column(db.Boolean, default=1)
    email = db.Column(db.String(64), nullable=True)
    create_time = db.Column(db.DateTime, default=datetime.now)
    update_time = db.Column(db.DateTime, onupdate=datetime.now, default=datetime.now)

    def hash_password(self, password):
        """密码加密"""
        self.password = sha256_crypt.encrypt(password)

    def verify_password(self, password):
        """校验密码"""
        return sha256_crypt.verify(password, self.password)

    def __repr__(self):
        return f"<Users(id='{self.id}', username='{self.username}'...)>"

同步数据库后新增一条数据

自定义数据库对象 (User),该对象具有属性name、address和date_updated。对象上的任何其他属性都被视为私有属性,不会在输出中呈现。

from flask_restx import Resource, Api, reqparse, fields

api = Api(app)


model = api.model('Model', {
    'username': fields.String,
    'email': fields.String,
    'create_time': fields.DateTime(dt_format='rfc822'),
})


@api.route('/todo')
class Todo(Resource):
    @api.marshal_with(model, envelope='resource')
    def get(self, **kwargs):
        user = Users.query.get(1)      # 查询Users表数据
        return user


if __name__ == '__main__':
    app.run(debug=True)

envelope='resource'关键字参数用来包装结果输出,返回结果示例

{
    "resource": {
        "username": "test",
        "email": null,
        "create_time": "Mon, 05 Sep 2022 11:13:16 -0000"
    }
}

如果去掉envelope='resource'关键字参数,那么返回

{
    "username": "test",
    "email": null,
    "create_time": "Mon, 05 Sep 2022 11:13:16 -0000"
}

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

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

class Todo(Resource):
    def get(self, **kwargs):
        return marshal(db_get_todo(), model), 200
@api.marshal_with装饰器添加了 swagger 的文档能力。

重命名属性

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

model = api.model('Model', {
    'name': fields.String(attribute='username'),
    'email': fields.String,
    'create_time': fields.DateTime(dt_format='rfc822'),
})

那么会输出

{
    "resource": {
        "name": "test",
        "email": null,
        "create_time": "Mon, 05 Sep 2022 11:13:16 -0000"
    }
}

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

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,
}

默认值

如果由于某种原因您的数据对象在字段列表中没有属性,您可以指定要返回的默认值而不是None.

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

自定义字段和多个值

有时您有自己的自定义格式需求。您可以子类化fields.Raw该类并实现格式功能。这在属性存储多条信息时特别有用。例如,一个位域,其各个位代表不同的值。您可以使用字段将单个属性多路复用到多个输出值。
此示例假定flags属性中的第 1 位表示“正常”或“紧急”项目,第 2 位表示“已读”或“未读”。这些项目可能很容易存储在位域中,但对于人类可读的输出,最好将它们转换为单独的字符串字段。

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

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关键字参数:

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

复杂结构

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

>>> from flask_restx 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': '10468'}
>>> json.dumps(marshal(data, resource_fields))
'{"name": "bob", "address": {"line 1": "123 fake street", "line 2": "", "state": "NY", "zip": "10468", "city": "New York"}}'

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

列表字段

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

>>> from flask_restx 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

>>> from flask_restx 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就像一个真正的球体,如下所示

>>> from flask_restx 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作为最后一个字段

>>> from flask_restx import fields, marshal
>>> import json
>>>
>>> wild = fields.Wildcard(fields.Integer)
>>> # you can use it in api.model like this:
>>> # some_fields = api.model('MyModel', {'zoro': fields.String, '*': wild})
>>>
>>> data = {'John': 12, 'bob': 42, 'Jane': '68', 'zoro': 72}
>>> json.dumps(marshal(data, mod))
>>> '{"zoro": "72", "Jane": 68, "bob": 42, "John": 12}'

嵌套字段

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

>>> from flask_restx 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中是位置属性。请记住:对象Nested为List属性创建了一个新范围。

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

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

user_fields = api.model('User', {
    'id': fields.Integer,
    'name': fields.String,
})

user_list_fields = api.model('UserList', {
    'users': fields.List(fields.Nested(user_fields)),
})
posted @ 2022-09-05 11:29  上海-悠悠  阅读(794)  评论(0编辑  收藏  举报