今天来学习下Flask-RESTful
插件,该插件支持在Flask
框架快速创建RESTful
样式APIS
。它是一种轻量级抽象,可与您现有的 ORM
/库一起使用。 Flask-RESTful
鼓励以最少的设置进行最佳实践。如果您熟悉 Flask、Flask-RESTful
应该很容易上手。
发布版本
- 0.0.1:2012/6/6发布
- 0.1:2012/10/19发布
- 0.1.1:2012/11/20发布
- 0.1.2:2013/1/9发布
- ……
- 0.3.8:2020/2/6发布
- 0.3.9:2021/3/18发布
安装
可以使用pip
安装flask-restful
,使用命令如下:
$ pip install flask-restful
也可以从 GitHub 上的页面下载开发版本。
$ git clone https://github.com/flask-restful/flask-restful.git
$ cd flask-restful
$ python setup.py develop
Flask-RESTful
需要 Python
版本 2.7、3.4、3.5、3.6
或 3.7
。
示例
如下给出一个简单的示例来了解如何使用Flask-restful
插件:
from flask import Flask
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
class HelloWorld(Resource):
def get(self):
return {'hello': 'world'}
api.add_resource(HelloWorld, '/')
if __name__ == '__main__':
app.run(debug=True)
将其保存为 api.py
并使用 Python
解释器运行它。请注意,我们启用了 Flask
调试模式以提供代码重新加载和更好的错误消息。
$ python api.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader
现在打开一个新提示以使用 curl
测试您的 API
:
$ curl http://127.0.0.1:5000/
{"hello": "world"}
Resource
Flask-restful
基于Resource
构建组件,资源建立在 Flask
可插入视图之上,让您只需在资源上定义方法即可轻松访问多个 HTTP
方法。 todo 应用程序的基本 CRUD
资源(当然)如下所示:
from flask import Flask, request
from flask_restful import Resource, Api
app = Flask(__name__)
api = Api(app)
todos = {}
class TodoSimple(Resource):
def get(self, todo_id):
return {todo_id: todos[todo_id]}
def put(self, todo_id):
todos[todo_id] = request.form['data']
return {todo_id: todos[todo_id]}
api.add_resource(TodoSimple, '/<string:todo_id>')
if __name__ == '__main__':
app.run(debug=True)
请求示例如下:
$ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT
{"todo1": "Remember the milk"}
$ curl http://localhost:5000/todo1
{"todo1": "Remember the milk"}
$ curl http://localhost:5000/todo2 -d "data=Change my brakepads" -X PUT
{"todo2": "Change my brakepads"}
$ curl http://localhost:5000/todo2
{"todo2": "Change my brakepads"}
或者从 python
如果你安装了请求库:
>>> from requests import put, get
>>> put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json()
{u'todo1': u'Remember the milk'}
>>> get('http://localhost:5000/todo1').json()
{u'todo1': u'Remember the milk'}
>>> put('http://localhost:5000/todo2', data={'data': 'Change my brakepads'}).json()
{u'todo2': u'Change my brakepads'}
>>> get('http://localhost:5000/todo2').json()
{u'todo2': u'Change my brakepads'}
Flask-RESTful
理解多种视图方法的返回值。与 Flask
类似,您可以返回任何可迭代对象并将其转换为响应,包括原始 Flask
响应对象。 Flask-RESTful
还支持使用多个返回值设置响应码和响应头,如下所示:
class Todo1(Resource):
def get(self):
# Default to 200 OK
return {'task': 'Hello world'}
class Todo2(Resource):
def get(self):
# Set the response code to 201
return {'task': 'Hello world'}, 201
class Todo3(Resource):
def get(self):
# Set the response code to 201 and return custom headers
return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'}
端点
很多时候在一个 API 中,您的资源会有多个 URL。您可以将多个 URL 传递给 Api 对象上的 add_resource() 方法。每一个都将被路由到您的资源。
api.add_resource(HelloWorld,
'/',
'/hello')
您还可以将部分路径作为变量匹配到您的资源方法。
api.add_resource(Todo,
'/todo/<int:todo_id>', endpoint='todo_ep')
参数解析
虽然 Flask
提供了对请求数据(即查询字符串或 POST
表单编码数据)的轻松访问,但验证表单数据仍然很痛苦。 Flask-RESTful
使用类似于 argparse
的库内置了对请求数据验证的支持。
from flask_restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate to charge for this resource')
args = parser.parse_args()
与 argparse
模块不同,reqparse.RequestParser.parse_args()
返回 Python
字典而不是自定义数据结构。
使用 reqparse
模块还可以免费为您提供合理的错误消息。如果一个参数未能通过验证,Flask-RESTful
将响应 400 Bad Request
和一个突出显示错误的响应。
$ curl -d 'rate=foo' http://127.0.0.1:5000/todos
{'status': 400, 'message': 'foo cannot be converted to int'}
inputs
模块提供了许多包含的通用转换函数,例如 inputs.date()
和 inputs.url()
。
使用 strict=True
调用 parse_args
可确保在请求包含您的解析器未定义的参数时抛出错误。
args = parser.parse_args(strict=True)
数据格式
默认情况下,您的返回峰值中的所有字段都将呈现为as-is
。虽然您只是在处理Python
数据结构时效果很好,但在使用对象时,它可能会变得非常令人沮丧。为了解决这个问题,Flask-RESTful
捕获提供了字段模块和元marshal_with()
装饰器。类似于Django Orm
和WTForm
,您使用fields
模块来描述响应的结构。
from flask_restful import fields, marshal_with
resource_fields = {
'task': fields.String,
'uri': fields.Url('todo_ep')
}
class TodoDao(object):
def __init__(self, todo_id, task):
self.todo_id = todo_id
self.task = task
# This field will not be sent in the response
self.status = 'active'
class Todo(Resource):
@marshal_with(resource_fields)
def get(self, **kwargs):
return TodoDao(todo_id='my_todo', task='Remember the milk')
上面的示例获取一个 python
对象并准备将其序列化。 marshal_with()
装饰器将应用 resource_fields
描述的转换。从对象中提取的唯一字段是任务。 fields.Url
字段是一个特殊字段,它采用端点名称并在响应中为该端点生成 URL
。您需要的许多字段类型已包含在内。有关完整列表,请参阅fields
指南。
完整示例
将此示例保存在 api.py
中:
from flask import Flask
from flask_restful import reqparse, abort, Api, Resource
app = Flask(__name__)
api = Api(app)
TODOS = {
'todo1': {'task': 'build an API'},
'todo2': {'task': '?????'},
'todo3': {'task': 'profit!'},
}
def abort_if_todo_doesnt_exist(todo_id):
if todo_id not in TODOS:
abort(404, message="Todo {} doesn't exist".format(todo_id))
parser = reqparse.RequestParser()
parser.add_argument('task')
# Todo
# shows a single todo item and lets you delete a todo item
class Todo(Resource):
def get(self, todo_id):
abort_if_todo_doesnt_exist(todo_id)
return TODOS[todo_id]
def delete(self, todo_id):
abort_if_todo_doesnt_exist(todo_id)
del TODOS[todo_id]
return '', 204
def put(self, todo_id):
args = parser.parse_args()
task = {'task': args['task']}
TODOS[todo_id] = task
return task, 201
# TodoList
# shows a list of all todos, and lets you POST to add new tasks
class TodoList(Resource):
def get(self):
return TODOS
def post(self):
args = parser.parse_args()
todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1
todo_id = 'todo%i' % todo_id
TODOS[todo_id] = {'task': args['task']}
return TODOS[todo_id], 201
##
## Actually setup the Api resource routing here
##
api.add_resource(TodoList, '/todos')
api.add_resource(Todo, '/todos/<todo_id>')
if __name__ == '__main__':
app.run(debug=True)
运行文件并模拟请求获取:
$ python api.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader
$ curl http://localhost:5000/todos
{"todo1": {"task": "build an API"}, "todo3": {"task": "profit!"}, "todo2": {"task": "?????"}}
$ curl http://localhost:5000/todos/todo3
{"task": "profit!"}
$ curl http://localhost:5000/todos/todo2 -X DELETE -v
> DELETE /todos/todo2 HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
> Host: localhost:5000
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 204 NO CONTENT
< Content-Type: application/json
< Content-Length: 0
< Server: Werkzeug/0.8.3 Python/2.7.2
< Date: Mon, 01 Oct 2012 22:10:32 GMT
$ curl http://localhost:5000/todos -d "task=something new" -X POST -v
> POST /todos HTTP/1.1
> User-Agent: curl/7.19.7 (universal-apple-darwin10.0) libcurl/7.19.7 OpenSSL/0.9.8l zlib/1.2.3
> Host: localhost:5000
> Accept: */*
> Content-Length: 18
> Content-Type: application/x-www-form-urlencoded
>
* HTTP 1.0, assume close after body
< HTTP/1.0 201 CREATED
< Content-Type: application/json
< Content-Length: 25
< Server: Werkzeug/0.8.3 Python/2.7.2
< Date: Mon, 01 Oct 2012 22:12:58 GMT
<
* Closing connection #0
{"task": "something new"}
$ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v
> PUT /todos/todo3 HTTP/1.1
> Host: localhost:5000
> Accept: */*
> Content-Length: 20
> Content-Type: application/x-www-form-urlencoded
>
* HTTP 1.0, assume close after body
< HTTP/1.0 201 CREATED
< Content-Type: application/json
< Content-Length: 27
< Server: Werkzeug/0.8.3 Python/2.7.3
< Date: Mon, 01 Oct 2012 22:13:00 GMT
<
* Closing connection #0
{"task": "something different"}
请求解析
Flask-Restful
的请求解析界面Reqparse
是按照ArgParse
接口建模的。它旨在对flask.request
提供对任何变量的简单均匀访问。
基本参数
这是请求解析器的一个简单示例。它在 flask.Request.values
字典中寻找两个参数:一个整数和一个字符串。
from flask_restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate cannot be converted')
parser.add_argument('name')
args = parser.parse_args()
如果您指定帮助值,则在解析它时出现类型错误时,它将呈现为错误消息。如果不指定帮助消息,则默认行为是从类型错误本身返回消息。
必需的参数
要要求为参数传递值,只需将 required=True
添加到对 add_argument()
的调用中。
parser.add_argument('name', required=True,
help="Name cannot be blank!")
多个值和列表
如果要接受键作为列表的多个值,则可以传递action='append'
parser.add_argument('name', action='append')
这将使您可以进行查询:
curl http://api.example.com -d "name=bob" -d "name=sue" -d "name=joe"
你的参数看起来像这样:
args = parser.parse_args()
args['name'] # ['bob', 'sue', 'joe']
其他Destinations
如果出于某种原因,RequestParser尝试从flask.Request,flask.Request.json中解析值。使add_argument() 的location
参数指定备用位置以从中提取值。 flask.Request
上的任何变量都可以使用。例如:
# Look only in the POST body
parser.add_argument('name', type=int, location='form')
# Look only in the querystring
parser.add_argument('PageSize', type=int, location='args')
# From the request headers
parser.add_argument('User-Agent', location='headers')
# From http cookies
parser.add_argument('session_id', location='cookies')
# From file uploads
parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files')
输出字段
Flask-RESTful
提供了一种简单的方法来控制您在响应中实际呈现的数据。使用 fields
模块,您可以在资源中使用您想要的任何对象(ORM
模型/自定义类等)。字段还允许您格式化和过滤响应,因此您不必担心暴露内部数据结构。
基础用法
您可以定义字段的 dict
或 OrderedDict
,其键是要呈现的对象上的属性或键的名称,其值是将格式化并返回该字段值的类。此示例包含三个字段:两个是String
,一个是DateTime
,格式为 RFC 822
日期字符串(也支持 ISO 8601
)
from flask_restful import Resource, fields, marshal_with
resource_fields = {
'name': fields.String,
'address': fields.String,
'date_updated': fields.DateTime(dt_format='rfc822'),
}
class Todo(Resource):
@marshal_with(resource_fields, envelope='resource')
def get(self, **kwargs):
return db_get_todo() # Some function that queries the db
重命名属性
通常,您面向公众的字段名称与您的内部字段名称不同。要配置此映射,请使用attribute
关键字参数。
fields = {
'name': fields.String(attribute='private_name'),
'address': fields.String,
}
lambda
(或任何可调用的)也可以指定为属性:
fields = {
'name': fields.String(attribute=lambda x: x._private_name),
'address': fields.String,
}
也可以使用属性访问嵌套属性:
fields = {
'name': fields.String(attribute='people_list.0.person_dictionary.name'),
'address': fields.String,
}
默认值
如果由于某种原因你的数据对象在你的字段列表中没有属性,你可以指定一个默认值来返回而不是 None
。
fields = {
'name': fields.String(default='Anonymous User'),
'address': fields.String,
}
自定义字段和多个值
有时您有自己的自定义格式需求。您可以子类化 fields.Raw
类并实现格式功能。当一个属性存储多条信息时,这尤其有用。例如一个位字段,其各个位代表不同的值。您可以使用字段将单个属性多路复用为多个输出值。
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"
fields = {
'name': fields.String,
'priority': UrgentItem(attribute='flags'),
'status': UnreadItem(attribute='flags'),
}
Url和其他具体字段
Flask-RESTful包括一个特殊字段,fields.url,该字段合成了所需的资源的URI。这也是一个很好的例子,说明如何将数据添加到您的数据对象上实际上并不存在的响应中。
class RandomNumber(fields.Raw):
def output(self, key, obj):
return random.random()
fields = {
'name': fields.String,
# todo_resource is the endpoint name when you called api.add_resource()
'uri': fields.Url('todo_resource'),
'random': RandomNumber,
}
扩展 Flask-RESTful
我们意识到每个人对 REST
框架都有不同的需求。 Flask-RESTful
试图尽可能灵活,但有时您可能会发现内置功能不足以满足您的需求。 Flask-RESTful
有几个不同的扩展点可以在这种情况下提供帮助。
内容协商
开箱即用, Flask-RESTful
仅配置为支持JSON
。我们决定使API维护者完全控制API
格式支持;因此,您不必使用您不知道存在的API
的CSV
代表来支持人们。要在API
中添加其他介体,您需要在API
对象上声明所支持的表示形式。
app = Flask(__name__)
api = Api(app)
@api.representation('application/json')
def output_json(data, code, headers=None):
resp = make_response(json.dumps(data), code)
resp.headers.extend(headers or {})
return resp
这些表示函数必须返回一个 Flask Response
对象。通过在应用程序配置中提供 RESTFUL_JSON
属性,可以配置默认 Flask-RESTful JSON
表示如何格式化 JSON
。此设置是一个字典,其键对应于 json.dumps()
的关键字参数。
class MyConfig(object):
RESTFUL_JSON = {'separators': (', ', ': '),
'indent': 2,
'cls': MyCustomEncoder}
自定义字段和输入
Flask-RESTful
最常见的添加之一是根据您自己的数据类型定义自定义类型或字段。
Fields
自定义输出字段可让您执行自己的输出格式,而无需直接修改内部对象。您要做的就是子类RAW
并实现format()
方法:
class AllCapsString(fields.Raw):
def format(self, value):
return value.upper()
# example usage
fields = {
'name': fields.String,
'all_caps_name': AllCapsString(attribute=name),
}
Inputs
对于解析参数,您可能需要执行自定义验证。创建自己的输入类型可让您轻松扩展请求解析。
def odd_number(value):
if value % 2 == 0:
raise ValueError("Value is not odd")
return value
请求解析器还将使您在要在错误消息中引用名称的情况下访问参数名称。
def odd_number(value, name):
if value % 2 == 0:
raise ValueError("The parameter '{}' is not odd. You gave us the value: {}".format(name, value))
return value
您还可以将公共参数值转换为内部表示:
# maps the strings to their internal integer representation
# 'init' => 0
# 'in-progress' => 1
# 'completed' => 2
def task_status(value):
statuses = [u"init", u"in-progress", u"completed"]
return statuses.index(value)
然后您可以在 RequestParser
中使用这些自定义输入类型:
parser = reqparse.RequestParser()
parser.add_argument('OddNumber', type=odd_number)
parser.add_argument('Status', type=task_status)
args = parser.parse_args()
响应格式
要支持其他表示形式(xml、csv、html),您可以使用 representation() 装饰器。您需要引用您的 API。
api = Api(app)
@api.representation('text/csv')
def output_csv(data, code, headers=None):
pass
# implement csv output!
实现此目的的另一种方法是继承 Api
类并提供您自己的输出函数。
class Api(restful.Api):
def __init__(self, *args, **kwargs):
super(Api, self).__init__(*args, **kwargs)
self.representations = {
'application/xml': output_xml,
'text/html': output_html,
'text/csv': output_csv,
'application/json': output_json,
}
资源方法装饰器
Resource
类上有一个名为 method_decorators
的属性。您可以子类化 Resource
并添加您自己的装饰器,这些装饰器将被添加到资源中的所有方法函数中。例如,如果您想在每个请求中构建自定义身份验证。
def authenticate(func):
@wraps(func)
def wrapper(*args, **kwargs):
if not getattr(func, 'authenticated', True):
return func(*args, **kwargs)
acct = basic_authentication() # custom account lookup function
if acct:
return func(*args, **kwargs)
flask_restful.abort(401)
return wrapper
class Resource(flask_restful.Resource):
method_decorators = [authenticate] # applies to all inherited resources