資料函式庫遷移和快取是現代 Web 應用程式開發的關鍵環節。本文將引導你使用 Alembic 和 Flask-Migrate 管理 Flask 應用程式的資料函式庫結構變更,確保資料函式庫與程式碼同步,同時示範如何整合 Redis 快取,提升應用程式效能。透過實際程式碼範例,你將學習如何初始化遷移環境、產生遷移指令碼、套用資料函式庫更新,以及使用 Redis 儲存和讀取快取資料,進一步最佳化 Flask 應用程式。文章也涵蓋了 NoSQL 資料函式庫 MongoDB 的整合方式,提供更多元的資料函式庫選擇。除了程式碼實作,文章也說明瞭 Flask 中根據函式和類別的檢視撰寫方法,以及如何使用 MethodView 簡化類別檢視的開發流程。

資料函式庫遷移與快取實作:使用Alembic、Flask-Migrate與Redis最佳化Flask應用

在開發Flask應用程式時,資料函式庫遷移和快取機制是至關重要的技術。本篇文章將探討如何使用Alembic和Flask-Migrate進行資料函式庫遷移,以及如何利用Redis實作高效的資料快取。

資料函式庫遷移:Alembic與Flask-Migrate的協同工作

在進行資料函式庫遷移前,首先需要初始化遷移環境。執行以下指令:

$ flask db init

此步驟會在專案中建立遷移指令碼所需的目錄結構。

重要注意事項

為確保Flask應用程式能夠正確執行遷移指令,需要設定FLASK_APP環境變數,指向你的Flask應使用案例項:

export FLASK_APP="my_app.__init__.py"

或簡化為:

export FLASK_APP=my_app

模型變更與遷移

當資料模型發生變更後,例如在Product模型中新增company欄位:

class Product(db.Model):
    # ... 其他欄位定義
    company = db.Column(db.String(100))

執行以下指令以建立遷移指令碼:

$ flask db migrate

此指令會比較資料函式庫目前的結構與更新後的模型定義,自動生成遷移指令碼。輸出範例如下:

INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.autogenerate.compare] Detected added column 'product.company'
Generating <path/to/application>/flask_catalog/migrations/versions/2c08f71f9253_.py ... done

接著,執行以下指令以套用遷移:

$ flask db upgrade

輸出結果如下:

INFO [alembic.runtime.migration] Context impl SQLiteImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade None -> 2c08f71f9253, empty message

資料函式庫遷移流程圖示

@startuml
skinparam backgroundColor #FEFEFE
skinparam componentStyle rectangle

title Flask應用資料函式庫遷移與快取實作

package "Flask 資料庫遷移與快取" {
    package "遷移工具" {
        component [Alembic] as alembic
        component [Flask-Migrate] as migrate
        component [flask db init] as init
    }

    package "遷移流程" {
        component [模型變更] as model
        component [migrate 產生] as gen
        component [upgrade 套用] as upgrade
    }

    package "快取整合" {
        component [Redis] as redis
        component [快取讀寫] as cache
        component [MongoDB] as mongo
    }
}

alembic --> migrate : 整合封裝
migrate --> init : 環境初始化
model --> gen : 偵測變更
gen --> upgrade : 套用遷移
redis --> cache : 高效存取
cache --> mongo : 多元資料庫

note right of migrate : FLASK_APP 設定\n遷移指令碼
note right of redis : 效能提升\n資料快取

@enduml

使用Redis進行資料快取

為了提高應用效能,可以利用Redis作為快取儲存非永續性資料。例如,記錄最近瀏覽的產品資訊。

安裝與設定Redis

首先,安裝Redis客戶端函式庫:

$ pip install redis

接著,在my_app/__init__.py中建立Redis連線:

from redis import Redis
redis = Redis()

快取最近瀏覽產品

在產品詳情頁面檢視函式中,新增以下程式碼以快取產品資訊:

@catalog.route('/product/<id>')
def product(id):
    product = Product.query.get_or_404(id)
    product_key = 'product-%s' % product.id
    redis.set(product_key, product.name)
    redis.expire(product_key, 600)  # 設定10分鐘過期
    return 'Product - %s, $%s' % (product.name, product.price)

取得最近瀏覽產品列表

建立新的檢視函式以擷取快取中的產品資訊:

@catalog.route('/recent-products')
def recent_products():
    keys_alive = redis.keys('product-*')
    products = [redis.get(k).decode('utf-8') for k in keys_alive]
    return jsonify({'products': products})

重點解析

  • 資料函式庫遷移:使用Alembic和Flask-Migrate管理資料函式庫結構變更。
  • 快取機制:利用Redis儲存非永續性資料,提高應用存取速度。
  • 實作細節:詳細介紹了資料函式庫遷移和快取實作的步驟與程式碼範例。

