正式在資訊相關工作就職後,所有開發工作轉變為前後端分離的開發模式,別於筆者在先前學習的 Flask 全端開發模式。這期間也有嘗試性地將 Flask 做到前後端分離,結果還是乖乖的使用 Jinja 進行輸出畫面。當然依現在回顧起來,當時的做法其實可以更為優化,不過時光已過。近期,發現 Flask 開發者之一的李輝的推出了全新的 Resp Api 套件 (框架?) APIFlask,因此就來寫一篇以 APIFlask 作為後端 API 伺服器的文章吧。
APIFlask 介紹
APIFlask 是一個基於 Flask 的輕量級 Web 框架,專為建立 RESTful API 而設計。它結合了 Flask 與 marshmallow-code 並可自動化產生互動式 API 文件。
APIFlask 安裝指令
# Terminal
pip install apiflask
範例專案結構
- project dir
-- app.py
Hello APIFlask 最簡程式碼
# Python
# <project dir>/app.py
from apiflask import APIFlask
app = APIFlask(__name__)
@app.route('/')
def index():
result = {
message: 'Hello APIFlask'
}
return result
基本上只是將 Flask 引入 from flask import Flask 改為 APIFlask 關鍵字,這一改變 APIFlask 會幫我們自動生成互動式的 API 文件,讓我們繼續將程式運行起來吧
# Terminal
flask run
當終端機回應出
---
Running on http://127.0.0.1:5000
---
時,使用瀏覽器網址列輸入上述網址與 http://127.0.0.1:5000/docs 可發現,APIFlask 自動做出
- 將 Python Dict 型態轉為一般的 JSON 字典型態。
- 生成 Swagger UI 的互動式 API 文件。
接下來讓程式碼更 APIFlask 化吧
# Python
# <project dir>/app.py
from apiflask import APIFlask
app = APIFlask(__name__)
@app.get('/')
def index():
result = {
message: 'Hello APIFlask'
}
return result
這步驟將原先 Flask 裝飾器 route 改為 get ,能更明確的指出當前的 http 方法,輸出結果其實與未修改時相同。在過去 Flask 時,
@app.route('/') 更顯性的寫法如下
@app.route('/', methods=['GET'])
若要包含多種方法,則必須將方法放到 methods 中,並使用 flask 中的 request 判斷當前 http 方法,再用 Python 的判斷語句去控制不同的方法內容。在 APIFlask 提供了 get 、 post 、 put 與 delete 四個常用的 http 方法裝飾器,可以對相同的網址路徑,進行不同的邏輯控制。
實作小程式 - 1
在前後端分離的架構中,後端 API 伺服器扮演著資源提供者的角色,將各種功能和資料抽象化,並以 RESTful API 的形式提供給前端伺服器。這種模式使得前端可以更加專注於用戶界面和用戶體驗。下列我們舉出兩個例子來說明
- 取得所有飯店資訊
假設我們有一個需求,需要取得所有飯店的資料。在這種情況下,前端伺服器會向後端 API 伺服器發出一個請求,例如發送一個 GET 請求到 /fruits 網址。後端接收到請求後,從資料庫中檢索所有飯店的資料,並以 JSON 格式回傳給前端伺服器。 - 取得登入資訊
考慮使用者的登入功能。在過去,用戶的登入狀態可能是通過 cookies 或 sessions 在服務器端進行管理的。然而,在前後端分離的架構中,這些資訊也被抽象為一種資源,由前端伺服器負責管理。例如,當用戶嘗試登入時,前端伺服器會後端發出一個 POST 請求,包含使用者登入訊息。後端伺服器接收到請求後,驗證用戶名和密碼,並回傳一個包含用使用者資訊,由前端管理登入狀態管理。也由於登入狀態已改為前端伺服器處理了,後端也將淡化登出的內容管理。
接著開始一起完成下列實作吧
第一部分: API 首頁與虛擬資料建構
# Python
# <project dir>/app.py
from apiflask import APIFlask
# 指定 app 實例化 APIFlask 類
app = APIFlask(__name__)
# 虛擬飯店列表
hotels = [
{
'id': 1,
'name': 'Hotel A',
'address': 'Taipei',
'price': 3000
},
{
'id': 2,
'name': 'Hotel B',
'address': 'Taichung',
'price': 2500
},
{
'id': 3,
'name': 'Hotel C',
'address': 'Kaohsiung',
'price': 2000
}
]
# API 首頁
@app.get('/')
@app.get('/apis')
def index():
"""
API 首頁
"""
result = {
'message': 'Hello, APIFlask!'
}
return result
# 取得所有飯店資料
@app.get('/apis/hotels')
def get_hotels():
"""
取得所有飯店資料
"""
result = hotels
return result
附帶參數的 Http 請求
一般狀況下,無法單純以網址及 Http 方法對於後端的資源取得,為達成更複雜的資料請求,附帶參數的 Http 請求相應而生,以下為筆者知道的附帶方法 URL Path Param URL Query Param 與 Request Body Param
- URL Path Param 是指在 URL 路徑中直接包含參數的方式。這種方式常用於 RESTful API 中,例如取得特定 ID 的資源。當我們需要取得 ID 為 123 的使用者資訊時,URL 就會類似於 "/users/123"
- URL Query Param 是指在 URL 的查詢字串中包含參數的方式。這種方式常用於搜尋或篩選資料。例如,我們可以使用查詢字串來搜尋名稱為 "John" 的使用者,URL 就會類似於 "/users/?name=John"
- Request Body Param 是指在 HTTP 請求的 body 中包含參數的方式。這種方式常用於 POST、PUT 或 PATCH 等方法,用於傳送資料到後端。例如,當我們需要創建一個新的使用者時,我們可以在請求的 body 中包含使用者的資訊。
在選擇使用哪種方法時,需要考慮以下幾點:
- 安全性:URL Path Param 和 URL Query Param 的參數會在 URL 中顯示,可能會導致敏感資訊暴露。因此,對於敏感資訊,應該使用 Request Body Param。
- 資料量:URL 的長度有限制,因此對於大量資料,使用 Request Body Param 是更好的選擇。
- 請求方法:對於建立或更新資源,使用 POST、PUT 或 PATCH 方法並在 Request Body 中包含資料是最常見的做法。
實作小程式 - 2
此部分實作將銜接實作小程式 - 1 繼續進行
第二部分: 取得所有飯店資料增加篩選功能
功能簡述: 原取得所有飯店資料僅能直接將資源輸出,接下來增加關鍵字搜尋飯店,金額篩選飯店的篩選功能,此部分使用 URL Query Param 來實作,APIFlask 提供了 input 裝飾器搭配 Schema 來完成
增加三個關鍵字
- kw 飯店關鍵字
- min-price 最低金額
- max-price 最高金額
# Python
# <project dir>/app.py
from apiflask import APIFlask
from apiflask import Schema
from apiflask.fields import String, Integer
...
class HotelSearchInSchema(Schema):
kw = String(description='關鍵字', required=False)
min_price = Integer(description='最低價格', required=False)
max_price = Integer(description='最高價格', required=False)
# 取得篩選飯店資料
@app.get('/apis/hotels')
@app.input(HotelSearchInSchema, location='query')
def get_hotels(**kwargs):
"""
取得篩選飯店資料
"""
query_params = kwargs.get('query_data', {})
kw = query_params.get('kw', '')
min_price = int(query_params.get('min_price', 0))
max_price = int(query_params.get('max_price', 0))
hotels_price = [hotel['price'] for hotel in hotels]
if max_price == 0:
max_price = max(hotels_price)
result = []
for hotel in hotels:
if kw in hotel['name'] and min_price <= hotel['price'] <= max_price:
result.append(hotel)
return result
第三部分: 取得指定飯店資料
功能簡述: 透過飯店 id 取得指定飯店資訊,此部分使用 URL Path Param 來實作,APIFlask 提供動態的網址來完成
# Python
# <project dir>/app.py
...
@app.get('/apis/hotels/<int:hotel_id>')
def get_hotel(hotel_id):
"""
取得單一飯店資料
"""
result = [hotel for hotel in hotels if hotel['id'] == hotel_id][0]
return result
第四部分: 新增一筆飯店資料
功能簡述: 輸入飯店的必填資料並將資料放入飯店資料內,其中必要欄位有
- name 飯店名稱
- address 飯店地址
- price 飯店價格
此部分使用 Request Body Param 來實作,APIFlask 提供了 input 裝飾器搭配 Schema 來完成
# Python
# <project dir>/app.py
...
class HotelCreateInSchema(Schema):
name = String(description='飯店名稱', required=True)
address = String(description='飯店地址', required=True)
price = Integer(description='飯店價格', required=True)
@app.post('/apis/hotels')
@app.input(HotelCreateInSchema, location='json')
def create_hotel(**kwargs):
"""
新增飯店資料
"""
raw_data = kwargs.get('json_data', {})
max_hotel_id = max([hotel['id'] for hotel in hotels])
new_hotel = {
'id': max_hotel_id + 1,
'name': raw_data['name'],
'address': raw_data['address'],
'price': raw_data['price']
}
hotels.append(new_hotel)
result = {
'data': new_hotel,
'message': 'Create Hotel Success!'
}
return result
到這部分,可以觀察出
APIFlask 在實作 URL Query Param 與 Request Body Param 的方式基本上是相同的,都是以 input 裝飾器與 Schema 來完成,唯一的差別是在 location 部分,
- URL Query Param: location='query'
- Request Body Param: location='json'
APIFlask 在實作 URL Path Param 與一般在操作動態的網址設定一致