1 Flask restful
1.1 简介
flask-restful 是flask扩展包 提供快速工具化构建restful风格api的支持 兼容已有的orm框架
1. 视图类方法映射请求方式(继承Resource视图类 可对多个url进行匹配)
2. 自带参数解析器(数据类型、数据值、其他扩展选项(必选、提示信息、参数类型{query_string|form|json|header}等))
3. 响应数据自定义扩展输出(直接和实体类pv关联、可多重嵌套输出)
1.2 安装
1. 在线
pip install flask-restful
2.离线
git clone https://github.com/twilio/flask-restful.git
cd flask-restful
python setup.py develop
1.3 实例
# -*- coding:utf-8 -*-
from flask import Flask
from flask_restful import Api, Resource
# 视图类
class Test(Resource):
def get(self):
return {'data': "data"}
if __name__ == '__main__':
# flask实例
app = Flask(__name__)
# 通过api封装flask
api = Api(app)
# 添加路由映射并关联到视图类
api.add_resource(Test, '/')
# 运行服务
app.run(debug=True)
'''
curl http://localhost:5000/
>>>{"data": "data"}
'''
# flask-restful-api实例
from flask_cors import CORS
from flask_restful import Api, Resource, reqparse, fields, marshal_with
from flask import Flask
# flask实例
app = Flask(__name__)
# 跨域问题
CORS(app)
# 通过api封装flask 提供restful风格
api = Api(app)
# 测试数据
todos = {
'todo1': {'task': 1},
'todo2': {'task': 2},
'todo3': {'task': 3},
}
# 请求参数解析
parser = reqparse.RequestParser()
parser.add_argument('task', type=int, help='task-pargrams must be int')
# 请求处理类
class TodoList(Resource):
def get(self):
return todos, 200, {'Etag': 'some-opaque-string'}
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']}
print(todos[todo_id])
return todos[todo_id], 201
# 添加路由
api.add_resource(TodoList, '/todos', '/tasks')
# 响应域问题
# 定义orm数据模型
class ToDos(object):
def __init__(self, todo_id, task):
self.todo_id = todo_id
self.task = task
self.status = 'active'
# marshal蒙版
resource_fields = {
'task': fields.String,
'url': fields.Url('todo_ep')
}
# 请求处理类
class Todo(Resource):
@marshal_with(resource_fields)
def get(self, todo_id):
return ToDos(todo_id=todo_id, task='task'+ str(todo_id)), 200
# 路由配置
api.add_resource(Todo, '/todos/<todo_id>', endpoint='todo_ep')
if __name__ == '__main__':
app.run(debug=True)
2 输入(参数解析)
2.1 参数解析器
from flask_restful import reqparse
# 1. 一般参数解析器
parser = reqparse.RequestParser()
parser.add_argument('name', type=str, required=True, help='name is not empty', dest='uName', location='')
parser.add_argument('score', type=int, help='score is type:int', action='append')
# 2. 复用参数解析器
parser_copy = parser.copy()
PS:
1.实际获取通过校验解析的参数
args = parser.parse_args()
2. 自带参数校验异常响应处理
3. type可以指定特定函数 扩展入参类型(基本数据结构+复杂数据结构)
def num_select(v):
if v % 2 == 0:
return v
else:
raise ValueError(f"value({v}) if invalid")
parser.add_argument("val", type=num_select)
2.2 实例
# -*- coding:utf-8 -*-
from flask import Flask, request
from flask_restful import reqparse, Resource, Api
from werkzeug.datastructures import FileStorage
# 1.请求参数解析器对象(可每个视图类一个或者复用)
parser = reqparse.RequestParser(trim=True, bundle_errors=True)
'''
-trim: 入参是否去除空格
-bundle_errors: 捆绑输出参数名和参数异常信息
PS: 可全局配置bundle_errors 设置且覆盖所有参数解析器的配置
Flask("myFlask").config['BUNDLE_ERRORS']=True
'''
# 2.添加参数并进行解析
parser.add_argument('name', type=str, required=True, help='name is not empty', dest='uName', location='args') # 必选参数
parser.add_argument('score', type=int, help='score is type:int', action='append') # 多值参数
parser.add_argument('User-Agent', type=str, location='headers') # 请求头信息
parser.add_argument('myCookie', type=str, location='cookies') # 请求cookies信息
parser.add_argument('jsonStr', type=str, location='json') # json
parser.add_argument('formVal', type=str, location='form') # form
parser.add_argument('portrait', type=FileStorage, location='files') # 文件上传
'''
add_argument 参数解析
-name: 参数名
-default: 参数默认值
-type: 数据类型
-required: 是否必选
-help: 提示信息
-dest: 入参别名(parse_args()实际存储的参数名)
-ignore: 是否忽略参数类型转换失败(默认为False)
-choices: 参数枚举值(且参数值只能是这些值)
-location: 入参来源(位置)
-form 表单(content-type的值是application/x-www-form-urlencoded 或者 multipart/form-data)
-json json字符串(content-type的值是application/json)
-headers 请求头
-files 文本
-cookies 请求cookies
-args 查询字符串
-values 等价于[form, args]
-action: 参数值操作形式
append 参数值追加
store: 存储值(默认形式)
-case_sensitive: 参数值是否区分大小写(默认True)
-trim: 是否剔除参数名空格(默认False)
-nullable: 是否允许参数值为null(默认为True)
-store_missing: 当获取不到指定参数是否存储默认值(默认为True)
PS: location 可设置多个且优先级最高的是最后一个值(location=[form, json] json优先级高)
'''
# 3.解析器继承(复用参数解析器)-在源基础上添加新参数验证
parser_copy = parser.copy() # 本质为深拷贝
parser_copy.add_argument('bar', type=int)
parser_copy.replace_argument('bar', type=str, required=True) # 替换特定参数的解析规则
parser_copy.remove_argument('bar') # 移除特定参数的解析
# 4.测试资源视图类
class ReqParseTest(Resource):
def get(self):
"""get方法"""
# 获取参数
args = parser.parse_args(req=None, strict=False, http_error_code=400)
'''
req: 可根据flask请求上下文覆盖req(默认None)
strict: 实际入参不在参数解析器内部抛出400异常(默认False)
http_error_code: 默认400
'''
print(f"GET-{args}")
print(f"ReqHeaders:\n{request.headers}\n")
return {"data": args, "method": "GET"}, 200
def post(self):
"""post方法"""
args = parser.parse_args()
print(f"POST-{args}")
'''
POST-{'uName': 'fff', 'score': [2, 3], 'User-Agent': 'myHeader', 'myCookie': 'myCookies', 'jsonStr': None, 'formVal': 'formVal', 'portrait': <FileStorage: "'1.jpg'" ('image/jpeg')>}
'''
print(f"ReqHeaders:\n{request.headers}\n")
portrait = args.get("portrait") # 上传图片
if portrait and isinstance(portrait, FileStorage):
args["portrait"] = f"fileSize={len(args.get('portrait').read()) / (1024*1024)}MB"
return {"data": args, "method": "POST"}, 200
if __name__ == '__main__':
app = Flask(__name__)
api = Api(app)
api.add_resource(ReqParseTest, '/')
app.run(debug=True)
# 1. GET请求
curl "http://localhost:5000/?name=fff&score=2&score=3&formVal=formVal&jsonStr=jsonStr&User-Agent:xxx&myCookie:yyy" -H "User-Agent:myHeader" --cookie "myCookie=myCookies"
# 2. POST请求-表单
curl "http://localhost:5000/?name=fff&score=2&score=3" -H "User-Agent:myHeader" --cookie "myCookie=myCookies" -F "portrait=@F:/test/data/1.jpg;filename='1.jpg'" -F "formVal=formVal" -X POST
# 3.POST请求-JSON
curl "http://localhost:5000/?name=fff&score=2&score=3" -H "User-Agent:myHeader" --cookie "myCookie=myCookies" -H "Content-Type:application/json;charset=utf-8;" -d "{\"json_1\": \"json_1\", \"jsonStr\": \"json_2\"}" -X POST


