Flask-RESTful 快速构建TODO应用

1,199 阅读6分钟
原文链接: mp.weixin.qq.com

大纲

  1. 简介

  2. 安装

  3. 快速入门

    • 一个最小的 api 例子

    • 资源丰富的路由

    • 端点

    • 参数解析

    • 数据格式化

  4. 完整 TODO 应用例子

简介

Flask-RESTful是一个Flask的扩展,它增加了对快速构建REST APIs的支持。它是一种轻量级的抽象,可以与现有的ORM/库一起工作。Flask-RESTful励以最少的安装方式进行最佳实践。如果你对Flask很熟悉的,Flask-RESTful会很容易上手。

安装

本文环境:python3

  1. pip3 install flask-restful

快速入门

一个最小的API

下面来编写一个最小的Flask-RESTful API:

  1. from flask import Flask

  2. from flask_restful import Resource, Api

  3. app = Flask(__name__)

  4. api = Api(app)

  5. class HelloWorld(Resource):

  6.    def get(self):

  7.        return {'hello': 'world'}

  8. api.add_resource(HelloWorld, '/')

  9. if __name__ == '__main__':

  10.    app.run(debug=True)

保存代码到api.py测试时打开debug模式会提供代码重载,以及更详细的错误信息。注意调试模式不可用在生产环境。接下来打开命令窗口输入命令执行py 文件

  1. $ python api.py

  2. * Running on http://127.0.0.1:5000/

  3. * Restarting with reloader

新建一个命令窗口,使用curl测试下API

  1. $ curl http://127.0.0.1:5000/

  2. {"hello": "world"}

资源丰富的路由

Flask-RESTful 提供的最主要的基础就是资源,资源是构建在Flask 可插拔的视图之上,只要在你的资源上定义方法就能很容易的访问多个 HTTP 方法,一个待办事项应用的基础 CRUD资源的编写像这样:

  1. from flask import Flask, request

  2. from flask_restful import Resource, Api

  3. app = Flask(__name__)

  4. api = Api(app)

  5. todos = {}

  6. class TodoSimple(Resource):

  7.    def get(self, todo_id):

  8.        # 从 todos 字典中读取数据 注:此处没有对不存在的 key 做处理

  9.        return {todo_id: todos[todo_id]}

  10.    def put(self, todo_id):

  11.        # 将数据保存到 todos 字典中

  12.        todos[todo_id] = request.form['data']

  13.        return {todo_id: todos[todo_id]}

  14. # 增加资源到 api, 匹配字符串到资源方法的变量

  15. api.add_resource(TodoSimple, '/<string:todo_id>')

  16. if __name__ == '__main__':

  17.    app.run(debug=True)

保存到文件后执行,使用 curl测试一下

  1. $ curl http://localhost:5000/todo1 -d "data=Remember the milk" -X PUT

  2. {"todo1": "Remember the milk"}

  3. $ curl http://localhost:5000/todo1

  4. {"todo1": "Remember the milk"}

  5. $ curl http://localhost:5000/todo2 -d "data=Change my brakepads" -X PUT

  6. {"todo2": "Change my brakepads"}

  7. $ curl http://localhost:5000/todo2

  8. {"todo2": "Change my brakepads"}

如果有安装 python 的 requests 库也可以用以下方法测试 (Python库系列之requests):

  1. >>> from requests import put, get

  2. >>> put('http://localhost:5000/todo1', data={'data': 'Remember the milk'}).json()

  3. {'todo1': 'Remember the milk'}

  4. >>> get('http://localhost:5000/todo1').json()

  5. {u'todo1': u'Remember the milk'}

  6. >>> put('http://localhost:5000/todo2', data={'data': 'Change my brakepads'}).json()

  7. {u'todo2': u'Change my brakepads'}

  8. >>> get('http://localhost:5000/todo2').json()

  9. {u'todo2': u'Change my brakepads'}

Flask-RESTful支持视图方法多种类型的返回值,像 Flask 一样,你可以返回任何迭代器,它会被转化成一个包含原始响应对象的响应,Flask-RESTful还支持使用多个返回时来设置响应码以及响应头,如下:

  1. class Todo1(Resource):

  2.    def get(self):

  3.        # 默认返回200

  4.        return {'task': 'Hello world'}

  5. class Todo2(Resource):

  6.    def get(self):

  7.        # 将响应码设为201

  8.        return {'task': 'Hello world'}, 201

  9. class Todo3(Resource):

  10.    def get(self):

  11.        # 将响应码设置为201,并返回自定义头

  12.        return {'task': 'Hello world'}, 201, {'Etag': 'some-opaque-string'}

  13. api.add_resource(Todo1, '/t1')

  14. api.add_resource(Todo2, '/t2')

  15. api.add_resource(Todo3, '/t3')

