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()
此示例假定您有一个自定义数据库对象 (todo
),该对象 () 具有属性name
、address
和date_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, '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, marshalimport 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
中是位置属性。记住:对象Nested
为List
属性创建了一个新的范围。
默认情况下,当子对象为None时,将生成具有嵌套字段默认值的对象,而不是null。这可以通过传递allow_null参数来修改,Nested
有关更多详细信息,请参阅构造函数。
使用Nested
withList
来编组更复杂对象的列表:
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()
工厂允许您将模型实例化并注册到您的API
或Namespace
.
1 2 3 4 5 6 7 8 9 10 11 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
复制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() 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' })