flask-restful扩展

1、视图

1.1、扩展介绍

Flask-RESTful 是一个 Flask 扩展,它添加了快速构建 REST APIs 的支持。

  • 安装flask-restful
pip install flask-restful

1.2、构建RESTAPI

使用 flask-restful 构建RESTAPI只需要进行三步操作

  • 创建扩展/组件对象
    • 组件对象 = Api(app)
  • 定义类视图
    • class 自定义视图类(Resource)
  • 组件添加类视图
    • 组件对象.add_resource(视图类,url资源段)

代码示例:

from flask import Flask
from flask_restful import Api, Resource

app = Flask(__name__)
# 1. 创建API对象  用于管理类视图(资源)
api = Api(app)


# 2.定义类视图  继承Resource
class DemoResource(Resource):
    def get(self):
        # 类视图响应的content-type默认变为json形式
        # 类视图的返回值可以是字典, 会被自动转为json字符串
        return {'foo': 'get'}

    def post(self):
        return {'foo': 'post'}


# 3.添加类视图  函数标记默认为类名小写
api.add_resource(DemoResource, '/', endpoint='demo')

# @app.route('/')
# def index():
#
#     return "index"

if __name__ == '__main__':
    print(app.url_map)
    app.run(debug=True)
View Code

类视图的优点

  • 类视图响应的 content-type 默认变为 JSON形式
  • 类视图的 返回值可以是字典, 会被自动转为json字符串

1.3、类视图设置装饰器

  • 通过 method_decorators类属性 来设置类视图的装饰器
  • 该属性接收两种类型数据
    • 列表形式: 所有请求方式都会使用列表中的装饰器
    • 字典形式: 给请求方式分别设置装饰器
from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

def deco1(f):
    def wrapper(*args, **kwargs):
        print('deco1')
        return f(*args, **kwargs)

    return wrapper


def deco2(f):
    def wrapper(*args, **kwargs):
        print('deco2')
        return f(*args, **kwargs)

    return wrapper


class DemoResource(Resource):
    # 通过method_decorators类属性来设置类视图的装饰器
    # method_decorators = [deco1, deco2]  # 列表形式 所有请求方式都会使用
    method_decorators = {'get': [deco1], 'post': [deco2]}  # 字典形式 给请求方式分别设置装饰器

    # @deco2
    # @deco1
    def get(self):
        return {'foo': "get"}

    def post(self):
        return {'foo': "post"}


api.add_resource(DemoResource, '/')


if __name__ == '__main__':
    app.run(debug=True)
View Code

1.4、蓝图和类视图

蓝图和类视图可以配合使用, 步骤如下:

  • 创建蓝图对象
    • 蓝图对象 = Blueprint(“蓝图名”, __name__)
  • 每个蓝图分别创建组件对象
    • 组件对象 = Api(蓝图对象)
  • 组件对象添加类视图
    • 组件对象.add_resource(视图类,url资源段)
  • 应用注册蓝图
    • 应用对象.register_blueprint(蓝图对象)

代码示例:

  • user包中创建 views文件, 定义类视图
# user/views.py

from flask_restful import Resource


class DemoResource(Resource):
    def get(self):
        return {'get': 'hello'}

    def post(self):
        return {'post': 'world'}
View Code
  • user包 的初始化文件 __init__.py 中:
    • 创建蓝图对象
    • 通过蓝图对象创建对应的组件对象
    • 组件对象添加类视图
# user/__init__.py

from flask import Blueprint
from flask_restful import Api
from user.views import DemoResource

# 1.创建蓝图对象
user_blu = Blueprint('user', __name__, url_prefix='/user')

# 2.创建蓝图对应的api对象
user_api = Api(user_blu)

# 3.添加类视图
user_api.add_resource(DemoResource, '/')
View Code
  • 想要让蓝图对象能够完成路由定义, 还需要 Flask应用注册蓝图对象
# main.py

from flask import Flask
from user import user_blu

app = Flask(__name__)
# 4.注册蓝图
app.register_blueprint(user_blu)


if __name__ == '__main__':
    print(app.url_map)
    app.run(debug=True, port=8000)
View Code

2、请求

2.1、请求解析

  • 通过前一阶段的学习, 我们知道 DRF框架 的核心功能 序列化器, 包括以下功能:
    • 反序列化
      • 参数解析
      • 数据保存
    • 序列化
  • flask-restful 也实现了类似的功能:
    • 参数解析 RequestParser
    • 序列化 marshal函数
  • flask-restful 没有提供数据保存功能, 数据存储可以使用后续的flask-sqlalchemy扩展 来完成

2.2、RequestParser

RequestParser 负责请求解析工作, 基本步骤如下:

  • 创建请求解析器
    • 请求解析器 = ResquestParser()
  • 添加参数规则
    • 请求解析器.add_argument(参数名, 参数规则..)
  • 执行解析
    • 参数对象 = 请求解析器.parse_args()
  • 获取参数
    • 参数对象.参数名

代码示例:

