Flask-RESTful 是一个 Flask 扩展,它添加了快速构建 REST APIs 的支持。它当然也是一个能够跟你现有的ORM/库协同工作的轻量级的扩展。Flask-RESTful 鼓励以最小设置的最佳实践。如果你熟悉 Flask 的话,Flask-RESTful 应该很容易上手。
#-*-coding:utf-8-*-
from flask import Flask
from flask_restful import Api,Resource
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)
运行此脚本,可在pycharm控制台看到:
* Debug mode: on
* Restarting with stat
* Debugger is active!
* Debugger PIN: 303-672-181
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
在浏览器中访问 http://127.0.0.1:5000/,可保存此json文件。
Flask-RESTful 提供的最主要的基础就是资源(resources)。资源(Resources)是构建在 Flask 可拔插视图 之上,只要在你的资源(resource)上定义方法就能够容易地访问多个 HTTP 方法。
#-*-coding:utf-8-*-
from flask import Flask,request
from flask_restful import Api,Resource
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,'/')
if __name__ == "__main__":
app.run(debug=True)
启动后,通过request库测试:
#-*-coding:utf-8-*-
from requests import put,get
if __name__ == "__main__":
temp = put('http://127.0.0.1:5000/todo1',data={'data':'Remember the milk'}).json()
print(temp)
temp = get('http://127.0.0.1:5000/todo1').json()
print(temp)
资源可以通过多个 URLs 访问。你可以把多个 URLs 传给 Api 对象的 Api.add_resource()
方法。每一个 URL 都能访问到你的 Resource
api.add_resource(HelloWorld,
'/',
'/hello')
也可以为你的资源方法指定 endpoint 参数。
api.add_resource(Todo,
'/todo/', endpoint='todo_ep')
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 字典而不是一个自定义的数据结构。
如果你指定了 help 参数的值,在解析的时候当类型错误被触发的时候,它将会被作为错误信息给呈现出来。如果你没有指定 help 信息的话,默认行为是返回类型错误本身的信息。
默认下,arguments 不是 必须的。另外,在请求中提供的参数不属于 RequestParser 的一部分的话将会被忽略。
另请注意:在请求解析中声明的参数如果没有在请求本身设置的话将默认为 None
。
使用 strict=True
调用 parse_args
能够确保当请求包含你的解析器中未定义的参数的时候会抛出一个异常。
args = parser.parse_args(strict=True)
要求一个值传递的参数,只需要添加 required=True
来调用 add_argument()
。
parser.add_argument('name', type=str, required=True,
help="Name cannot be blank!")
如果接收的参数有多个值或者是个列表,可以传入 action='append'
parser.add_argument('name', type=str, action='append')
你可以这样的测试:
from requests import post
post('http://127.0.0.1:5000/todos',data={"task":"do something","name":["bob","sue","joe"]})
得到的参数:
args = parser.parse_args()
args['name'] # ['bob', 'sue', 'joe']
如果由于某种原因,你想要以不同的名称存储你的参数一旦它被解析的时候,你可以使用 dest
kwarg。
parser.add_argument('name', type=str, dest='public_name')
args = parser.parse_args()
args['public_name']
默认下,RequestParser
试着从 flask.Request.values
,以及 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', type=str, location='headers')
# From http cookies
parser.add_argument('session_id', type=str, location='cookies')
# From file uploads
parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files')
多个位置
通过传入一个列表到 location
中可以指定 多个 参数位置:
parser.add_argument('text', location=['headers', 'values'])
列表中最后一个优先出现在结果集中。(例如:location=[‘headers’, ‘values’],解析后 ‘values’ 的结果会在 ‘headers’ 前面)
往往你会为你编写的每个资源编写不同的解析器。这样做的问题就是如果解析器具有共同的参数。不是重写,你可以编写一个包含所有共享参数的父解析器接着使用 copy()
扩充它。你也可以使用 replace_argument()
覆盖父级的任何参数,或者使用 remove_argument()
完全删除参数。 例如:
parser = RequestParser()
parser.add_argument('foo', type=int)
parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)
# parser_copy has both 'foo' and 'bar'
parser_copy.replace_argument('foo', type=str, required=True, location='json')
# 'foo' is now a required str located in json, not an int as defined
# by original parser
parser_copy.remove_argument('foo')
# parser_copy no longer has 'foo' argument
Flask-RESTful 提供了一个简单的方式来控制在你的响应中实际呈现什么数据,因此您不必担心暴露内部数据结构。
基本用法:
你可以定义一个字典或者 fields
的 OrderedDict 类型,OrderedDict 类型是指键名是要呈现的对象的属性或键的名称,键值是一个类,该类格式化并返回该字段的值。
from flask_restful import 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
#-*-coding:utf-8-*-
from flask import Flask,request
from flask_restful import Api,Resource,reqparse,abort
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',type=str)
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)
tempDel = TODOS[todo_id]
del TODOS[todo_id]
return tempDel
def put(self,todo_id):
args = parser.parse_args()
task = {'task':args['task']}
TODOS[todo_id] = task
return task,201
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
api.add_resource(TodoList,'/todos')
api.add_resource(Todo,'/todos/')
if __name__ == "__main__":
app.run(debug=True)
测试:
#-*-coding:utf-8-*-
from requests import put,get,post,delete
import json
if __name__ == "__main__":
temp = get('http://127.0.0.1:5000/todos')
print(json.loads(temp.content))
print(json.loads(get('http://127.0.0.1:5000/todos/todo3').content))
print(json.loads(delete('http://127.0.0.1:5000/todos/todo1').content))
print(json.loads(post('http://127.0.0.1:5000/todos',data={"task":"do something"}).content))