前言
flask框架默認的路由和視圖函數映射規則是通過在視圖函數上直接添加路由裝飾器來實現的,這使得路由和視圖函數的對應關系變得清晰,但對於統一的API開發就變得不怎么美妙了,尤其是當路由接口足夠多的時候,可讀性會變差。flask_restful可以使我們像Django那樣統一在一個地方設計所有的API規則。
flask_restful
安裝
pip install flask_restful
初始化
# __init__.py
from flask import Flask, current_app
app = Flask(__name__)
app.config['SECRET_KEY'] = '123'
# rest/__init___.py
from flask import Blueprint
from flask_restful import Api
# 創建藍圖
rest = Blueprint("rest", __name__)
flask_api = Api(app=rest)
__all__ = ["rest","api"]
# 加載藍圖所對應的視圖上下文
from .api import *
# 設計路由
flask_api.add_resource(ToDo1,'/restful')
flask_api.add_resource(Todo,"/restful1")
# rest/api.py
from flask_restful import Resource, fields, marshal_with, reqparse
parser = reqparse.RequestParser()
class ToDo1(Resource):
def get(self):
# 拷貝一個對象備用
new_parser = parser.copy()
# 添加請求參數和它的驗證規則
new_parser.add_argument('n', type=int, help='Rate to charge for this resource,{error_msg}')
# 進行驗證,得到通過驗證后的字典或者拋出400錯誤
args = new_parser.parse_args(strict=True)
print(args)
return "OK"
說明:
-
Api類管理着視圖函數的注冊和相關資源,使用app或藍圖對象對其進行初始化;
-
我們定義一個類繼承Resource,通過方法名指定post、get等請求方式的處理邏輯;
-
使用Api.add_resource方法設計api接口,指定路由和視圖函數的映射關系;
-
使用RequestParser對象對參數進行驗證;
-
核心的對象有Api和Resource,針對其一一分析;
APi對象分析
- 初始化
針對需要使用restful風格的藍圖進行初始化。
test = Blueprint("test", __name__)
api = Api(app=test, prefix='/rest1') # prefix添加url的前綴,如果藍圖也有前綴則拼接
- Api.add_resource
該方法對路由和視圖函數進行設計設定。創建了RULE對象,並將其添加到Maps對象中,同時將resource添加到app的view_functions中;
# 參數
resource:視圖類
urls:路由url,可以多個指向一個視圖類
endpoint:視圖函數的標志,默認用類名替代
api.add_resource(Todo,"/restful","/index", endpoint="restful")
請求數據驗證
對於收到的每一個請求,如果它帶了參數,后台是需要對參數進行驗證的,這是一個十分枯燥的工作,RequestParser對象就是幫助我們對參數進行驗證的,它可以實現驗證方法對驗證參數的解耦,是的代碼結構趨向統一。
- RequestParser
# 創建一個RequestParser,其會攔截request上下文,但是其內部存儲的參數列表對所有的請求都是同一個,因此針對每個請求都需要一個新的對象
parser = reqparse.RequestParser()
class ToDo1(Resource):
def get(self):
# 拷貝一個對象備用
new_parser = parser.copy()
# 向參數列表添加請求參數和它的驗證規則,每個參數單獨添加
new_parser.add_argument('n', type=int, help='Rate to charge for this resource,{error_msg}')
# 進行驗證,得到通過驗證后的字典或者拋出400錯誤
args = new_parser.parse_args(strict=True)
print(args)
return "OK"
# 使用 strict=True 調用 parse_args 能夠確保當請求包含你的解析器中未定義的參數的時候會拋出一個異常。
- RequestParser.add_argument
該方法其實是創建了一個Argument對象,其參數對應了Argument的初始化參數
def __init__(self, name, default=None, dest=None, required=False,
ignore=False, type=text_type, location=('json', 'values',),
choices=(), action='store', help=None, operators=('=',),
case_sensitive=True, store_missing=True, trim=False,
nullable=True):
self.name = name # 參數的名字
self.default = default # 如果沒有該參數,其默認的值
self.dest = dest # name的別名,解析后該參數的鍵編程別名
self.required = required # 參數是否可以省略,設置為True時,這個參數是必須的
self.ignore = ignore # 是否忽略參數類型轉換失敗的情況,默認不忽略
self.location = location # 從請求對象中獲取的參數類型,默認從values和json中解析值,但是可以指定'args','form','json',''headers','files'等解析;或指定列表['args','form'],優先解析列表末尾的值
self.type = type # 轉換的參數類型,默認轉換成str,失敗就報錯
self.choices = choices # 參數的值的有限集合,不在其中就報錯,如[1,2,3]
self.action = action # 如果要接受一個鍵有多個值的話,可以傳入 action='append', 默認是禁止的
self.help = help # 參數無效時可以返回的信息,默認返回錯誤信息
self.case_sensitive = case_sensitive # 是否區分大小寫,默認所有大寫轉換成小寫
self.operators = operators
self.store_missing = store_missing # 缺少參數時是否加默認的值
self.trim = trim # 是否去除參數周圍的空白
self.nullable = nullable # 是否允許參數使用空值
new_parser.add_argument('n', type=int, help='msgerr',location=('args',), required=True) # 從參數中獲取n,該參數是必須的
- RequestParser.replace_argument和RequestParser.remove_argument
覆蓋解析規則和刪除解析規則,主要用在當多個api有相同的解析參數時,可以設置共享參數解析;
parser = RequestParser()
parser.add_argument('foo', type=int)
parser_copy = parser.copy()
parser_copy.add_argument('bar', type=int)
parser_copy.replace_argument('foo', type=str, required=True, location='json')
驗證方法
Argument對象的type參數指定了參數的驗證方法,除了我們python常用的數據類型外,inputs模塊預定義了一些驗證方法供我們驗證。
- input模塊
parser_copy.add_argument('bar', type=url) # 驗證是否符合url格式
parser_copy.add_argument('bar', type=inputs.regex('^[0-9]+$')) # 自定義正則驗證
date:轉換成datatime.data對象;
natural:轉換類型為自然數;
positive:轉換類型為正整數
int_range:輸入整數的范圍,設置最大最小值
boolean:限制必須為布爾類型;
- 自定義驗證方法
input模塊提供的方法也是不夠用的。很多時候我們需要自定義驗證方法:
def ver_num(value, name):name表示參數名
if value % 2 == 0:
raise ValueError("Value is not odd")
return value
parser_copy.add_argument('bar', type=ver_num) # 驗證是調用:ver_num("xxx","bar")
# 方法必須有兩個參數,第一個value表示參數的值,name表示參數的名字,
# 如果驗證不通過,我們就拋出一個ValueError錯誤。
- RequestParser.parse_args
當調用parse_args時就會按添加時的順序依次驗證請求參數,如果有驗證不通過的參數,調用flask.abort(400);
如果設置了strict=True,請求包含解析器中未定義的參數也會拋出400狀態碼。
parse_args
#參數:
req:請求上下文,默認使用flask.request
strict:請求是否可以包含解析器中未定義的參數,默認false,即可以。
實際使用時如果我們想改變拋出異常的錯誤處理就最好捕捉一下異常:
class ToDo1(Resource):
try:
new_parser = parser.copy()
new_parser.add_argument('n', type=int, help='Rate to charge for this resource,{error_msg}')
args = new_parser.parse_args(strict=True)
except HTTPException as e:
do somthing...
raise e
return "OK"
小結
-
對於RequestParser來說,其主要的功能對請求的參數進行驗證,驗證通過反饋參數字典,否則拋出400異常;
-
我們可以設置自定義的驗證類型來驗證額外的需求;
格式化返回數據
通過marshal_with裝飾器,我們可以對視圖函數返回的數據進行格式化,使得呈現給前端的數據符合我們預期要返回的數據格式,而不需要去手動使用代碼轉化;
class Todo(Resource):
@marshal_with({'data':fields.String}, envelope='resource')
def get(self, **kwargs):
return {'data':'xiao'}
# 前端數據
{"resource": {"data": "xiao"}}
# 參數
fields:數據規則;
envelope:數據的鍵,如果定義了該值,數據就以該值為鍵返回。
使用方式
- 定義想要返回的數據格式,可以是復雜的嵌套結構
resource_dict = {"status_code": fields.Integer,
"message": {"count": fields.Integer, "newcount": fields.Integer}}
但是我們視圖函數的返回值可以是扁平結構:
resource_fields = {"status_code":fields.Integer,
"data":{"count": fields.Integer, "newcount": fields.List(fields.Integer)}},
@app.route('/')
@marshal_with(resource_fields,
envelope='message')
def index(self):
return {"status_code":200, "count":10, "newcount": [5,6]}
marshal_with會將你返回的數據和預定義的數據進行比較,存在的就填充,不存在就用None替代,用envelope為鍵將結果包裹;裝飾過后視圖函數的返回結果是OrderedDict對象。
- 自定義返回的數據類型
restful模塊默認對前端返回的是json格式的字符串數據,所有的Api都只支持json,即所有視圖返回的數據都會默認嘗試轉化成json格式,不成功就會報錯。我們可以指定APi默認輸出的格式,如html,csv,json等;
# 設置輸出html格式,所有視圖返回的數據都會默認嘗試轉化成text/html格式
rest_api = Api(app)
@rest_api.representation('text/html')
def output_html(data, code, headers=None):
resp = make_response(data, code)
resp.headers.extend(headers or {})
return resp
#或者這樣寫:
rest_api.representations = OrdereDict({'text/html':output_html})
# 同理如果想設置其他格式的處理函數
@rest_api.representation('application/json')
def output_json(data, code, headers=None):
pass
@api.representation('text/csv')
def output_csv(data, code, headers=None):
pass
# 或者
rest_api.representations = OrdereDict({'application/json':output_json})
rest_api.representations = OrdereDict({'text/csv':output_csv})
fields模塊
fields模塊可以說是專為json數據交互准備的,被marshal_with裝飾的視圖函數必須返回字典對象,它定義了許多的針對視圖函數對外的格式化處理方法。
resource_fields = {
'name': fields.String,
'address': fields.String,
'date_updated': fields.DateTime(dt_format='rfc822'),
}
# 相應的字段有:
"String":字符串,參數attribute指定別名,default指定默認值;
"FormattedString":格式化字符串,參數如"name{age}"這種形式
"Url":生成url,參數endpoint和url_for的參數類似,根據它找到對應的url,參數absolute=True則生成絕對url,參數scheme='https'則修改協議
"DateTime":轉化日期的標准格式,如datetime.datetime(2018,11,23)--》"Fri, 23 Nov 2018 00:00:00 -0000"
"Float":小數;
"Integer":整形,默認為0;
"Arbitrary":任意精度的浮點數
"Nested":嵌套結構;
"List":列表類型,參數cls_or_instance指定列表元素的類型,如:List(String)
"Raw":基類,參數attribute指定別名,default指定默認值;
"Boolean":布爾值,返回True或False
"Fixed":固定精度的十進制數,參數decimals指定精度;
"Price":Fixed的引用;
- 如果內置的字段不滿足我們的要求,我們可以自定義:
class UrgentItem(fields.Raw):
def format(self, value):
return "big" if value == 1 else "small"
# 只需要繼承Raw並實現format函數即可