http://flask-restful.readthedocs.io/en/0.3.5/quickstart.html#full-example
Flask-RESTful 提供了一个 Resource 基础类,它能够定义一个给定 URL 的一个或者多个 HTTP 方法。
add_resource 函数使用指定的 endpoint 注册路由到框架上。如果没有指定 endpoint,Flask-RESTful 会根据类名生成一个,但是有时候有些函数比如 url_for 需要 endpoint,因此我会明确给 endpoint 赋值。
class TaskListAPI(Resource):
def get(self):
pass
def post(self):
pass
class TaskAPI(Resource):
def get(self, id):
pass
def put(self, id):
pass
def delete(self, id):
pass
api.add_resource(TaskListAPI, '/todo/api/v1.0/tasks', endpoint = 'tasks')
api.add_resource(TaskAPI, '/todo/api/v1.0/tasks/<int:id>', endpoint = 'task')
Flask-RESTful 提供了一个更好的方式来处理数据验证,它叫做 RequestParser 类。这个类工作方式类似命令行解析工具 argparse。
首先,对于每一个资源需要定义参数以及怎样验证它们:
from flask.ext.restful import reqparse
class TaskListAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = str, required = True,
help = 'No task title provided', location = 'json')
self.reqparse.add_argument('description', type = str, default = "", location = 'json')
super(TaskListAPI, self).__init__()
# ...
class TaskAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = str, location = 'json')
self.reqparse.add_argument('description', type = str, location = 'json')
self.reqparse.add_argument('done', type = bool, location = 'json')
super(TaskAPI, self).__init__()
在 TaskListAPI 资源中,POST 方法是唯一接收参数的。参数“标题”是必须的,因此我定义一个缺少“标题”的错误信息。当客户端缺少这个参数的时候,Flask-RESTful 将会把这个错误信息作为响应发送给客户端。“描述”字段是可选的,当缺少这个字段的时候,默认的空字符串将会被使用。一个有趣的方面就是 RequestParser 类默认情况下在 request.values 中查找参数,因此 location 可选参数必须被设置以表明请求过来的参数是 request.json 格式的。
TaskAPI 资源的参数处理是同样的方式,但是有少许不同。PUT 方法需要解析参数,并且这个方法的所有参数都是可选的。
当请求解析器被初始化,解析和验证一个请求是很容易的。 例如,请注意 TaskAPI.put() 方法变的多么地简单:
def put(self, id): task = filter(lambda t: t['id'] == id, tasks) if len(task) == 0: abort(404) task = task[0] args = self.reqparse.parse_args() for k, v in args.iteritems(): if v != None: task[k] = v return jsonify( { 'task': make_public_task(task) } )
使用 Flask-RESTful 来处理验证的另一个好处就是没有必要单独地处理类似 HTTP 400 错误,Flask-RESTful 会来处理这些。
生成响应
原来设计的 REST 服务器使用 Flask 的 jsonify 函数来生成响应。Flask-RESTful 会自动地处理转换成 JSON 数据格式,因此下面的代码需要替换:
return jsonify( { 'task': make_public_task(task) } )
现在需要写成这样:
return { 'task': make_public_task(task) }
Flask-RESTful 也支持自定义状态码,如果有必要的话:
return { 'task': make_public_task(task) }, 201
Flask-RESTful 还有更多的功能。make_public_task 能够把来自原始服务器上的任务从内部形式包装成客户端想要的外部形式。最典型的就是把任务的 id 转成 uri。Flask-RESTful 就提供一个辅助函数能够很优雅地做到这样的转换,不仅仅能够把 id 转成 uri 并且能够转换其他的参数:
from flask.ext.restful import fields, marshal task_fields = { 'title': fields.String, 'description': fields.String, 'done': fields.Boolean, 'uri': fields.Url('task') } class TaskAPI(Resource): # ... def put(self, id): # ... return { 'task': marshal(task, task_fields) }
task_fields 结构用于作为 marshal 函数的模板。fields.Uri 是一个用于生成一个 URL 的特定的参数。 它需要的参数是 endpoint。
from flask.ext.restful import reqparse
class TaskListAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = str, required = True,
help = 'No task title provided', location = 'json')
self.reqparse.add_argument('description', type = str, default = "", location = 'json')
super(TaskListAPI, self).__init__()
# ...
class TaskAPI(Resource):
def __init__(self):
self.reqparse = reqparse.RequestParser()
self.reqparse.add_argument('title', type = str, location = 'json')
self.reqparse.add_argument('description', type = str, location = 'json')
self.reqparse.add_argument('done', type = bool, location = 'json')
super(TaskAPI, self).__init__()
# ...
官方的todo代码
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)