使用 MongoDB 的 NoSQL 方式

在建構應用程式時,有時所使用的資料可能根本沒有結構,或者是半結構化的,又或者有一些資料的結構會隨著時間頻繁變更。在這種情況下,我們會避免使用關聯式資料函式庫(RDBMS),因為它會增加痛苦且難以擴充套件和維護。對於這種情況,最好使用 NoSQL 資料函式庫。

此外,由於目前流行的開發環境中快速發展的結果,我們不可能第一次就設計出完美的結構。NoSQL 提供了修改結構的彈性,而不會造成太大的麻煩。

在生產環境中,資料函式庫通常會隨著時間的推移而變得非常龐大。這會大大影響整個系統的效能。雖然有垂直和水平擴充套件技術可用,但有時它們可能會非常昂貴。在這種情況下,可以考慮使用 NoSQL 資料函式庫,因為它從一開始就為類別似的目的而設計。NoSQL 資料函式庫能夠在大型多叢集上執行,並處理以高速率生成的大量資料,這使得它們在處理傳統 RDBMS 的擴充套件問題時成為一個不錯的選擇。

在本配方中,我們將使用 MongoDB 來學習如何將 NoSQL 與 Flask 整合。

準備工作

有許多擴充功能可用於將 Flask 與 MongoDB 搭配使用。我們將使用 Flask-MongoEngine,因為它提供了良好的抽象層級,使得理解起來更容易。它可以使用以下指令安裝:

$ pip install flask-mongoengine

請記得執行 MongoDB 伺服器,以便建立連線。關於安裝和執行 MongoDB 的更多詳細資訊,請參考 http://docs.mongodb.org/manual/installation/

操作步驟

首先,使用命令列手動建立 MongoDB 中的資料函式庫。讓我們將這個資料函式庫命名為 my_catalog

>>> mongosh
Current Mongosh Log ID: 62fa8dtfd435df654150997b
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.5.4
Using MongoDB: 6.0.0
Using Mongosh: 1.5.4
test> use my_catalog
switched to db my_catalog

以下是使用 MongoDB 重寫我們的目錄應用程式。第一個變化出現在我們的設定檔 my_app/__init__.py 中:

from flask import Flask
from flask_mongoengine import MongoEngine

app = Flask(__name__)
app.config['MONGODB_SETTINGS'] = {'DB': 'my_catalog'}
app.debug = True
db = MongoEngine(app)

from my_app.catalog.views import catalog
app.register_blueprint(catalog)

重點資訊

請注意,我們現在使用的是 MONGODB_SETTINGS 而不是通常的 SQLAlchemy 設定。在這裡,我們只需要指定要使用的資料函式庫名稱,即 my_catalog

接下來,我們將使用 MongoDB 欄位建立一個 Product 模型。這通常發生在模型檔案 my_app/catalog/models.py 中:

import datetime
from my_app import db

class Product(db.Document):
    created_at = db.DateTimeField(default=datetime.datetime.now, required=True)
    key = db.StringField(max_length=255, required=True)
    name = db.StringField(max_length=255, required=True)
    price = db.DecimalField()

    def __repr__(self):
        return '<Product %r>' % self.id

重點資訊

現在是時候看看用於建立前述模型的 MongoDB 欄位,以及它們與之前配方中使用的 SQLAlchemy 欄位的相似之處。在這裡,我們沒有 ID 欄位,而是使用 key 來儲存用於唯一識別記錄的唯一識別碼。另外,請注意建立模型時繼承的類別。在 SQLAlchemy 的情況下,它是 db.Model,而在 MongoDB 的情況下,它是 db.Document。這符合這些資料函式庫系統的工作方式。SQLAlchemy 使用傳統的 RDBMS,而 MongoDB 是一個 NoSQL 檔案資料函式庫系統。

以下是檢視檔案,即 my_app/catalog/views.py

from decimal import Decimal
from flask import request, Blueprint, jsonify
from my_app.catalog.models import Product

catalog = Blueprint('catalog', __name__)

@catalog.route('/')
@catalog.route('/home')
def home():
    return "Welcome to the Catalog Home."

@catalog.route('/product/<key>')
def product(key):
    product = Product.objects.get_or_404(key=key)
    return 'Product - %s, $%s' % (product.name, product.price)

@catalog.route('/products')
def products():
    products = Product.objects.all()
    res = {}
    for product in products:
        res[product.key] = {
            'name': product.name,
            'price': str(product.price),
        }
    return jsonify(res)