3 输出(响应域)
3.1 响应域
flask-restful 使用fileds模块 对ORM模型类和基本实体类进行映射关联后 达到实际的数据自定义展示渲染输出能够自主控制的目的
# 简单示例
# -*- coding:utf-8 -*-
import datetime
from flask import Flask
from flask_restful import Resource, fields, marshal_with, marshal, Api
# 实体类
class StuInfo:
def __init__(self, name, class_no, age, admission_time):
self.other_name = name
self.class_no = class_no
self.age = age
self.admission_time = admission_time
# 响应域(实际输出的数据结构)
resource_fields = {
'name': fields.String(attribute='other_name'), # 别名 attribute指定实际属性名
'cno': fields.String(attribute=lambda x: x.class_no), # lambda表达式__call__对象属性
'age': fields.Integer(default=12), # default 默认值指定
'admission_time': fields.DateTime(dt_format='iso8601'),
}
# 实体
StuInfo1 = StuInfo("aa", "c1", None, datetime.datetime.now())
# 方式一 marshal_with装饰器
class Test1(Resource):
@marshal_with(resource_fields, envelope="result")
def get(self):
return StuInfo1
'''
{
"result": {
"name": "aa",
"cno": "c1",
"age": 12,
"admission_time": "2023-04-03T10:24:55.526729"
}
}
'''
# 方式二 marshal方法
class Test2(Resource):
def get(self):
return marshal(StuInfo1, resource_fields), 200
'''
{
"name": "aa",
"cno": "c1",
"age": 12,
"admission_time": "2023-04-03T10:25:24.180457"
}
'''
if __name__ == '__main__':
app = Flask(__name__)
api = Api(app)
api.add_resource(Test1, '/test1')
api.add_resource(Test2, '/test2')
app.run(debug=True)
3.2 常规用法
# 1.常规用法
# 1.1 输出响应域格式
resource_fields = {
'name': fields.String(attribute='other_name'), # 别名 attribute指定实际属性名
'cno': fields.String(attribute=lambda x: x.cno), # lambda表达式__call__对象属性
'age': fields.Integer(default=12), # default 默认值指定
'admission_time': fields.DateTime(dt_format='iso8601'), # 日期输出格式
'score': fields.List(fields.Integer, attribute="score_list"), # 单属性对应多个值
'evaluate': Evaluate(attribute="evaluate"), # 自定义响应字段
'school_info': fields.Nested(school_info_resource), # 嵌套响应字段1
'score_detail': fields.List(fields.Nested(score_detail_resource)), # 嵌套字段2
'api_absolute': fields.Url('hello', absolute=True, scheme='http'), # 其他(absolute全API信息|scheme匹配特征协议)
'api_simple': fields.Url('hello',)
}
# 1.2 嵌套响应域格式
school_info_resource = {
"sName": fields.String,
"addr": fields.String
}
resource_fields = {
'school_info': fields.Nested(school_info_resource), # 嵌套响应字段
}
# 1.3 自定义响应字段
class Evaluate(fields.Raw):
def format(self, value):
return "良好" if not value else "优秀"
# 2. 全局指定
3.3 实例
# -*- coding:utf-8 -*-
import datetime
from flask import Flask
from flask_restful import fields, Resource, marshal_with, Api
# 自定义响应字段
class Evaluate(fields.Raw):
def format(self, value):
return "良好" if not value else "优秀"
# 嵌套响应字段1
school_info_resource = {
"sName": fields.String,
"addr": fields.String
}
# 嵌套响应字段2
score_detail_resource = {
"subject": fields.String(attribute=lambda x: x.subject),
"score": fields.Integer
}
# 响应域(实际输出的数据结构)
resource_fields = {
'name': fields.String(attribute='other_name'), # 别名 attribute指定实际属性名
'cno': fields.String(attribute=lambda x: x.cno), # lambda表达式__call__对象属性
'age': fields.Integer(default=12), # default 默认值指定
'admission_time': fields.DateTime(dt_format='iso8601'), # 日期输出格式
'score': fields.List(fields.Integer, attribute="score_list"), # 单属性对应多个值
'evaluate': Evaluate(attribute="evaluate"), # 自定义响应字段
'school_info': fields.Nested(school_info_resource), # 嵌套响应字段1
'score_detail': fields.List(fields.Nested(score_detail_resource)), # 嵌套字段2
'api_absolute': fields.Url('hello', absolute=True, scheme='http'), # 其他(absolute全API信息|scheme匹配特征协议)
'api_simple': fields.Url('hello',)
}
# ORM实体类
class SchoolInfo:
def __init__(self):
self.sName = "学校名"
self.addr = "学校地址"
class ScoreInfo:
def __init__(self):
self.subject = "语文"
self.score = 85
class StuInfo:
def __init__(self, **stu_info):
self.other_name = stu_info.get("name")
self.cno = stu_info.get("cno")
self.age = stu_info.get("age")
self.admission_time = stu_info.get("admission_time")
self.score_list = stu_info.get("score_list")
self.evaluate = stu_info.get("evaluate")
self.school_info = stu_info.get("school_info")
self.score_detail = stu_info.get("score_detail")
# pv访问资源类
class StuResource(Resource):
@marshal_with(resource_fields)
def get(self):
# 查询输出特定实体类
stu_info = {
'name': "n1", # 别名 attribute指定实际属性名
'cno': "c1", # lambda表达式__call__对象属性
'age': None, # default 默认值指定
'admission_time': datetime.datetime.now(), # 日期输出格式
'score_list': [88, 85], # 单属性对应多个值
'evaluate': 0, # 自定义响应字段
'school_info': SchoolInfo(),
'score_detail': ScoreInfo()
}
s1 = StuInfo(**stu_info)
return s1
if __name__ == '__main__':
app = Flask(__name__)
api = Api(app)
api.add_resource(StuResource, "/", endpoint='hello')
app.run(debug=True)

