Flask Web Development —— 大型應用程序結構(上)

雖然小型web應用程序用單個腳本能夠很方便,但這種方法卻不能很好地擴展。隨着應用變得複雜,在單個大的源文件中處理會變得問題重重。html

與大多數其餘web框架不一樣,Flask對大型項目沒有特定的組織方式;應用程序的結構徹底交給開發人員本身決定。在這一章,提出一個可能的方式來組織管理一個大型應用程序的包和模塊。這種結構將用於書中其他的示例中。python

一、項目結構

示例7-1展現基本Flask應用程序結構web

示例7-1:基本多文件Flask應用結構sql

|-flasky
  |-app/
    |-templates/
    |-static/
    |-main/
      |-__init__.py
      |-errors.py
      |-forms.py
      |-views.py
    |-__init__.py
    |-email.py
    |-models.py
  |-migrations/
  |-tests/
    |-__init__.py
    |-test*.py
  |-venv/
  |-requirements.txt
  |-config.py
  |-manage.py

這個結構有四個頂層目錄:數據庫

  • Flask應用通常放置在名爲app的目錄下。flask

  • migrations目錄包含數據庫遷移腳本,這和以前說的同樣。bootstrap

  • 單元測試放置在test目錄下安全

  • venv目錄包含Python虛擬環境,這和以前說的也是同樣的。session

還有一些新的文件:app

  • requirements.txt列出一些依賴包,這樣就能夠很容易的在不一樣的計算機上部署一個相同的虛擬環境。

  • config.py存儲了一些配置設置。

  • manage.py用於啓動應用程序和其餘應用程序任務。

爲了幫助你徹底理解這個結構,下面會描述將hello.py應用改成符合這一結構的整個流程。

二、配置選項

應用程序一般須要幾個配置設置。最好的例子就是在開發過程當中須要使用不一樣的數據庫,測試,生產環境,這樣他們能夠作到互不干擾。

咱們可使用配置類的層次結構來代替hello.py中的簡單類字典結構配置。示例7-2展現了config.py文件。

示例7-2. 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
    FLASKY_MAIL_SUBJECT_PREFIX = '[Flasky]'
    FLASKY_MAIL_SENDER = 'Flasky Admin <flasky@example.com>' 
    FLASKY_ADMIN = os.environ.get('FLASKY_ADMIN')
    
    @staticmethod
    def init_app(app): 
        pass

class DevelopmentConfig(Config): 
    DEBUG = True

    MAIL_SERVER = 'smtp.googlemail.com'
    MAIL_PORT = 587
    MAIL_USE_TLS = True
    MAIL_USERNAME = os.environ.get('MAIL_USERNAME')
    MAIL_PASSWORD = os.environ.get('MAIL_PASSWORD') 
    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
}

Config基類包含一些相同配置;不一樣的子類定義不一樣的配置。額外配置能夠在須要的時候在加入。

爲了讓配置更靈活更安全,一些設置能夠從環境變量中導入。例如,SECRET_KEY,因爲它的敏感性,能夠在環境中設置,但若是環境中沒有定義就必須提供一個默認值。

在三個配置中SQLALCHEMY_DATABASE_URI變量能夠分配不一樣的值。這樣應用程序能夠在不一樣的配置下運行,每一個可使用不一樣的數據庫。

配置類能夠定義一個將應用程序實例做爲參數的init_app()靜態方法。這裏特定於配置的初始化是能夠執行的。這裏Config基類實現一個空init_app()方法。

在配置腳本的底部,這些不一樣的配置是註冊在配置字典中。將其中一個配置(開發配置)註冊爲默認配置。

三、應用程序包

應用程序包放置了全部應用程序代碼、模板和靜態文件。它被簡單的稱爲app,也能夠給定一個特定於應用的名稱(若是須要的話)。templatesstatic目錄是應用的一部分,所以這兩個目錄應該放置在app中。數據庫模型和電子郵件支持功能也要置入到這個包中,每一個都以app/models.pyapp/email.py形式存入本身的模塊當中。

3.一、使用一個應用程序工廠

在單個文件中建立應用程序的方式很是方便,可是它有一個大缺點。由於應用程序建立在全局範圍,沒有辦法動態的適應應用配置的更改:腳本運行時,應用程序實例已經建立,因此它已經來不及更改配置。對於單元測試這是特別重要的,由於有時須要在不一樣的配置下運行應用程序來得到更好的測試覆蓋率。

解決這一問題的方法就是將應用程序放入一個工廠函數中來延遲建立,這樣就能夠從腳本中顯式的調用。