保存到文件后执行,使用 curl 测试一下

  1. $curl http://127.0.0.1:5000/t1 -I

  2. HTTP/1.0 200 OK

  3. Content-Type: application/json

  4. Content-Length: 30

  5. Server: Werkzeug/0.12.2 Python/3.6.4

  6. Date: Wed, 03 Jan 2018 15:07:07 GMT

  7. $curl http://127.0.0.1:5000/t2 -I

  8. HTTP/1.0 201 CREATED

  9. Content-Type: application/json

  10. Content-Length: 30

  11. Server: Werkzeug/0.12.2 Python/3.6.4

  12. Date: Wed, 03 Jan 2018 15:07:10 GMT

  13. $curl http://127.0.0.1:5000/t3 -I

  14. HTTP/1.0 201 CREATED

  15. Content-Type: application/json

  16. Content-Length: 30

  17. Etag: some-opaque-string

  18. Server: Werkzeug/0.12.2 Python/3.6.4

  19. Date: Wed, 03 Jan 2018 15:05:58 GMT

端点

很多时候在一个 API 中,你的资源可以通过多个URLs访问。你可以把多个 URLs 传给 Api 对象的 add_resource() 方法。每一个 URL 都能访问到你的资源

  1. api.add_resource(HelloWorld,

  2.    '/',

  3.    '/hello')

你还可以将路径的部分匹配为资源方法的变量

  1. api.add_resource(Todo,

  2.    '/todo/<int:todo_id>', endpoint='todo_ep')

注:

如果一个请求与你的应用程序端点中的任何一个都不匹配,Flask-RESTful 将会返回404错误,并附带一段有关其它最相似匹配的端点建议。你可以通过在配置中将ERROR404HELP设置为 False禁用此项。

参数解析

尽管 Flask 提供了便捷的方式获取请求的数据(例:查询字符串或POST 表单编码的数据),验证表单依旧很痛苦。Flask-RESTful 内置了支持验证请求数据,它使用了一个类似argparse 的库。

  1. from flask_restful import reqparse

  2. parser = reqparse.RequestParser()

  3. parser.add_argument('rate', type=int, help='Rate to charge for this resource')

  4. args = parser.parse_args()

注:与 argparse 模块不同的是,reqparse.RequestParser.parse_args() 返回了 Python 字典而不是一个自定义的数据结构。

使用 reqparse 模块同样可以自由地提供全面的错误信息。如果一个参数没有通过校验,Flask-RESTful 将会以一个400的错误请求以及高亮的错误信息回应。

  1. $ curl -d 'rate=foo' http://127.0.0.1:5000/todos

  2. {'status': 400, 'message': 'foo cannot be converted to int'}

inputs模块提供许多常用的转换函数,像 inputs.date() 和 inputs.url()。

调用 parse_args 传入 strict=True 能够确保当请求包含了你的解析器中未定义的参数时抛出一个异常。

  1. args = parser.parse_args(strict=True)

数据格式化

默认情况下,在你的迭代返回中所有的字段都将会原样呈现。当你处理 Python 数据结构的时候会觉得它很棒,但在处理对象时会变得非常令人沮丧。为了解决这个问题,Flask-RESTful 提供了fields 模块以及 marshal_with()装饰器。类似 Django ORM 和 WTForm ,你可以使用 fields 模块来描述响应的数据结构。

  1. from flask_restful import fields, marshal_with

  2. resource_fields = {

  3.    'task':   fields.String,

  4.    'uri':    fields.Url('todo_ep')

  5. }

  6. class TodoDao(object):

  7.    def __init__(self, todo_id, task):

  8.        self.todo_id = todo_id

  9.        self.task = task

  10.        # This field will not be sent in the response

  11.        self.status = 'active'

  12. class Todo(Resource):

  13.    @marshal_with(resource_fields)

  14.    def get(self, **kwargs):

  15.        return TodoDao(todo_id='my_todo', task='Remember the milk')