@catalog.route('/product-create', methods=['POST',])
def create_product():
    name = request.form.get('name')
    key = request.form.get('key')
    price = request.form.get('price')
    product = Product(
        name=name,
        key=key,
        price=Decimal(price)
    )
    product.save()
    return 'Product created.'

你會注意到它與根據 SQLAlchemy 的模型的檢視非常相似。只是從 MongoEngine 擴充功能呼叫的方法有一些不同,這些應該很容易理解。

工作原理

首先,使用 /product-create 端點新增產品到資料函式庫:

>>> res = requests.post('http://127.0.0.1:5000/product-create', data={'key': 'iphone-5s', 'name': 'iPhone 5S', 'price': '549.0'})

現在,透過存取 http://127.0.0.1:5000/products 端點來驗證產品的新增。以下是結果 JSON 值:

{
    "iphone-5s": {
        "name": "iPhone 5S",
        "price": "549.00"
    }
}

相關內容

請參考「建立基本產品模型」配方,以瞭解此應用程式的結構。

在Flask中處理檢視(Views)

撰寫根據函式的檢視與URL路由

在Flask中,撰寫根據函式的檢視與URL路由是最簡單的方式。我們只需撰寫一個方法並使用裝飾器(decorator)來定義端點。在本文中,我們將示範如何撰寫用於GET和POST請求的URL路由。

簡單的GET請求

以下是一個簡單的GET請求範例:

@app.route('/a-get-request')
def get_request():
    bar = request.args.get('foo', 'bar')
    return '一個簡單的Flask請求,其中foo是 %s' % bar

在這個範例中,我們檢查URL查詢是否包含名為foo的引數。如果有,我們在回應中顯示該值;否則,預設值為bar

簡單的POST請求

POST請求與GET請求相似,但有一些差異:

@app.route('/a-post-request', methods=['POST'])
def post_request():
    bar = request.form.get('foo', 'bar')
    return '一個簡單的Flask請求,其中foo是 %s' % bar

在這個範例中,我們在路由中增加了一個額外的引數methods,並使用request.form來取得表單資料。

簡單的GET/POST請求

我們可以將GET和POST請求合併成一個檢視函式:

@app.route('/a-request', methods=['GET', 'POST'])
def some_request():
    if request.method == 'GET':
        bar = request.args.get('foo', 'bar')
    else:
        bar = request.form.get('foo', 'bar')
    return '一個簡單的Flask請求,其中foo是 %s' % bar

內容解密:

  1. request.args.get()用於取得GET請求中的查詢引數。
  2. request.form.get()用於取得POST請求中的表單資料。
  3. methods引數用於指定檢視函式支援的HTTP方法。

撰寫根據類別的檢視

Flask 0.7版本引入了可插拔檢視(pluggable views)的概念,使得我們可以以類別的形式撰寫檢視。在本文中,我們將示範如何建立根據類別的檢視。

簡單的GET請求

以下是一個簡單的GET請求範例:

from flask.views import View

class GetRequest(View):
    def dispatch_request(self):
        bar = request.args.get('foo', 'bar')
        return '一個簡單的Flask請求,其中foo是 %s' % bar

app.add_url_rule('/a-get-request', view_func=GetRequest.as_view('get_request'))

簡單的GET/POST請求

我們可以建立一個支援GET和POST請求的類別檢視:

class GetPostRequest(View):
    methods = ['GET', 'POST']

    def dispatch_request(self):
        if request.method == 'GET':
            bar = request.args.get('foo', 'bar')
        elif request.method == 'POST':
            bar = request.form.get('foo', 'bar')
        return '一個簡單的Flask請求,其中foo是 %s' % bar

app.add_url_rule('/a-request', view_func=GetPostRequest.as_view('a_request'))

內容解密:

  1. View類別是Flask提供的基礎類別,用於建立可插拔檢視。
  2. dispatch_request方法是檢視的核心,用於處理請求並傳回回應。
  3. methods屬性用於指定檢視支援的HTTP方法。

使用MethodView簡化類別檢視

Flask提供了MethodView類別,使得我們可以更簡潔地建立類別檢視。以下是一個範例:

from flask.views import MethodView

class GetPostRequest(MethodView):
    def get(self):
        bar = request.args.get('foo', 'bar')
        return '一個簡單的Flask請求,其中foo是 %s' % bar

    def post(self):
        bar = request.form.get('foo', 'bar')
        return '一個簡單的Flask請求,其中foo是 %s' % bar

app.add_url_rule('/a-request', view_func=GetPostRequest.as_view('a_request'))

內容解密:

  1. MethodView類別提供了更簡潔的方式來建立類別檢視。
  2. 我們可以為每個HTTP方法定義單獨的方法(例如getpost等)。
  3. as_view方法用於將類別檢視轉換為可用的檢視函式。