天天看點

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}'...)>"      

同步資料庫後新增一條資料

Flask 學習-47.Flask-RESTX 自定義響應内容marshal_with

自定義資料庫對象 (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有關更多詳細資訊,請參閱構造函數。

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

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