天天看點

Flask--項目結構Flask程式的基本結構1. 配置選項檔案2. 使用工廠函數,建立程式執行個體

Flask程式的基本結構

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

Flask--項目結構Flask程式的基本結構1. 配置選項檔案2. 使用工廠函數,建立程式執行個體

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()
           

工程的目錄結構為:

Flask--項目結構Flask程式的基本結構1. 配置選項檔案2. 使用工廠函數,建立程式執行個體

這裡仍然有問題,那就是視圖函數在

__init__.py

中,并沒有獨立到

views.py

檔案中。

  1. 直接粗暴的把

    __init__.py

    中的綁定路由放到一個

    views.py

    的檔案中。這種方案是不可行的,因為路由的綁定使用的是

    app.route

    這個裝飾器,這時的

    app

    一定要是一個

    Flask

    執行個體對象。如果直接把路由綁定寫到

    views.py

    中,在這個檔案中沒有

    Flask

    執行個體對象

    app

  2. views.py

    的頭部

    import

    一個

    app

    執行個體。注意由于工廠函數的引入,在

    __init__.py

    中是沒有Flask對象執行個體的,有的僅僅是一個工廠函數,真正擁有Flask執行個體對象的是外層的

    manage.py

    ,但是這個檔案屬于工程的配置文本,不屬于真正的工程檔案。
    如果,在

    __init__.py

    中不使用工廠函數,直接建立

    Flask

    執行個體對象,方案2從

    __init__.y

    檔案中

    import app

    是可行的。
  3. 使用藍本,這是标準的解決辦法。

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

子產品。