Flask重構-使用工廠方法和藍圖
雖然初步開發的時候可以不用管結構,全部程式碼都塞在一起就好,但是遇到要擴展或是測試的時候問題就出現了,很多潛在的問題在程式擴展的路上會出現,增加很多時間成本。而我自己也想練習一下寫測試,有好的結構才能快速切換測試和開發的環境,剛好藉由自己的這個小project來玩玩看
結構
以下是重構後的資料結構
code
都拆開放在專屬的py
檔案裡了
app
資料夾是放Flask
主程式,也可以換成其他名子app/main
底下放的是views
(update
,delete
,add
之類的url
)app/templates
存放各個頁面的html
app/tests
放測試app
底下放command
(建資料庫,新增帳號密碼之類的)以及models
(放資料庫的models
)/
根目錄放main.py
用來呼叫app
裡的create_app()
來建立app
,這個是工廠方法,下面會提到
工廠方法(Factory pattern)
為了讓程式可以建立多個instance
,以便Unit test
(因為為了提高測試覆蓋度,有時候必須在不同的的設定下執行程式)。單instance
建立後沒辦法再修改設定,所以需要多個instance
解決方法是延遲建立instance,建立過程移至工廠方法,這裡放在app/__init__.py
(官方文件寫得超簡短…用處有限🤦♂️只知道db
要放在create_app()
)
工廠方法要配合藍圖(blueprint)才能建立路由和自訂
404
頁面
import os
import sys
from flask import Blueprint, Flask
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from .config import config
# SQLite URI compatible
WIN = sys.platform.startswith('win')
if WIN:
prefix = 'sqlite:///'
else:
prefix = 'sqlite:////'
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
from .models import db
db.init_app(app)
login_manager = LoginManager(app)
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
from .models import User
user = User.query.get(int(user_id))
return user
login_manager.login_view = 'main.login'
@app.context_processor
def inject_user():
from .models import User
user = User.query.first()
return dict(user=user)
# 註冊藍圖
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
藍圖
- 藍圖: 放在
app/main/__init__.py
- 由於換成工廠模式來建立
instance
,建立後才能使用app.route
,但已經無法定義路由了,也就是在create_app()
裡定義藍圖好定義路由 - 藍圖中定義路由和錯誤處理程式處於休眠狀態,只有註冊(
register
)到應用程式上才成為他的一部分 - 可以在單個檔案中定義,也可以用結構化方式在多個
module
裡建立,為了方便直接在app/main
裡建立一個blueprint
,包含views
和errors
- 定義路由的時候要寫上藍圖的名稱,因為是由藍圖提供,而非
app.route
,這裡是@main.route()
- 使用藍圖時,
url_for()
裡的參數不再是直接寫上路由路徑的名子,例如url_for('index')
。而是要加上藍圖的名稱變為url_for('main.index')
這是為了在不同藍圖中使用同樣的路徑名 - 當同一個藍圖重定向(
redirect
)的時候可以簡化寫法變為url_for('.index')
,跨藍圖的時候就得加上藍圖名
from flask import Blueprint
# Main blueprint
# 兩個必要的參數: 藍圖名稱和所在的包或模塊
main = Blueprint('main', __name__)
# 避免循環依賴
# 因為兩個script都會導入main
from . import views, errors
設定檔
由於不同的階段會有不同的設定以及參數,所以需要設定檔來定義每個階段需要的設定,以便快速切換。例如開發、測試、和部署,都需要不同的設定以及參數,這時有設定檔就可以隨意切換,不須手動調整(自動就是爽)
這裡我是放在app/config.py
,簡單設定一下不同環境的參數
import os
import sys
# SQLite URI compatible
WIN = sys.platform.startswith('win')
if WIN:
prefix = 'sqlite:///'
else:
prefix = 'sqlite:////'
def create_sqlite_uri(db_name):
return prefix + os.path.join(os.path.dirname(__file__), db_name)
class BaseConfig(object):
SECRET_KEY = '你的密碼'
class DevelopmentConfig(BaseConfig):
DEBUG = False
SQLALCHEMY_DATABASE_URI = create_sqlite_uri('watching-history.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
class TestingConfig(BaseConfig):
TESTING = True
SQLALCHEMY_DATABASE_URI = create_sqlite_uri('test.db')
SQLALCHEMY_TRACK_MODIFICATIONS = False
config = {
'development': DevelopmentConfig,
'testing': TestingConfig
}
SECRET_KEY
等一些機密資訊可以用環境變數導入,更安全,為了方便,而且沒有機密,所以就直接寫入
切記不要把密碼以及機密資訊寫在版本控制的設定檔中
上面程式碼中也可以看到SQLALCHEMY_DATABASE_URI
在development
和testing
中是不同的值,這樣可以在不同環境中使用不同的資料庫,以免原本的資料庫被覆寫
啟動
- 重構後在根目錄建立
.flaskenv
讓flask
知道哪個是啟動的py
檔案
FLASK_APP=main.py
# main.py
from app import create_app
app = create_app(config_name='development')
Flask重構-使用工廠方法和藍圖
https://f88083.github.io/2024/01/09/Flask重構-使用工廠方法和藍圖/