4 扩展
4.1 全局响应内容自定义
# -*- coding:utf-8 -*-
'''
flask-restful 扩展知识
'''
import json
from flask import Flask, make_response
from flask_restful import fields, Resource, marshal_with, Api
# 响应域(实际输出的数据结构)
resource_fields = {
'name': fields.String(attribute='other_name'),
'cno': fields.String(attribute=lambda x: x.cno),
'age': fields.Integer(default=12),
}
class StuInfo:
def __init__(self, **stu_info):
self.other_name = stu_info.get("name")
self.cno = stu_info.get("cno")
self.age = stu_info.get("age")
# pv访问资源类
class StuResource(Resource):
@marshal_with(resource_fields)
def get(self):
# 查询输出特定实体类
stu_info = {
'name': "n1",
'cno': "c1",
'age': None,
}
s1 = StuInfo(**stu_info)
return s1
# 方式一
# 1.1 json数据自定义格式输出
def out_json(data, code, headers=None):
resp = make_response(json.dumps({"code": 0, "msg": "", "data": data}), code)
resp.headers.extend(headers or {})
return resp
# 1.2 MyApi继承Api并重写
class MyApi(Api):
def __init__(self, *args, **kwargs):
super(MyApi, self).__init__(*args, **kwargs)
self.representations = {
"application/json": out_json,
# 支持多种类型返回格式定义
# "application/xml": out_xml,
# "text/html": out_html,
# "text/csv": out_csv
}
if __name__ == '__main__':
app = Flask(__name__)
api = MyApi(app) # 方式一
api.add_resource(StuResource, "/", endpoint='hello')
app.run(debug=True)
# -*- coding:utf-8 -*-
'''
flask-restful 扩展知识
'''
from functools import wraps
from flask import Flask
from flask_restful import fields, Resource, marshal_with, Api, marshal
common_res = {
"code": fields.Integer(default=0),
"msg": fields.String(default="")
}
def response_return(func):
@wraps(func)
def inner_wraps(*args, **kwargs):
data = func(*args, **kwargs)
data = data if data else None
return_data = marshal(None, common_res)
return_data["data"] = data
return return_data, 200
return inner_wraps
# 2.2 自定义资源类
class MyResource(Resource):
method_decorators = [response_return] # 继承MyResource的子类会全局生效
# 响应域(实际输出的数据结构)
resource_fields = {
'name': fields.String(attribute='other_name'), # 别名 attribute指定实际属性名
'cno': fields.String(attribute=lambda x: x.cno), # lambda表达式__call__对象属性
'age': fields.Integer(default=12), # default 默认值指定
}
# ORM实体类
class StuInfo:
def __init__(self, **stu_info):
self.other_name = stu_info.get("name")
self.cno = stu_info.get("cno")
self.age = stu_info.get("age")
# pv访问资源类
class StuResource(MyResource):
@marshal_with(resource_fields)
def get(self):
# 查询输出特定实体类
stu_info = {
'name': "n1",
'cno': "c1",
'age': None,
}
s1 = StuInfo(**stu_info)
return s1
if __name__ == '__main__':
app = Flask(__name__)
api = Api(app) # 方式二
api.add_resource(StuResource, "/", endpoint='hello')
app.run(debug=True)

