使用 pip 安装 Flask-RESTful:
pip install flask-restful
开发的版本可以从 GitHub 上的页面 下载
git clone https://github.com/twilio/flask-restful.git
cd flask-restful
python setup.py develop
一个最小的 API
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class HellowWOrld(Resource):
def get(self):
return {"hello": "world"}
api.add_resource(HellowWOrld, '/')
if __name__ == "__main__":
app.run(debug=True)
资源丰富的路由
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, '/<string:todo_id>')
if __name__ == "__main__":
app.run()
通过requests
库发送请求
设置响应状态码以及响应头
from flask import Flask
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class Todo1(Resource):
def get(self):
# 默认响应状态码为200
return {"task": 'Hello World'}
class Todo2(Resource):
def get(self):
# 修改状态码为201
return {"task": "Hello World"}, 201
class Todo3(Resource):
def get(self):
# 添加响应头信息
return {"task": "Hello World"}, 201, {"Python": "Flask", "python": "Tornado"}
api.add_resource(Todo1, '/todo1')
api.add_resource(Todo2, '/todo2')
api.add_resource(Todo3, '/todo3')
if __name__ == "__main__":
app.run()
请求参数解析
- 验证请求参数,类似于flask中的
WTF
扩展表单验证
from flask_restful import reqparse
parser = reqparse.RequestParser()
parser.add_argument('rate', type=int, help='Rate cannot be converted')
parser.add_argument('name', type=str)
args = parser.parse_args()
# coding:utf-8
from flask import Flask, request
from flask_restful import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
# 创建请求参数解析对象
parser = reqparse.RequestParser()
# 添加验证字段,以及验证信息
parser.add_argument('uname', type=str, help='is must str')
parser.add_argument('password', type=str, required=True, help='is must exists')
parser.add_argument('age', type=int, help='is must int')
class Register(Resource):
def get(self):
return "welcome come to register page"
def post(self):
data = parser.parse_args(strict=True)
return data
api.add_resource(Register, '/register')
if __name__ == "__main__":
app.run(debug=True)
限制该字段不能为空
- 只需要添加
required=True
parser.add_argument('name', type=str, required=True, help="Name cannot be blank!")
接受该字段可以有多个值&列表
action='append'
parser.add_argument('name', type=str, 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']
设置请求参数来源(url、表单、请求头、cookie…)
# 表单数据
parser.add_argument('name', type=int, location='form')
# url数据
parser.add_argument('PageSize', type=int, location='args')
# 请求头数据
parser.add_argument('User-Agent', type=str, location='headers')
# cookies数据
parser.add_argument('session_id', type=str, location='cookies')
# 多媒体文件
parser.add_argument('picture', type=werkzeug.datastructures.FileStorage, location='files')
多个位置参数
- 列表中最后一个优先出现在结果集中。(例如:location=[‘headers’, ‘values’],解析后 ‘values’ 的结果会在 ‘headers’ 前面)
parser.add_argument('text', location=['headers', 'values'])
继承解析
- 往往你会为你编写的每个资源编写不同的解析器。这样做的问题就是如果解析器具有共同的参数。不是重写,你可以编写一个包含所有共享参数的父解析器接着使用
copy()
扩充它。你也可以使用replace_argument()
覆盖父级的任何参数,或者使用remove_argument()
完全删除参数。 例如:
from flask.ext.restful import RequestParser
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
输出字段
(1)基本用法
- 你可以定义一个字典或者 fields 的 OrderedDict 类型,OrderedDict 类型是指
键名是
要呈现的对象的属性
或键的名称,键值是一个类
,该类格式化和返回的该字段的值。下面例子有三个字段,两个是字符串(Strings)以及一个是日期时间(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
- 这个例子假设你有一个自定义的
数据库对象
(todo),它具有属性:name
,address
, 以及date_updated
。该对象上任何其它的属性可以被认为是私有的不会在输出中呈现出来。一个可选的 envelope 关键字参数被指定为封装结果输出。 - 装饰器 marshal_with 是真正接受你的数据对象并且过滤字段。marshal_with 能够在单个对象,字典,或者列表对象上工作。
- 注意:
marshal_with
是一个很便捷的装饰器,在功能上等效于如下的 return marshal(db_get_todo(), resource_fields), 200。这个明确的表达式能用于返回 200 以及其它的 HTTP 状态码作为成功响应。
(2)示例演示
# coding:utf-8
from flask import Flask, request
from flask_restful import Api, Resource, reqparse, fields, marshal_with
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
app = Flask(__name__)
api = Api(app)
class Config(object):
"""配置参数"""
SQLALCHEMY_DATABASE_URI = "mysql://root:123456@127.0.0.1:3306/db_flask"
# 设置sqlalchemy自动跟踪数据库
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KRY = '#%*(_)?./DFVDjnd34534'
app.config.from_object(Config)
# 创建数据库sqlalchemy工具对象
db = SQLAlchemy(app)
# 创建flask脚本管理工具对象
manager = Manager(app)
# 创建数据库迁移工具对象
Migrate(app, db)
# 向manager对象中添加数据库操作命令
manager.add_command('db', MigrateCommand)
# 创建请求参数解析对象
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, help='is must str')
parser.add_argument('password', type=str, required=True, help='is must exists')
parser.add_argument('id', type=int, help='is must int')
# 对象输出属性
resource_fields = {
'id': fields.String,
'name': fields.String,
'email': fields.String
}
# 创建数据库模型类
class Role(db.Model):
"""用户角色表"""
__tablename__ = 'tbl_roles'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(32), unique=True)
# 需要手动添加,方便使用Role.user查询用户对象,user列不是真实存在的,backref="role"为了方便通过User.role获取角色对象,
# 因为使用User.role_id只能获取到角色id,要想获取角色对象,还需要再在Role表中查询一次
users = db.relationship("User", backref="role")
def __repr__(self):
"""定义之后,可以让显示对象的时候更直观,类似于Django中的__str__"""
return "Rloe object: name=%s" % self.name
class User(db.Model):
"""用户表"""
__tablename__ = 'tbl_users' # 指明数据库表名
id = db.Column(db.Integer, primary_key=True) # 整型主键,会默认设置为自增主键
name = db.Column(db.String(64), unique=True)
email = db.Column(db.String(128), unique=True)
password = db.Column(db.String(128), nullable=False) # nullable=False 参数必须传
role_id = db.Column(db.Integer, db.ForeignKey("tbl_roles.id"))
class Users(Resource):
@marshal_with(resource_fields, envelope='users')
def post(self):
args = parser.parse_args()
id = args.get('id')
name = args.get('name')
password = args.get('password')
user = User.query.filter_by(name=name, password=password).all()
return user
api.add_resource(Users, '/users')
if __name__ == "__main__":
app.run(debug=True)
- 运行项目
- 发起post请求
- 我们将返回结果格式化在解释
- 这是我们定义的输出内容
- 这是数据库对象属性
- 视图返回
如果不定义输出字段,则只会返回该用户对象,定义了输出字段resource_fields
后,使用@marshal_with(resource_fields, envelope='users')
,则会返回resource_fields
内定义了的字段属性,envelope
:即你想要用户看到的对象,我们上述示例中因为获取的是user
对象信息,所以我定义为users
,在返回结果中看到的users
即我们此处定义的。
(3)重命名属性
- 很多时候你面向公众的字段名称是不同于内部的属性名。使用
attribute
可以配置这种映射。
fields = {
'name': fields.String(attribute='private_name'),
'address': fields.String,
}
lambda
也能在 attribute 中使用
fields = {
'name': fields.String(attribute=lambda x: x._private_name),
'address': fields.String,
}
(4)默认值
- 如果由于某种原因你的数据对象中并
没有
你定义的字段列表中的属性
,你可以指定一个
默认值而不是返回 None。
fields = {
'name': fields.String(default='Anonymous User'),
'address': fields.String,
}
(5)自定义字段&多个值
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'),
}
(6)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:访问视图时的路由
'uri': fields.Url('todo_resource'),
'random': RandomNumber,
}
- 示例
- 默认情况下,fields.Url 返回一个
相对的 uri
。为了生成包含协议(scheme),主机名以及端口的绝对 uri
,需要在字段声明的时候传入absolute=True
。传入scheme
关键字参数可以覆盖默认的协议(scheme):
fields = {
'uri': fields.Url('todo_resource', absolute=True)
'https_uri': fields.Url('todo_resource', absolute=True, scheme='https')
}
- 示例
(7)复杂结构
- 你可以有一个
扁平的结构
,marshal_with 将会把它转变为一个嵌套结构
>>> from flask_restful import fields, marshal
>>> import json
>>>
>>> resource_fields = {'name': fields.String}
>>> resource_fields['address'] = {}
>>> resource_fields['address']['line 1'] = fields.String(attribute='addr1')
>>> resource_fields['address']['line 2'] = fields.String(attribute='addr2')
>>> resource_fields['address']['city'] = fields.String
>>> resource_fields['address']['state'] = fields.String
>>> resource_fields['address']['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"}}'
- 注意:
address
字段并不真正地存在于数据对象中,但是任何一个子字段(sub-fields)可以直接地访问对象的属性,就像没有嵌套一样。
(8)列表字段
- 你也可以把字段解组(unmarshal)成列表
>>> from flask_restful 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"}'
(10)高级:嵌套字段
- 尽管使用字典套入字段能够使得一个扁平的数据对象变成一个嵌套的响应,你可以使用
Nested
解组(unmarshal)嵌套数据结构并且合适地呈现它们。
>>> from flask_restful 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_with(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"}}'
扩展 Flask-RESTful
默认的API仅支持JSON格式内容,我们可以通过扩展,使其可以支持更多类型数据@api.representation
- 下面表示函数必须返回一个 Flask Response 对象。
app = Flask(__name__)
api = restful.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
@api.representation('application/xml')
def xml(data, code, headers):
resp = make_response(convert_data_to_xml(data), code)
resp.headers.extend(headers)
return resp
(1)自定义字段 & 输入
- 自定义输出字段让你无需直接修改内部对象执行自己的输出格式。所有你必须做的就是继承
Raw
并且实现format()
方法: - 下面示例:将用户定义的字段
name
改为大写NAME
输出
class AllCapsString(fields.Raw):
def format(self, value):
return value.upper()
# example usage
fields = {
'name': fields.String,
'all_caps_name': AllCapsString(attribute=name),
}
(2)输入
- 对于解析参数,你可能要执行自定义验证。创建你自己的输入类型让你轻松地扩展请求解析。
- 请求参数解析
RequestParser
中使用
parser = reqparse.RequestParser()
parser.add_argument('OddNumber', type=odd_number)
parser.add_argument('Status', type=task_status)
args = parser.parse_args()
- 对于解析参数,你可能要执行自定义验证。创建你自己的输入类型让你轻松地扩展请求解析。
def odd_number(value):
if value % 2 == 0:
raise ValueError("Value is not odd")
return value
- 请求解析器在你想要在错误消息中引用名称的情况下将也会允许你
访问参数的名称(OddNumber)
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
- 你还可以将公开的参数转换为内部表示(根据用户请求
value
返回列表中对应数据)
# 'init' => 0
# 'in-progress' => 1
# 'completed' => 2
def task_status(value):
statuses = [u"init", u"in-progress", u"completed"]
return statuses.index(value)
(3)响应格式
- 为了支持其它的表示(像 XML,CSV,HTML),你可以使用
representation()
装饰器。你需要在你的 API 中引用它。
api = restful.Api(app)
@api.representation('text/csv')
def output_csv(data, code, headers=None):
pass
# implement csv output!
- 这些输出函数有三个参数,data,code,以及 headers。
data
是你从你的资源方法返回的对象code
是预计的 HTTP 状态码headers
是设置在响应中任意的 HTTP 头。你的输出函数应该返回一个 Flask 响应对象。
def output_json(data, code, headers=None):
"""Makes a Flask response with a JSON encoded body"""
resp = make_response(json.dumps(data), code)
resp.headers.extend(headers or {})
return resp
另外一种实现这一点的就是继承 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,
}
(4)自定义错误处理器
app = Flask(__name__)
api = flask_restful.Api(app, catch_all_404s=True)
- Flask-RESTful 会处理除了自己路由上的错误还有应用程序上所有的 404 错误。
- 有时候你想在发生错误的时候做一些特别的东西 - 记录到文件,发送邮件,等等。使用
got_request_exception()
方法把自定义错误处理加入到异常。
def log_exception(sender, exception, **extra):
""" Log an exception to our logging framework """
sender.logger.debug('Got exception during processing: %s', exception)
from flask import got_request_exception
got_request_exception.connect(log_exception, app)
(5)定义自定义错误消息
- 在一个请求期间遇到某些错误的时候,你可能想返回一个特定的消息以及/或者状态码。你可以告诉 Flask-RESTful 你要如何处理每一个错误/异常,因此你不必在你的 API 代码中编写 try/except 代码块。
errors = {
'UserAlreadyExistsError': {
'message': "A user with that username already exists.",
'status': 409,
},
'ResourceDoesNotExist': {
'message': "A resource with that ID no longer exists.",
'status': 410,
'extra': "Any extra information you want.",
},
}
- 包含 ‘
status
’ 键可以设置响应的状态码。如果没有指定的话,默认是 500
,一旦你的 errors 字典定义,简单地把它传给 Api 构造函数
app = Flask(__name__)
api = flask_restful.Api(app, errors=errors)