from flask import Flask
from flask_restful import Resource, Api
from flask_restful.reqparse import RequestParser

app = Flask(__name__)
api = Api(app)


class DemoResource(Resource):
    def get(self):
        # 1.创建请求解析器
        parser = RequestParser()

        # 2.添加参数规则
        parser.add_argument('name')
        parser.add_argument('age')

        # 3.执行解析  默认会从 查询字符串/post键值对/post-json数据 进行参数提取
        args = parser.parse_args()

        # 4.获取参数
        print(args.name)
        print(args.age)

        return {'foo': "get"}


api.add_resource(DemoResource, '/')


if __name__ == '__main__':
    app.run(debug=True)
View Code

常用参数

参数 说明 取值
default 给参数设置默认值, 如果不传递该参数会使用默认值 str/int
required 是否必须传递,默认False,如果设置为True, 不传递会返回400 True / False
lacation 设置参数提取的位置 字符串, args/form/json/files/headers/cookies
type 设置参数的转换类型(类型转换 & 格式校验) 函数引用, int/内置类型/自定义函数

代码示例:

  • default、required、location
from flask import Flask
from flask_restful import Resource, Api
from flask_restful.reqparse import RequestParser

app = Flask(__name__)
api = Api(app)


class DemoResource(Resource):
    def post(self):
        parser = RequestParser()

        parser.add_argument('name', required=True, location='json')
        parser.add_argument('age', default=10)

        args = parser.parse_args()

        print(args.name)
        print(args.age)

        return {'foo': "post"}


api.add_resource(DemoResource, '/')


if __name__ == '__main__':
    app.run(debug=True)
View Code
  • type
from flask import Flask
from flask_restful import Resource, Api
from flask_restful.reqparse import RequestParser
from flask_restful.inputs import *

app = Flask(__name__)
api = Api(app)

# 自定义函数进行参数校验和转换
def func1(value):  # 必须定义形参来接收传递来的参数
    if re.match(r'^user:', value):
        return value[5:] # 转换完, 还需要将结果返回
    else:
        raise ValueError('age参数格式错误')  # 校验失败, 会将错误信息以json形式返回


class DemoResource(Resource):
    def put(self):
        parser = RequestParser()

        parser.add_argument('name')
        # parser.add_argument('age', type=int)  # 转为int类型

        # parser.add_argument('age', type=boolean)  # 转为bool类型   1/0 true/false
        # parser.add_argument('age', type=date)  # 日期 转为datetime类型   YYYY-mm-dd
        # parser.add_argument('age', type=datetime_from_iso8601)  # 时间 转为datetime类型  2012-01-01T23:30:00+02:00
        # parser.add_argument('age', type=int_range(5, 10))  # 转为int类型 限定范围[5, 10]

        # parser.add_argument('age', type=regex(r'^1[3-9]\d{9}$'))  # 要求匹配正则
        parser.add_argument('age', type=func1)  # 自定义函数

        args = parser.parse_args()

        print(args.name)
        print(args.age)
        print(type(args.age))

        return {'foo': 'put'}


api.add_resource(DemoResource, '/')


if __name__ == '__main__':
    app.run(debug=True)
View Code

3、响应

3.1、序列化

  • flask-restful 通过 marshal函数 来完成序列化处理
  • 操作步骤如下:

    • 定义序列化规则

      序列化规则 = {字段名: 序列化类型}

    • marshal函数 按照序列化规则 将模型对象转为字典

      序列化后的字典 = marshal(模型对象, 序列化规则)

常用序列化类型:

序列化类型 说明
String 可转换 字符串类型属性
Integer 可转换 整数类型属性
Float

可转换 浮点数类型属性

List 可转换 列表类型属性, 要求列表中元素类型唯一

Nested

可转换 字典类型属性

from flask import Flask
from flask_restful import Resource, Api, marshal, fields

app = Flask(__name__)
api = Api(app)

class User:  # 定义模型类
    def __init__(self):
        self.name = 'zs'
        self.age = 20
        self.height = 1.8
        self.scores = [80, 90]
        self.info = {
            'gender': True
        }


fields = {  # 序列化规则
    'username': fields.String(attribute='name'), # 指定对应的模型属性
    'age': fields.Integer(default=10),  # 设置默认值
    'height': fields.Float,
    'scores': fields.List(fields.Integer),  # 元素类型唯一
    'info': fields.Nested({'gender': fields.Boolean})
}


class DemoResource(Resource):
    def get(self):

        user1 = User()

        # marshal函数可以按照指定的序列化规则将 模型对象 转为 字典
        return marshal(user1, fields, envelope='data')


api.add_resource(DemoResource, '/')


if __name__ == '__main__':
    app.run(debug=True)
View Code

拓展:序列化还有一种装饰器形式 marshal_with, 使用装饰器形式则视图可以直接返回模型对象

from flask import Flask
from flask_restful import Resource, Api, marshal_with, fields

app = Flask(__name__)
api = Api(app)

