1. 程式人生 > >flask實戰-個人博客-使用工廠函數創建程序實例

flask實戰-個人博客-使用工廠函數創建程序實例

blue current commands creat comm 個人 dev ots reat

使用工廠函數創建程序實例

使用藍本還有一個重要的好處,那就是允許使用工廠函數來創建程序實例。在OOP(Object-Oriented Programming,面向對象編程)中,工廠(factory)是指創建其他對象的對象,通常是一個返回其他類的對象的函數或方法,比如我們之前的例子中創建的WTForms驗證器(函數)。在personalBlog程序中,程序實例可以設計為在工廠函數中創建,這個函數返回程序實例app。按照慣例,這個函數被命名為create_app()或make_app()。我們把這個工廠函數稱為程序工廠(Application Factory)--即“生產程序的工廠”,使用它可以在任何地方創建程序實例。

工廠函數使得測試和部署更加方便。我們不必將加載的配置寫死在某處,而是直接在不同的地方按照需要的配置創建程序實例。通過支持創建多個程序實例,工廠函數提供了很大的靈活性。另外,借助工廠函數,我們還可以分離擴展的初始化操作。創建擴展對象的操作可以分離到單獨的模塊,這樣可以有效減少循環依賴的發生。personalBlog程序的工廠函數如下所示:

personalBlog/__init__.py

from flask import Flask
from personalBlog.settings import config

def create_app(config_name=None):
    
if config_name is None: config_name = os.getenv(FLASK_CONFIG, development) app = Flask(personalBlog) app.config.from_object(config[config_name]) app.register_blueprint(auth, url_prefix = /auth) return app

工廠函數接收配置名作為參數,返回創建好的程序實例。如果沒有傳入配置名,我們會從FLASK_CONFIG環境變量獲取,如果沒有獲取到則使用默認值development。

在這個工廠函數中,我們會創建程序實例,然後為其加載配置,註冊在前面創建的藍本,最後返回程序實例。不過,現在的程序實例還沒有執行擴展的初始化操作,後續一步步擴充它。

工廠函數一般在程序包的構造文件中創建,如果你願意,也可以在程序包內新建的模塊來存放,比如factory.py或是app.py。

1、加載配置

工廠函數接收配置名稱作為參數,這允許我們在程序的不同位置傳入不同的配置來創建程序實例。比如,使用工廠函數後,我們可以在測試腳本中使用測試配置來調用工廠函數,創建一個單獨用於測試的程序實例,而不用從某個模塊導入程序實例。

2、初始化擴展

為了完成擴展的初始化操作,我們需要在實例化擴展類時傳入程序實例。但使用工廠函數時,並沒有一個創建好的程序實例可以導入。如果我們把實例化操作放到工廠函數中,那麽我們就沒有一個全局的擴展對象可以使用,比如表示數據庫的db對象。

為了解決這個問題,大部分擴展都提供了一個init_app()方法來支持分離擴展的實例化和初始化操作。現在我們仍然像往常一樣初始化擴展類,但是並不傳入程序實例。這時擴展類實例化的工作可以集中放到extension.py腳本中,如下所示:

personalBlog/extensions.py:擴展類實例化

from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from flask_mail_import Mail
from flask_ckeditor import CKEditor
from flask_moment import Moment

bootstrap = Bootstrap()
db = SQLAlchemy()
moment = Moment()
ckeditor = CKEditor()
mail = Mail()

現在,當我們需要在程序實例中使用擴展對象時,直接從這個extensions模塊導入即可。在工廠函數中,我們導入所有擴展對象,並對其調用init_app()方法,傳入程序實例完成初始化操作:

from personalBlog.extensions import bootstrap, db, moment, ckeditor, mail

def create_app(config_name=None):
    if config_name is None:
        config_name = os.getenv(FLASK_CONFIG, development)

    app = Flask(personalBlog)
    app.config.from_object(config[config_name])

    app.register_blueprint(auth, url_prefix = /auth)

    bootstrap.init_app(app)
    db.init_app(app)
    moment.init_app(app)
    ckeditor.init_app(app) 
    mail.init_app(app)
    
    return app

3、組織工廠函數

除了擴展初始化操作,還有很多處理函數要註冊到程序上,比如錯誤處理函數、上下文處理函數等。雖然藍本也可以註冊全局的處理函數,但是為了便於管理,除了藍本特定的處理函數,這些處理函數一般都放到工廠函數中註冊。

