Flask 擴充套件開發

擴充套件是為 Flask 應用程式新增功能的額外套件。雖然 PyPI 包含許多 Flask 擴充套件,您可能找不到符合您需求的套件。如果這種情況發生,您可以建立自己的擴充套件,並發布供其他人使用。

本指南將說明如何建立 Flask 擴充套件,以及其中涉及的一些常見模式和需求。由於擴充套件可以執行任何操作,因此本指南無法涵蓋所有可能性。

了解擴充套件的最佳方法是查看您使用的其他擴充套件的撰寫方式,並與其他人討論。在我們的 Discord 聊天室GitHub 討論區 上與其他人討論您的設計想法。

最佳的擴充套件會分享常見的模式,以便任何熟悉使用一個擴充套件的人都不會對另一個擴充套件感到完全陌生。只有在早期進行協作,這才有可能實現。

命名

Flask 擴充套件通常在其名稱中帶有 flask 作為前綴或後綴。如果它包裝了另一個函式庫,則也應包含該函式庫的名稱。這使得搜尋擴充套件變得容易,並使其用途更清晰。

一個通用的 Python 套件建議是,來自套件索引的安裝名稱和 import 陳述式中使用的名稱應相關聯。匯入名稱是小寫,單字之間用底線 (_) 分隔。安裝名稱是小寫或詞首大寫,單字之間用破折號 (-) 分隔。如果它包裝了另一個函式庫,則優先使用與該函式庫名稱相同的大小寫。

以下是一些安裝和匯入名稱的範例

  • Flask-Name 匯入為 flask_name

  • flask-name-lower 匯入為 flask_name_lower

  • Flask-ComboName 匯入為 flask_comboname

  • Name-Flask 匯入為 name_flask

擴充套件類別和初始化

所有擴充套件都需要一個進入點,用於初始化應用程式的擴充套件。最常見的模式是建立一個類別,表示擴充套件的組態和行為,並具有一個 init_app 方法,將擴充套件實例應用於給定的應用程式實例。

class HelloExtension:
    def __init__(self, app=None):
        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        app.before_request(...)

重要的是,應用程式不應儲存在擴充套件上,不要執行 self.app = app。擴充套件應直接存取應用程式的唯一時間是在 init_app 期間,否則應使用 current_app

這允許擴充套件支援應用程式工廠模式,避免在使用者程式碼中的其他位置匯入擴充套件實例時出現循環匯入問題,並使使用不同組態進行測試更容易。

hello = HelloExtension()

def create_app():
    app = Flask(__name__)
    hello.init_app(app)
    return app

在上面,hello 擴充套件實例獨立於應用程式存在。這表示使用者專案中的其他模組可以執行 from project import hello,並在應用程式存在之前在藍圖中使用擴充套件。

Flask.extensions 字典可用於在應用程式上儲存對擴充套件的參考,或特定於應用程式的其他狀態。請注意,這是一個單一命名空間,因此請使用對您的擴充套件而言唯一的名稱,例如不帶 "flask" 前綴的擴充套件名稱。

新增行為

擴充套件可以透過多種方式新增行為。Flask 物件上可用的任何設定方法都可以在擴充套件的 init_app 方法期間使用。

一個常見的模式是使用 before_request() 在每個請求開始時初始化一些資料或連線,然後使用 teardown_request() 在結束時清理它。這可以儲存在 g 上,稍後將詳細討論。

更懶惰的方法是提供一個初始化並快取資料或連線的方法。例如,ext.get_db 方法可以在第一次呼叫時建立資料庫連線,以便不使用資料庫的視圖不會建立連線。

除了在每個視圖之前和之後執行某些操作外,您的擴充套件可能也想要新增一些特定的視圖。在這種情況下,您可以定義一個 Blueprint,然後在 init_app 期間呼叫 register_blueprint(),以將藍圖新增到應用程式。

組態技巧