4.2 资源类-装饰器应用
# -*- coding:utf-8 -*-
import time
from functools import wraps
from flask import Flask
from flask_restful import marshal_with, fields, marshal, Api, Resource
# 装饰器作用方法或者资源类Resource
# 1. 自定义装饰器-局部作用范围
def cal_time(func):
@wraps(func)
def inner(*args, **kwargs):
st = time.time()
data = func(*args, **kwargs)
data = data if data else {}
time.sleep(0.1)
et = round((time.time() - st), 2)
data.update({'costTime(s)': et, "dataType": str(type(data))})
return data
return inner
# 2. 自定义装饰器-全局作用范围
def response_return(func):
@wraps(func)
def inner_wraps(*args, **kwargs):
data = func(*args, **kwargs)
data = data if data else None
return_data = marshal(None, {"code": fields.Integer(default=0), "msg": fields.String(default="")})
return_data["data"] = data
return return_data, 200
return inner_wraps
# 自定义资源类
class MyResource(Resource):
method_decorators = [response_return] # 继承MyResource的子类会全局生效
# 响应域(实际输出的数据结构)
resource_fields = {
'name': fields.String(attribute='other_name'),
'cno': fields.String(attribute=lambda x: x.cno),
'age': fields.Integer(default=12),
}
# ORM实体类
class StuInfo:
def __init__(self, **stu_info):
self.other_name = stu_info.get("name")
self.cno = stu_info.get("cno")
self.age = stu_info.get("age")
# pv访问资源类
class StuResource(MyResource):
method_decorators = {'get': [cal_time]} # 特定资源类生效(覆盖全局)
@marshal_with(resource_fields)
def get(self):
# 查询输出特定实体类
stu_info = {
'name': "n1",
'cno': "c1",
'age': None,
}
s1 = StuInfo(**stu_info)
return s1
if __name__ == '__main__':
app = Flask(__name__)
api = Api(app) # 方式二
api.add_resource(StuResource, "/", endpoint='hello')
app.run(debug=True)

4.3 蓝图应用
# -*- coding:utf-8 -*-
from flask import Flask, Blueprint
from flask_restful import Api, Resource
# 访问资源类
class StuResource(Resource):
def get(self):
return "OK"
if __name__ == '__main__':
app = Flask(__name__)
api_bp = Blueprint('api', "api_bp") # 创建蓝图
api = Api(api_bp) # api关联蓝图
api.add_resource(StuResource, "/", endpoint='api_bp') # 资源类和路由关联映射
app.register_blueprint(api_bp) # 注册蓝图
app.run(debug=True)
4.4 资源类引用外部依赖
# -*- coding:utf-8 -*-
'''
初始化资源类完成外呼依赖引入
'''
from flask import Flask
from flask_restful import Api, Resource
# 访问资源类
class StuResource(Resource):
def __init__(self, *args, **kwargs):
self.outside_dependencies = kwargs['od']
def get(self):
return self.outside_dependencies
if __name__ == '__main__':
app = Flask(__name__)
api = Api(app)
api.add_resource(StuResource, "/", endpoint='hello', resource_class_kwargs={"od": "外部依赖引用"})
app.run(debug=True)
5. 参考文档