這不只給腳本充足的時間來設置配置,也能用於建立多個應用程序實例——一些在測試過程當中很是有用的東西。被定義在app包的構造函數中的應用程序工廠函數會在示例7-3中展現。

這個構造函數導入大部分當前須要使用的擴展,但由於沒有應用程序實例初始化它們,它能夠被建立但不初始化經過不傳遞參數給它們的構造函數。create_app()即應用程序工廠函數,須要傳入用於應用程序的配置名。配置中的設置被保存在config.py中的一個類中,可使用Flask的app.config配置對象的from_object()方法來直接導入。配置對象能夠經過對象名從config字典中選出。一旦應用程序被建立且配置好,擴展就能夠被初始化。調用擴展裏的init_app()以前先建立並完成初始化工做。

示例7-3. app/ _init__.py:應用程序包構造函數_

from flask import Flask, render_template 
from flask.ext.bootstrap import Bootstrap 
from flask.ext.mail import Mail
from flask.ext.moment import Moment
from flask.ext.sqlalchemy import SQLAlchemy 
from config import config

bootstrap = Bootstrap()
mail = Mail()
moment = Moment()
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)
    mail.init_app(app)
    moment.init_app(app)
    db.init_app(app)

    # attach routes and custom error pages here
    
    return app

工廠函數返回建立的應用程序實例,可是請注意,在當前狀態下使用工廠函數建立的應用程序是不完整的,由於它們沒有路由和自定義錯誤頁面處理程序。這是下一節的主題。

3.二、在藍圖中實現應用程序的功能

應用程序工廠的轉化工做引出了路由的複雜化。在單腳本應用中,應用程序實例是全局的,因此能夠很容易地使用app.route裝飾器定義路由。可是如今應用程序在運行時建立,app.route裝飾器只有在create_app()調用後纔開始存在,這就太遲了。就像路由那樣,這些經過app.errorhandler裝飾器定義的自定義錯誤頁面處理程序也存在一樣的問題。

幸運的是Flask使用藍圖來提供一個更好的解決方案。一個藍圖就相似於一個能夠定義路由的應用程序。不一樣的是,和路由相關聯的藍圖都在休眠狀態,只有當藍圖在應用中被註冊後,此時的路由纔會成爲它的一部分。使用定義在全局做用域下的藍圖,定義應用程序的路由就幾乎能夠和單腳本應用程序同樣簡單了。

和應用程序同樣,藍圖能夠定義在一個文件或一個包中與多個模塊一塊兒建立更結構化的方式。爲了追求最大的靈活性,能夠在應用程序包中建立子包來持有藍圖。示例7-4展現了建立藍圖的構造函數。

示例7-4. 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/init.py腳本的底部導入模塊要避免循環依賴,由於view.pyerrors.py都須要導入main藍圖。

藍圖和應用程序同樣註冊在create_app()工廠函數中,如示例7-5所示。

示例7-5. app/ _init__.py:藍圖註冊_

def create_app(config_name): 
    # ...
    from .main import main as main_blueprint 
    app.register_blueprint(main_blueprint)

    return app

示例7-6展現了錯誤處理。

示例7-6. 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'), 404

@main.app_errorhandler(500) 
def internal_server_error(e):
    return render_template('500.html'), 500

在藍圖中寫錯誤處理的不一樣之處是,若是使用了errorhandler裝飾器,則只會調用在藍圖中引發的錯誤處理。而應用程序範圍內的錯誤處理則必須使用app_errorhandler

示例7-7展現了被更新在藍圖中的應用程序路由。

示例7-7. 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()函數的使用。你可能會回想,該函數的第一個參數爲路由節點名,它給基於應用程序的路由指定默認視圖函數。例如,單腳本應用程序中的index()視圖函數的URL能夠經過url_for('index')來得到。

不一樣的是Flask名稱空間適用於來自藍圖的全部節點,這樣多個藍圖可使用相同節點定義視圖函數而不會產生衝突。名稱空間就是藍圖名(Blueprint構造函數中的第一個參數),因此index()視圖函數註冊爲main.index且它的URL能夠經過url_for('main.index')得到。

在藍圖中,url_for()函數一樣支持更短格式的節點,省略藍圖名,例如url_for('.index')。有了這個,就能夠這樣使用當前請求的藍圖了。這實際意味着相同藍圖內的重定向可使用更短的形式,若是重定向跨藍圖則必須使用帶名稱空間的節點名。

完成了應用程序頁面更改,表單對象也保存在app/main/forms.py模塊中的藍圖裏面。

相關文章
相關標籤/搜索