`

上面的例子接收了一个 python对象并准备将其序列化。marshalwith()装饰器会通过resourcefields()进行转换。从对象中提取的唯一字段是 task。fields.Url是一个特殊的字段,它接受端点名称并为响应中的端点生成一个URL。您需要的许多字段类型已经包含在其中。可以查看 fields 项查看完整列表。

完整 TODO 应用例子

  1. from flask import Flask

  2. from flask_restful import reqparse, abort, Api, Resource

  3. app = Flask(__name__)

  4. api = Api(app)

  5. TODOS = {

  6.    'todo1': {'task': 'build an API'},

  7.    'todo2': {'task': '?????'},

  8.    'todo3': {'task': 'profit!'},

  9. }

  10. def abort_if_todo_doesnt_exist(todo_id):

  11.    if todo_id not in TODOS:

  12.        abort(404, message="Todo {} doesn't exist".format(todo_id))

  13. parser = reqparse.RequestParser()

  14. parser.add_argument('task')

  15. # Todo

  16. # 显示单个待办任务,并允许删除待办任务项

  17. class Todo(Resource):

  18.    def get(self, todo_id):

  19.        abort_if_todo_doesnt_exist(todo_id)

  20.        return TODOS[todo_id]

  21.    def delete(self, todo_id):

  22.        abort_if_todo_doesnt_exist(todo_id)

  23.        del TODOS[todo_id]

  24.        return '', 204

  25.    def put(self, todo_id):

  26.        args = parser.parse_args()

  27.        task = {'task': args['task']}

  28.        TODOS[todo_id] = task

  29.        return task, 201

  30. # TodoList

  31. # 展示所有 todos 的列表,允许以POST的方式新建一个 tasks

  32. class TodoList(Resource):

  33.    def get(self):

  34.        return TODOS

  35.    def post(self):

  36.        args = parser.parse_args()

  37.        todo_id = int(max(TODOS.keys()).lstrip('todo')) + 1

  38.        todo_id = 'todo%i' % todo_id

  39.        TODOS[todo_id] = {'task': args['task']}

  40.        return TODOS[todo_id], 201

  41. # 设置 api 资源路由

  42. api.add_resource(TodoList, '/todos')

  43. api.add_resource(Todo, '/todos/<todo_id>')

  44. if __name__ == '__main__':

  45.    app.run(debug=True)

例子使用:

  1. $ python api.py

  2. * Running on http://127.0.0.1:5000/

  3. * Restarting with reloader

获取 Todo 列表

  1. $ curl http://localhost:5000/todos

  2. {"todo1": {"task": "build an API"}, "todo3": {"task": "profit!"}, "todo2": {"task": "?????"}}

获取单个 Todo 任务

  1. $ curl http://localhost:5000/todos/todo3

  2. {"task": "profit!"}

删除一个任务

  1. $ curl http://localhost:5000/todos/todo2 -X DELETE -v

  2. > DELETE /todos/todo2 HTTP/1.1

  3. > Host: localhost:5000

  4. > User-Agent: curl/7.52.1

  5. > Accept: */*

  6. >

  7. * HTTP 1.0, assume close after body

  8. < HTTP/1.0 204 NO CONTENT

  9. < Content-Type: application/json

  10. < Content-Length: 0

  11. < Server: Werkzeug/0.12.2 Python/3.6.4

  12. < Date: Wed, 03 Jan 2018 16:07:19 GMT

添加一个新的任务

  1. $ curl http://localhost:5000/todos -d "task=something new" -X POST -v

  2. > POST /todos HTTP/1.1

  3. > Host: localhost:5000

  4. > User-Agent: curl/7.52.1

  5. > Accept: */*

  6. > Content-Length: 18

  7. > Content-Type: application/x-www-form-urlencoded

  8. >

  9. * upload completely sent off: 18 out of 18 bytes

  10. * HTTP 1.0, assume close after body

  11. < HTTP/1.0 201 CREATED

  12. < Content-Type: application/json

  13. < Content-Length: 32

  14. < Server: Werkzeug/0.12.2 Python/3.6.4

  15. < Date: Wed, 03 Jan 2018 16:05:27 GMT

  16. <

  17. {

  18.    "task": "something new"

  19. }

  20. * Curl_http_done: called premature == 0

  21. * Closing connection 0

更新任务

  1. $ curl http://localhost:5000/todos/todo3 -d "task=something different" -X PUT -v

  2. > PUT /todos/todo3 HTTP/1.1

  3. > Host: localhost:5000

  4. > User-Agent: curl/7.52.1

  5. > Accept: */*

  6. > Content-Length: 24

  7. > Content-Type: application/x-www-form-urlencoded

  8. >

  9. * upload completely sent off: 24 out of 24 bytes

  10. * HTTP 1.0, assume close after body

  11. < HTTP/1.0 201 CREATED

  12. < Content-Type: application/json

  13. < Content-Length: 38

  14. < Server: Werkzeug/0.12.2 Python/3.6.4

  15. < Date: Wed, 03 Jan 2018 16:09:05 GMT

  16. <

  17. {

  18.    "task": "something different"

  19. }

  20. * Curl_http_done: called premature == 0

  21. * Closing connection 0

本文关键词

  • Python

  • Flask-RESTful

  • curl

  • requests

更多阅读

  • Flask-RESTful — Flask-RESTful 0.3.6 documentation

    

更多姿势,长按识别二维码,关注我