擴充套件可以有多個層級和來源的組態。您應該考慮擴充套件的哪些部分屬於每個層級和來源。

  • 每個應用程式實例的組態,透過 app.config 值。這是對於應用程式的每個部署都可能合理變更的組態。一個常見的範例是外部資源的 URL,例如資料庫。組態金鑰應以擴充套件的名稱開頭,以便它們不會干擾其他擴充套件。

  • 每個擴充套件實例的組態,透過 __init__ 引數。此組態通常會影響擴充套件的使用方式,使其在每次部署時變更它沒有意義。

  • 每個擴充套件實例的組態,透過實例屬性和裝飾器方法。在建立擴充套件實例後,指派給 ext.value,或使用 @ext.register 裝飾器註冊函式,可能會更符合人體工學。

  • 透過類別屬性的全域組態。變更類別屬性(如 Ext.connection_class)可以自訂預設行為,而無需建立子類別。這可以與每個擴充套件的組態結合,以覆寫預設值。

  • 子類別化和覆寫方法和屬性。使擴充套件本身的 API 成為可以覆寫的內容,為進階自訂提供了非常強大的工具。

Flask 物件本身使用所有這些技巧。

根據您的需求和您想要支援的內容,由您決定哪些組態適合您的擴充套件。

組態不應在應用程式設定階段完成且伺服器開始處理請求後變更。組態是全域的,對其進行的任何變更都不能保證對其他工作程序可見。

請求期間的資料

在撰寫 Flask 應用程式時,g 物件用於在請求期間儲存資訊。例如,教學 將與 SQLite 資料庫的連線儲存為 g.db。擴充套件也可以使用此物件,但需要謹慎。由於 g 是一個單一的全域命名空間,因此擴充套件必須使用唯一的名稱,這些名稱不會與使用者資料衝突。例如,使用擴充套件名稱作為前綴或作為命名空間。

# an internal prefix with the extension name
g._hello_user_id = 2

# or an internal prefix as a namespace
from types import SimpleNamespace
g._hello = SimpleNamespace()
g._hello.user_id = 2

g 中的資料會在應用程式內容中持續存在。當請求內容處於活動狀態時,或當 CLI 命令執行時,應用程式內容處於活動狀態。如果您要儲存應關閉的內容,請使用 teardown_appcontext() 以確保在應用程式內容結束時關閉它。如果它應僅在請求期間有效,或者在請求之外的 CLI 中不會使用,請使用 teardown_request()

視圖和模型

您的擴充套件視圖可能想要與資料庫中的特定模型互動,或與連接到應用程式的其他擴充套件或資料互動。例如,讓我們考慮一個 Flask-SimpleBlog 擴充套件,它與 Flask-SQLAlchemy 搭配使用,以提供 Post 模型和視圖來撰寫和讀取文章。

Post 模型需要子類別化 Flask-SQLAlchemy db.Model 物件,但只有在您建立該擴充套件的實例後,才可以使用該物件,而不是在您的擴充套件定義其視圖時。那麼,在模型存在之前定義的視圖程式碼如何存取模型?

一種方法是使用 基於類別的視圖。在 __init__ 期間,建立模型,然後透過將模型傳遞給視圖類別的 as_view() 方法來建立視圖。

class PostAPI(MethodView):
    def __init__(self, model):
        self.model = model

    def get(self, id):
        post = self.model.query.get(id)
        return jsonify(post.to_json())

class BlogExtension:
    def __init__(self, db):
        class Post(db.Model):
            id = db.Column(primary_key=True)
            title = db.Column(db.String, nullable=False)

        self.post_model = Post

    def init_app(self, app):
        api_view = PostAPI.as_view(model=self.post_model)

db = SQLAlchemy()
blog = BlogExtension(db)
db.init_app(app)
blog.init_app(app)

另一種技巧是使用擴充套件上的屬性,例如上面的 self.post_model。在 init_app 中將擴充套件新增到 app.extensions,然後從視圖存取 current_app.extensions["simple_blog"].post_model

您可能還想要提供基底類別,以便使用者可以提供自己的 Post 模型,該模型符合您的擴充套件期望的 API。因此,他們可以實作 class Post(blog.BasePost),然後將其設定為 blog.post_model

如您所見,這可能會變得有點複雜。不幸的是,這裡沒有完美的解決方案,只有不同的策略和權衡,具體取決於您的需求以及您想要提供的自訂程度。幸運的是,這種資源依賴性並不是大多數擴充套件的常見需求。請記住,如果您需要設計方面的幫助,請在我們的 Discord 聊天室GitHub 討論區 上提問。