1. 程式人生 > 實用技巧 >flask-restful擴充套件

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