class User:  # 定义模型类
    def __init__(self):
        self.name = 'zs'
        self.age = 20
        self.height = 1.8
        self.scores = [80, 90]
        self.info = {
            'gender': True
        }


fields = {  # 序列化规则
    'username': fields.String(attribute='name'), # 指定对应的模型属性
    'age': fields.Integer(default=10),  # 设置默认值
    'height': fields.Float,
    'scores': fields.List(fields.Integer),  # 元素类型唯一
    'info': fields.Nested({'gender': fields.Boolean})
}


class DemoResource(Resource):
    method_decorators = {'post': [marshal_with(fields)]}
    def post(self):
        user1 = User()
        # 如果设置了marshal_with装饰器, 可以直接返回模型对象
        return user1


api.add_resource(DemoResource, '/')


if __name__ == '__main__':
    app.run(debug=True)
View Code

实际开发中, 考虑到序列化的复杂性, 部分公司还会采用 自定义方法 的方式来完成序列化处理

from flask import Flask
from flask_restful import Resource, Api

app = Flask(__name__)
api = Api(app)

class User:
    def __init__(self):
        self.name = 'zs'
        self.age = 20
        self.height = 1.8
        self.scores = [80, 90]
        self.info = {
            'gender': True
        }

    def to_dict(self):  # 自定义模型转换方法
        return {
            'name': self.name,
            'age': self.age
        }


class DemoResource(Resource):
    def put(self):
        user1 = User()
        return user1.to_dict()


api.add_resource(DemoResource, '/')


if __name__ == '__main__':
    app.run(debug=True)
View Code

3.2、自定义JSON

实际开发中, 返回的JSON数据中除了包含基础数据, 往往还需要设置一些 统一的外层包装, 以便前端进行更好的解析处理, 如:

{
    "message": "ok",  // 外层包装
    "code": 200,  // 外层包装
    "data": {   // 基础数据
        "name": "张三",
        "age": 20
    }
}
  • flask-restful 提供了 api.representation()装饰器方法, 允许开发者自定义返回的数据格式
  • 在实现自定义JSON格式时, 可以参考 flask-restful 默认转换函数 (字典转json数据) 的源代码

  • 默认转换函数的源代码:

# flask_restful/representations/json.py

def output_json(data, code, headers=None):
    """Makes a Flask response with a JSON encoded body"""

    settings = current_app.config.get('RESTFUL_JSON', {})

    # If we're in debug mode, and the indent is not set, we set it to a
    # reasonable value here.  Note that this won't override any existing value
    # that was set.  We also set the "sort_keys" value.
    if current_app.debug:
        settings.setdefault('indent', 4)
        settings.setdefault('sort_keys', not PY3)

    # always end the json dumps with a new line
    # see https://github.com/mitsuhiko/flask/pull/1262
    dumped = dumps(data, **settings) + "\n"

    resp = make_response(dumped, code)
    resp.headers.extend(headers or {})
    return resp
View Code
from collections import OrderedDict
from json import dumps

from flask import Flask, current_app, make_response, Response
from flask_restful import Resource, Api
from six import PY3

app = Flask(__name__)
api = Api(app)


@api.representation('application/json')  # 指定响应形式对应的转换函数
def output_json(data, code, headers=None):
    """自定义json形式"""
    # 根据flask内置配置, 进行格式处理(缩进/key是否排序等)
    settings = current_app.config.get('RESTFUL_JSON', {})

    if current_app.debug:
        settings.setdefault('indent', 4)
        settings.setdefault('sort_keys', not PY3)

    # 添加json外层包装
    if 'message' not in data:  # 判断是否设置了自定义的错误信息
        data = {
            'message': 'ok',
            'data': data
        }

    # 字典转json字符串
    dumped = dumps(data, **settings) + "\n"

    # 构建响应对象
    resp = make_response(dumped, code)
    resp.headers.extend(headers or {})
    return resp


class DemoResource(Resource):
    def get(self):

        return {'foo': "get"}

    def post(self):
        return {'message': 'parameter error: name', "data": None}


api.add_resource(DemoResource, '/')


if __name__ == '__main__':
    app.run(debug=True)
View Code

实际开发中, 可能还会展开装饰器来设置自定义响应格式

def output_json(data, code, headers=None):
    """自定义json形式"""
    # 根据flask内置配置, 进行格式处理(缩进/key是否排序等)
    settings = current_app.config.get('RESTFUL_JSON', {})

    if current_app.debug:
        settings.setdefault('indent', 4)
        settings.setdefault('sort_keys', not PY3)

    # 添加json外层包装
    if 'message' not in data:  # 判断是否设置了自定义的错误信息
        data = {
            'message': 'ok',
            'data': data
        }

    # 字典转json字符串
    dumped = dumps(data, **settings) + "\n"

    # 构建响应对象
    resp = make_response(dumped, code)
    resp.headers.extend(headers or {})
    return resp


# 展开装饰器的形式设置自定义响应形式
api.representation('application/json')(output_json)
View Code

posted on 2020-11-01 18:20  yycnblog  阅读(213)  评论(0)    收藏  举报

导航