Flask程式的基本結構
先來一張Flask程式的基本結構圖:

1. 配置選項檔案
config.py 文 件的内容:
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'hard to guess string'
SQLALCHEMY_COMMIT_ON_TEARDOWN = True
#the database pagination
FLASKY_POSTS_PER_PAGE =
@staticmethod
def init_app(app):
pass
class DevelopmentConfig(Config):
DEBUG = True
SQLALCHEMY_DATABASE_URI = os.environ.get('DEV_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-dev.sqlite')
class TestingConfig(Config):
TESTING = True
SQLALCHEMY_DATABASE_URI = os.environ.get('TEST_DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data-test.sqlite')
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'sqlite:///' + os.path.join(basedir, 'data.sqlite')
config = {
'development': DevelopmentConfig,
'testing': TestingConfig,
'production': ProductionConfig,
'default': DevelopmentConfig
}
為了讓配置方式更靈活且更安全,某些配置可以從環境變量中導入。例如,
SECRET_KEY
的值,這是個敏感資訊,可以在環境中設定,但系統也提供了一個預設值,以防環境中沒有定義。
配置類可以定義 init_app() 類方法,其參數是程式執行個體。在這個方法中,可以執行對目前 環境的配置初始化。現在,基類 Config 中的 init_app() 方法為空。
2. 使用工廠函數,建立程式執行個體
在單個檔案中開發程式很友善,但卻有個很大的缺點,因為程式在全局作用域中建立,是以無法動态修改配置。運作腳本時,程式執行個體已經建立,再修改配置為時已晚。這一點對單元測試尤其重要,因為有時為了提高測試覆寫度,必須在不同的配置環境中運作程式。
這個問題的解決方法是延遲建立程式執行個體,把建立過程移到可顯式調用的工廠函數中。這種方法不僅可以給腳本留出配置程式的時間,還能夠建立多個程式執行個體,這些執行個體有時在測試中非常有用。程式的工廠函數在 app 包的構造檔案中定義。
構造檔案導入了大多數正在使用的 Flask 擴充。由于尚未初始化所需的程式執行個體,是以沒有初始化擴充,建立擴充類時沒有向構造函數傳入參數。create_app() 函數就是程式的工 廠函數,接受一個參數,是程式使用的配置名。配置類在 config.py 檔案中定義,其中儲存的配置可以使用 Flask app.config 配置對象提供的 from_object() 方法直接導入程式。
程式建立并配置好後,就能初始化 擴充了。在之前建立的擴充對象上調用 init_app() 可以完成初始化過程。
說白了,這裡的工廠函數的作用就是,調用者可以通過一個函數調用建立Flask執行個體對象,不要跟随着程式的運作立即就建立Flask執行個體對象。讓調用者有機會做進一步的配置。
from flask import Flask
from flask_bootstrap import Bootstrap
from flask_sqlalchemy import SQLAlchemy
from config import config
bootstrap = Bootstrap()
db = SQLAlchemy()
def create_app(config_name):
app = Flask(__name__)
app.config.from_object(config[config_name])
config[config_name].init_app(app)
bootstrap.init_app(app)
db.init_app(app)
#附加路由和自定義的錯誤頁面
return app
現在工廠函數建立的程式還不完整,因為沒有路由和自定義的錯誤頁面處理程式。
假設在上面增加的路由是下面的代碼:
# add the app route and error handler function
@app.route('/')
def index():
return '<h1>Hello, World!</h1>'
這時的
manage.py
檔案腳本為:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
from flask_script import Manager, Server
from app import create_app
app = create_app(os.getenv('FLASK_CONFIG') or 'default')
manager = Manager(app)
manager.add_command("runserver", Server(host="0.0.0.0", port=, use_debugger=True))
if __name__ == "__main__":
manager.run()
工程的目錄結構為:
這裡仍然有問題,那就是視圖函數在
__init__.py
中,并沒有獨立到
views.py
檔案中。
- 直接粗暴的把
中的綁定路由放到一個__init__.py
的檔案中。這種方案是不可行的,因為路由的綁定使用的是views.py
這個裝飾器,這時的app.route
一定要是一個app
執行個體對象。如果直接把路由綁定寫到Flask
中,在這個檔案中沒有views.py
執行個體對象Flask
。app
- 在
的頭部views.py
一個import
執行個體。注意由于工廠函數的引入,在app
中是沒有Flask對象執行個體的,有的僅僅是一個工廠函數,真正擁有Flask執行個體對象的是外層的__init__.py
,但是這個檔案屬于工程的配置文本,不屬于真正的工程檔案。manage.py
如果,在
中不使用工廠函數,直接建立__init__.py
執行個體對象,方案2從Flask
檔案中__init__.y
是可行的。import app
- 使用藍本,這是标準的解決辦法。
3. 在藍本中實作程式功能
轉換成程式工廠函數的操作讓定義路由變複雜了。在單腳本程式中,程式執行個體存在于全 局作用域中,路由可以直接使用 app.route 修飾器定義。但現在程式在運作時建立,隻有調用 create_app() 之後才能使用 app.route 修飾器。但是create_app的調用者是項目的配置腳本,雖然技術實作上可以在這裡配置路由,但是從項目的結構上說不能在這裡添加路由。
和路由 一樣,自定義的錯誤頁面處理程式也面臨相同的困難,因為錯誤頁面處理程式使用 app. errorhandler 修飾器定義。
藍本和程式類似,也可以定義路由。不同的 是,在藍本中定義的路由處于休眠狀态,直到藍本注冊到程式上後,路由才真正成為程式 的一部分。使用位于全局作用域中的藍本時,定義路由的方法幾乎和單腳本程式一樣。
和程式一樣,藍本可以在單個檔案中定義,也可使用更結構化的方式在包中的多個子產品中 建立。為了獲得最大的靈活性,程式包中建立了一個子包,用于儲存藍本。
藍本的建立
在
app/main/__init__.py
中建立藍本:
from flask import Blueprint
main = Blueprint('main', __name__)
from . import views, errors
通過執行個體化一個
Blueprint
類對象可以建立藍本。這個構造函數有兩個必須指定的參數: 藍本的名字和藍本所在的包或子產品。和程式一樣,大多數情況下第二個參數使用
Python
的
__name__
變量即可。
程式的路由儲存在包裡的
app/main/views.py
子產品中,而錯誤處理程式儲存在
app/main/ errors.py
子產品中。導入這兩個子產品就能把路由和錯誤處理程式與藍本關聯起來。
注意,這 些子產品在腳本的末尾導入,這是為了避免循環導入依賴,因為在
app/main/__init__.py
和
views.py
中還要導入藍本
errors.py
。
main
藍本的注冊
藍本在
app/_init_.py
中的工廠函數 create_app() 中注冊到程式上:
def create_app(config_name):
# create the app
from .main import main as main_blueprint
app.register_blueprint(main_blueprint)
return app
藍本内部路由的配置
app/main/errors.py
檔案藍本中的錯誤處理程式:
from flask import render_template
from . import main
@main.app_errorhandler(404)
def page_not_found(e):
return render_template('404.html'),
@main.app_errorhandler(500)
def internal_server_error(e):
return render_template('500.html'),
在藍本中編寫錯誤處理程式稍有不同,如果使用 errorhandler 修飾器,那麼隻有藍本中的錯誤才能觸發處理程式。要想注冊程式全局的錯誤處理程式,必須使用 app_errorhandler。
app/main/views.py
檔案藍本中定義的程式路由:
from datetime import datetime
from flask import render_template, session, redirect, url_for
from . import main
from .forms import NameForm
from .. import db
from ..models import User
@main.route('/', methods=['GET', 'POST'])
def index():
form = NameForm()
if form.validate_on_submit():
# ...
return redirect(url_for('.index'))
return render_template(
'index.html',
form=form,
name=session.get('name'),
known=session.get('known', False),
current_time=datetime.utcnow())
在藍本中編寫視圖函數主要有兩點不同:
第一:和前面的錯誤處理程式一樣,路由修飾器 由藍本提供;
第二:
url_for()
函數的用法不同。你可能還記得,
url_for()
函數的第一 個參數是路由的端點名,在程式的路由中,預設為視圖函數的名字。例如,在單腳本程式 中,
index()
視圖函數的
URL
可使用
url_for('index')
擷取。
在藍本中就不一樣了,
Flask
會為藍本中的全部端點加上一個命名空間,這樣就可以在不同的藍本中使用相同的端點名定義視圖函數,而不會産生沖突。命名空間就是藍本的名字 (
Blueprint
構造函數的第一個參數),是以視圖函數
index()
注冊的端點名是
main.index
,其 URL 使用
url_for('main.index')
擷取。
url_for()
函數還支援一種簡寫的端點形式,在藍本中可以省略藍本名,例如
url_for('. index')
。在這種寫法中,命名空間是目前請求所在的藍本。這意味着同一藍本中的重定向 可以使用簡寫形式,但跨藍本的重定向必須使用帶有命名空間的端點名。
為了完全修改程式的頁面,表單對象也要移到藍本中,儲存于 app/main/forms.py
子產品。