為了避免把工廠函數弄得太長太復雜,我們可以根據類別把這些代碼分離成多個函數,這些函數接收程序實例app作為參數,分別用來為程序實例初始化擴展、註冊藍本、註冊錯誤處理函數、註冊上下文處理函數等一系列操作,如下所示:

personalBlog/__init__.py: 組織工廠函數

from personalBlog.extensions import bootstrap, db, moment, ckeditor, mail, loginManager


def create_app(config_name = None):
    if config_name is None:
        config_name = os.getenv(FLASK_CONFIG, development)

    app = Flask(personalBlog)
    app.config.from_object(config[config_name])

    register_logging(app)  # 註冊日誌處理器
    register_extensions(app)  # 註冊擴展(擴展初始化)
    register_blueprints(app)  # 註冊藍本
    register_commands(app)  # 註冊自定義shell命令
    register_errors(app)  # 註冊錯誤處理函數
    register_shell_context(app)  # 註冊錯誤處理函數
    register_template_context(app)  # 註冊模板上下文處理函數
    return app

def register_logging(app):
    pass  #後續介紹日誌

def register_extensions(app):
    bootstrap.init_app(app)
    db.init_app(app)
    ckeditor.init_app(app)
    mail.init_app(app)
    moment.init_app(app)

def register_blueprints(app):
    app.register_blueprint(auth, url_prefix = /auth)

def register_shell_context(app):
    @app.shell_context_processor
    def make_shell_context():
        return dict(db = db)

def register_template_context(app):
    pass

def register_errors(app):
    @app.errorhandler(400)
    def bad_request(e):
        return render_template(errors/400.html), 400

def register_commands(app):
    pass
這裏的register_*函數的命名只是約定,你也可以使用configure_*或類似的命名形式。另外,你也可以按需要添加或刪除對應的函數。
現在,當工廠函數被調用後。首先創建一個特定配置類的程序實例,然後執行一些列註冊函數為程序實例註冊擴展、藍本、錯誤處理器、上下文處理器、請求處理器。。。在這個程序工廠的加工流水線的盡頭,我們可以得到一個包含所有基本組件的可以直接運行的程序實例。
當使用工廠函數時,因為擴展初始化操作分離,db.create_all()將依賴於程序上下文才能正常執行。執行flask shell命令啟動的python shell會自動激活程序上下文,flask命令也會默認在程序上下文環境下執行,所以目前程序中的db.create_all()方法可以被正確執行。當在其他腳本中直接調用db.create_all(),或是在普通的python shell中調用時,則需要手動激活程序上下文。
4、啟動程序
當使用flask run命令啟動程序時,Flask的自動發現程序實例機制還包含另一種行為:flask會自動從環境變量FLASK_APP的值定義的模塊中尋找名稱為create_app()或make_app()的工廠函數,自動調用工廠函數創建程序實例並運行。因為我們已經在.flaskenv文件中將FLASK_APP設為personalBlog,所以不需要更改任何設置,繼續使用flask run命令即可運行程序:
flask run
技術分享圖片

如果想設置特定的配置名稱,最簡單的方式是通過環境變量FLASK_CONFIG設置。另外,你也可以使用FLASK_APP顯示地指定工廠函數並傳入參數:
FLASK_APP = "personalBlog:create_app(‘development‘)"

為了支持Flask自動從FLASK_APP環境變量對應值指向的模塊或包中發現工廠函數,工廠函數中接收的參數必須是默認參數,即設置了默認值的參數,比如“config_name=None”。
5、current_app
使用工廠函數後,我們會遇到一個問題:對於藍本實例沒有提供,程序實例獨有的屬性和方法應該如何調用呢(比如獲取配置的app.config屬性)?考慮下面的因素:
1-使用工廠函數創建程序實例後,在其他模塊中並沒有一個創建好的程序實例可以讓我們導入使用。
2-使用工廠函數後,程序實例可以在任何地方被創建。你不能固定導入某一個程序實例,因為不同程序實例可能加載不同的配置變量。
解決方法是使用current_app對象,它是一個表示當前程序實例的代理對象。當某個程序實例被創建並運行時,它會自動指向當前運行的程序實例,並把所有操作都轉發到當前的程序實例。比如,當我們需要獲取配置值時,會使用current_app.config,其他方法和屬性也一樣。
current_app是程序上下文全局變量,所以只有在激活了程序上下文之後才可以使用。比如在視圖函數中,或是在視圖函數中調用的函數和對象中。

flask實戰-個人博客-使用工廠函數創建程序實例