在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ Python/ Flask 方案
應(yīng)用環(huán)境
配置管理
大型應(yīng)用
可插撥視圖
Flask 方案
在 Shell 中使用 Flask
針對高級程序員的前言
使用藍(lán)圖的模塊化應(yīng)用
部署方式
信號
排除應(yīng)用錯誤
模板
請求環(huán)境
掌握應(yīng)用錯誤
測試 Flask 應(yīng)用
前言
教程
安裝
快速上手
Flask 擴(kuò)展

Flask 方案

有一些東西是大多數(shù)網(wǎng)絡(luò)應(yīng)用都會用到的。比如許多應(yīng)用都會使用關(guān)系型數(shù)據(jù)庫和用戶 驗證,在請求之前連接數(shù)據(jù)庫并得到當(dāng)前登錄用戶的信息,在請求之后關(guān)閉數(shù)據(jù)庫連接。

更多用戶貢獻(xiàn)的代碼片斷和方案參見 Flask 代碼片斷歸檔

本章包含內(nèi)容:

  • 大型應(yīng)用
  • 應(yīng)用工廠
  • 應(yīng)用調(diào)度
  • 實現(xiàn) API 異常處理
  • URL 處理器
  • 使用 Distribute 部署
  • 使用 Fabric 部署
  • 在 Flask 中使用 SQLite 3
  • 在 Flask 中使用 SQLAlchemy
  • 上傳文件
  • 緩存
  • 視圖裝飾器
  • 使用 WTForms 進(jìn)行表單驗證
  • 模板繼承
  • 消息閃現(xiàn)
  • 通過 jQuery 使用 AJAX
  • 自定義出錯頁面
  • 惰性載入視圖
  • 在 Flask 中使用 MongoKit
  • 添加一個頁面圖標(biāo)
  • 流內(nèi)容
  • 延遲的請求回調(diào)
  • 添加 HTTP 方法重載
  • 請求內(nèi)容校驗
  • 基于 Celery 的后臺任務(wù)

大型應(yīng)用

對于大型應(yīng)用來說使用包代替模塊是一個好主意。使用包非常簡單。假設(shè)有一個小應(yīng)用如下:

/yourapplication
    /yourapplication.py
    /static
        /style.css
    /templates
        layout.html
        index.html
        login.html
        ...

簡單的包

要把上例中的小應(yīng)用裝換為大型應(yīng)用只要在現(xiàn)有應(yīng)用中創(chuàng)建一個名為 yourapplication 的新文件夾,并把所有東西都移動到這個文件夾內(nèi)。然后把 yourapplication.py 更名為 __init__.py 。(請首先刪除所有 .pyc 文件,否則基本上會出問題)

修改完后應(yīng)該如下例:

/yourapplication
    /yourapplication
        /__init__.py
        /static
            /style.css
        /templates
            layout.html
            index.html
            login.html
            ...

但是現(xiàn)在如何運行應(yīng)用呢?原本的 python yourapplication/__init__.py 無法運行了。因為 Python 不希望包內(nèi)的模塊成為啟動文件。但是這不是一個大問題,只要在 yourapplication 文件夾旁添加一個 runserver.py 文件就可以了,其內(nèi)容如下:

from yourapplication import app
app.run(debug=True)

我們從中學(xué)到了什么?現(xiàn)在我們來重構(gòu)一下應(yīng)用以適應(yīng)多模塊。只要記住以下幾點:

  1. Flask 應(yīng)用對象必須位于 __init__.py文件中。這樣每個模塊就可以安全地導(dǎo)入了,且 __name__變量會解析到正確的包。

  2. 所有視圖函數(shù)(在頂端有 route() 的)必須在 __init__.py 文件中被導(dǎo)入。不是導(dǎo)入對象本身,而是導(dǎo)入視圖模塊。請 在應(yīng)用對象創(chuàng)建之后導(dǎo)入視圖對象。

__init__.py 示例:

from flask import Flask
app = Flask(__name__)

import yourapplication.views

views.py 內(nèi)容如下:

from yourapplication import app

@app.route('/')
def index():
    return 'Hello World!'

最終全部內(nèi)容如下:

/yourapplication
    /runserver.py
    /yourapplication
        /__init__.py
        /views.py
        /static
            /style.css
        /templates
            layout.html
            index.html
            login.html
            ...

回環(huán)導(dǎo)入

回環(huán)導(dǎo)入是指兩個模塊互相導(dǎo)入,本例中我們添加的 views.py 就與 __init__.py 相互依賴。每個 Python 程序員都討厭回環(huán)導(dǎo)入。一般情況下回環(huán)導(dǎo)入是個壞主意,但 在這里一點問題都沒有。原因是我們沒有真正使用 __init__.py 中的視圖,只是 保證模塊被導(dǎo)入,并且我們在文件底部才這樣做。

但是這種方式還是有些問題,因為沒有辦法使用裝飾器。要找到解決問題的靈感請參閱大型應(yīng)用一節(jié)。

使用藍(lán)圖

對于大型應(yīng)用推薦把應(yīng)用分隔為小塊,每個小塊使用藍(lán)圖輔助執(zhí)行。關(guān)于這個主題的介紹 請參閱使用藍(lán)圖的模塊化應(yīng)用一節(jié) 。

應(yīng)用工廠

如果你已經(jīng)在應(yīng)用中使用了包和藍(lán)圖( 使用藍(lán)圖的模塊化應(yīng)用 ),那么還有許多方法可以更 進(jìn)一步地改進(jìn)你的應(yīng)用。常用的方案是導(dǎo)入藍(lán)圖后創(chuàng)建應(yīng)用對象,但是如果在一個函數(shù)中創(chuàng)建對象,那么就可以創(chuàng)建多個實例。

那么這樣做有什么用呢?

  1. 用于測試??梢葬槍Σ煌那闆r使用不同的配置來測試應(yīng)用。

  2. 用于多實例,如果你需要運行同一個應(yīng)用的不同版本的話。當(dāng)然你可以在服務(wù)器上 使用不同配置運行多個相同應(yīng)用,但是如果使用應(yīng)用工廠,那么你可以只使用一個應(yīng)用進(jìn)程而得到多個應(yīng)用實例,這樣更容易操控。

那么如何做呢?

基礎(chǔ)工廠

方法是在一個函數(shù)中設(shè)置應(yīng)用,具體如下:

def create_app(config_filename):
    app = Flask(__name__)
    app.config.from_pyfile(config_filename)

    from yourapplication.model import db
    db.init_app(app)

    from yourapplication.views.admin import admin
    from yourapplication.views.frontend import frontend
    app.register_blueprint(admin)
    app.register_blueprint(frontend)

    return app

這個方法的缺點是在導(dǎo)入時無法在藍(lán)圖中使用應(yīng)用對象。但是你可以在一個請求中使用它。 如何通過配置來訪問應(yīng)用?使用 current_app:

from flask import current_app, Blueprint, render_template
admin = Blueprint('admin', __name__, url_prefix='/admin')

@admin.route('/')
def index():
    return render_template(current_app.config['INDEX_TEMPLATE'])

這里我們在配置中查找模板的名稱。

擴(kuò)展對象初始化時不會綁定到一個應(yīng)用,應(yīng)用可以使用 db.init_app 來設(shè)置擴(kuò)展。 擴(kuò)展對象中不會儲存特定應(yīng)用的狀態(tài),因此一個擴(kuò)展可以被多個應(yīng)用使用。關(guān)于擴(kuò)展設(shè)計的更多信息請參閱 Flask 擴(kuò)展開發(fā)

當(dāng)使用 Flask-SQLAlchemy 時,你的 model.py 可能是這樣的:

from flask.ext.sqlalchemy import SQLAlchemy
# no app object passed! Instead we use use db.init_app in the factory.
db = SQLAlchemy()

# create some models

使用應(yīng)用

因此,要使用這樣的應(yīng)用就必須先創(chuàng)建它。下面是一個運行應(yīng)用的示例 run.py 文件:

from yourapplication import create_app
app = create_app('/path/to/config.cfg')
app.run()

改進(jìn)工廠

上面的工廠函數(shù)還不是足夠好,可以改進(jìn)的地方主要有以下幾點:

  1. 為了單元測試,要想辦法傳入配置,這樣就不必在文件系統(tǒng)中創(chuàng)建配置文件。

  2. 當(dāng)設(shè)置應(yīng)用時從藍(lán)圖調(diào)用一個函數(shù),這樣就可以有機(jī)會修改屬性(如掛接請求前/后 處理器等)。

  3. 如果有必要的話,當(dāng)創(chuàng)建一個應(yīng)用時增加一個 WSGI 中間件。

應(yīng)用調(diào)度

應(yīng)用調(diào)度是在 WSGI 層面組合多個 WSGI 應(yīng)用的過程??梢越M合多個 Flask 應(yīng)用,也可以 組合 Flask 應(yīng)用和其他 WSGI 應(yīng)用。通過這種組合,如果有必要的話,甚至可以在同一個 解釋器中一邊運行 Django ,一邊運行 Flask 。這種組合的好處取決于應(yīng)用內(nèi)部是如何 工作的。

應(yīng)用調(diào)度與模塊化的最大不同在于應(yīng)用調(diào)度中的每個 應(yīng)用是完全獨立的,它們以各自的配置運行,并在 WSGI 層面被調(diào)度。

說明

下面所有的技術(shù)說明和舉例都?xì)w結(jié)于一個可以運行于任何 WSGI 服務(wù)器的 application 對象。對于生產(chǎn)環(huán)境,參見 部署方式 。對于開發(fā)環(huán)境, Werkzeug 提供了一個內(nèi)建開發(fā)服務(wù)器,它使用 werkzeug.serving.run_simple() 來運行:

from werkzeug.serving import run_simple
run_simple('localhost', 5000, application, use_reloader=True)

注意 run_simple 不能用于生產(chǎn)環(huán)境,生產(chǎn)環(huán)境服務(wù)器參見成熟的 WSGI 服務(wù)器 。

為了使用交互調(diào)試器,應(yīng)用和簡單服務(wù)器都應(yīng)當(dāng)處于調(diào)試模式。下面是一個簡單的 “ hello world ”示例,使用了調(diào)試模式和 run_simple:

from flask import Flask
from werkzeug.serving import run_simple

app = Flask(__name__)
app.debug = True

@app.route('/')
def hello_world():
    return 'Hello World!'

if __name__ == '__main__':
    run_simple('localhost', 5000, app,
               use_reloader=True, use_debugger=True, use_evalex=True)

組合應(yīng)用

如果你想在同一個 Python 解釋器中運行多個獨立的應(yīng)用,那么你可以使用 werkzeug.wsgi.DispatcherMiddleware 。其原理是:每個獨立的 Flask 應(yīng)用都 是一個合法的 WSGI 應(yīng)用,它們通過調(diào)度中間件組合為一個基于前綴調(diào)度的大應(yīng)用。

假設(shè)你的主應(yīng)用運行于 / ,后臺接口位于 /backend:

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

根據(jù)子域調(diào)度

有時候你可能需要使用不同的配置來運行同一個應(yīng)用的多個實例。可以把應(yīng)用創(chuàng)建過程 放在一個函數(shù)中,這樣調(diào)用這個函數(shù)就可以創(chuàng)建一個應(yīng)用的實例,具體實現(xiàn)參見應(yīng)用工廠方案。

最常見的做法是每個子域創(chuàng)建一個應(yīng)用,配置服務(wù)器來調(diào)度所有子域的應(yīng)用請求,使用 子域來創(chuàng)建用戶自定義的實例。一旦你的服務(wù)器可以監(jiān)聽所有子域,那么就可以使用一個很簡單的 WSGI 應(yīng)用來動態(tài)創(chuàng)建應(yīng)用了。

WSGI 層是完美的抽象層,因此可以寫一個你自己的 WSGI 應(yīng)用來監(jiān)視請求,并把請求分配給你的 Flask 應(yīng)用。如果被分配的應(yīng)用還沒有創(chuàng)建,那么就會動態(tài)創(chuàng)建應(yīng)用并被登記下來:

from threading import Lock

class SubdomainDispatcher(object):

    def __init__(self, domain, create_app):
        self.domain = domain
        self.create_app = create_app
        self.lock = Lock()
        self.instances = {}

    def get_application(self, host):
        host = host.split(':')[0]
        assert host.endswith(self.domain), 'Configuration error'
        subdomain = host[:-len(self.domain)].rstrip('.')
        with self.lock:
            app = self.instances.get(subdomain)
            if app is None:
                app = self.create_app(subdomain)
                self.instances[subdomain] = app
            return app

    def __call__(self, environ, start_response):
        app = self.get_application(environ['HTTP_HOST'])
        return app(environ, start_response)

調(diào)度器示例:

from myapplication import create_app, get_user_for_subdomain
from werkzeug.exceptions import NotFound

def make_app(subdomain):
    user = get_user_for_subdomain(subdomain)
    if user is None:
        # 如果子域沒有對應(yīng)的用戶,那么還是得返回一個 WSGI 應(yīng)用
        # 用于處理請求。這里我們把 NotFound() 異常作為應(yīng)用返回,
        # 它會被渲染為一個缺省的 404 頁面。然后,可能還需要把
        # 用戶重定向到主頁。
        return NotFound()

    # 否則為特定用戶創(chuàng)建應(yīng)用
    return create_app(user)

application = SubdomainDispatcher('example.com', make_app)

根據(jù)路徑調(diào)度

根據(jù) URL 的路徑調(diào)度非常簡單。上面,我們通過查找 Host 頭來判斷子域,現(xiàn)在只要查找請求路徑的第一個斜杠之前的路徑就可以了:

from threading import Lock
from werkzeug.wsgi import pop_path_info, peek_path_info

class PathDispatcher(object):

    def __init__(self, default_app, create_app):
        self.default_app = default_app
        self.create_app = create_app
        self.lock = Lock()
        self.instances = {}

    def get_application(self, prefix):
        with self.lock:
            app = self.instances.get(prefix)
            if app is None:
                app = self.create_app(prefix)
                if app is not None:
                    self.instances[prefix] = app
            return app

    def __call__(self, environ, start_response):
        app = self.get_application(peek_path_info(environ))
        if app is not None:
            pop_path_info(environ)
        else:
            app = self.default_app
        return app(environ, start_response)

與根據(jù)子域調(diào)度相比最大的不同是:根據(jù)路徑調(diào)度時,如果創(chuàng)建函數(shù)返回 None ,那么就會回落到另一個應(yīng)用:

from myapplication import create_app, default_app, get_user_for_prefix

def make_app(prefix):
    user = get_user_for_prefix(prefix)
    if user is not None:
        return create_app(user)

application = PathDispatcher(default_app, make_app)

實現(xiàn) API 異常處理

在 Flask 上經(jīng)常會執(zhí)行 RESTful API 。開發(fā)者首先會遇到的問題之一是用于 API 的內(nèi)建異常處理不給力,回饋的內(nèi)容不是很有用。

對于非法使用 API ,比使用 abort 更好的解決方式是實現(xiàn)你自己的異常處理類型, 并安裝相應(yīng)句柄,輸出符合用戶格式要求的出錯信息。

簡單的異常類

基本的思路是引入一個新的異常,回饋一個合適的可讀性高的信息、一個狀態(tài)碼和一些可選的負(fù)載,給錯誤提供更多的環(huán)境內(nèi)容。

以下是一個簡單的示例:

from flask import jsonify

class InvalidUsage(Exception):
    status_code = 400

    def __init__(self, message, status_code=None, payload=None):
        Exception.__init__(self)
        self.message = message
        if status_code is not None:
            self.status_code = status_code
        self.payload = payload

    def to_dict(self):
        rv = dict(self.payload or ())
        rv['message'] = self.message
        return rv

這樣一個視圖就可以拋出帶有出錯信息的異常了。另外,還可以通過 payload 參數(shù)以字典的形式提供一些額外的負(fù)載。

注冊一個錯誤處理句柄

現(xiàn)在,視圖可以拋出異常,但是會立即引發(fā)一個內(nèi)部服務(wù)錯誤。這是因為沒有為這個錯誤處理類注冊句柄。句柄增加很容易,例如:

@app.errorhandler(InvalidAPIUsage)
def handle_invalid_usage(error):
    response = jsonify(error.to_dict())
    response.status_code = error.status_code
    return response

在視圖中的用法

以下是如何在視圖中使用該功能:

@app.route('/foo')
def get_foo():
    raise InvalidUsage('This view is gone', status_code=410)

URL 處理器

New in version 0.7.

Flask 0.7 引入了 URL 處理器,其作用是為你處理大量包含相同部分的 URL 。假設(shè)你有 許多 URL 都包含語言代碼,但是又不想在每個函數(shù)中都重復(fù)處理這個語言代碼,那么就可可以使用 URL 處理器。

在與藍(lán)圖配合使用時, URL 處理器格外有用。下面我們分別演示在應(yīng)用中和藍(lán)圖中使用 URL 處理器。

國際化應(yīng)用的 URL

假設(shè)有應(yīng)用如下:

from flask import Flask, g

app = Flask(__name__)

@app.route('/<lang_code>/')
def index(lang_code):
    g.lang_code = lang_code
    ...

@app.route('/<lang_code>/about')
def about(lang_code):
    g.lang_code = lang_code
    ...

上例中出現(xiàn)了大量的重復(fù):必須在每一個函數(shù)中把語言代碼賦值給 g 對象。當(dāng)然,如果使用一個裝飾器可以簡化這個工作。但是,當(dāng)你需要生成由一個函數(shù)指向另一個函數(shù)的 URL 時,還是得顯式地提供語言代碼,相當(dāng)麻煩。

我們使用 url_defaults() 函數(shù)來簡化這個問題。這個函數(shù)可以自動 把值注入到 url_for() 。以下代碼檢查在 URL 字典中是否存在語言代碼, 端點是否需要一個名為 'lang_code' 的值:

@app.url_defaults
def add_language_code(endpoint, values):
    if 'lang_code' in values or not g.lang_code:
        return
    if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
        values['lang_code'] = g.lang_code

URL 映射的 is_endpoint_expecting() 方法可用于檢查端點是否需要提供一個語言代碼。

上例的逆向函數(shù)是 url_value_preprocessor() 。這些函數(shù)在請求匹配后立即根據(jù) URL 的值執(zhí)行代碼。它們可以從 URL 字典中取出值,并把取出的值放在 其他地方:

@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code', None)

這樣就不必在每個函數(shù)中把 lang_code 賦值給 g 了。你還可以作 進(jìn)一步改進(jìn):寫一個裝飾器把語言代碼作為 URL 的前綴。但是更好的解決方式是使用 藍(lán)圖。一旦 'lang_code' 從值的字典中彈出,它就不再傳送給視圖函數(shù)了。精簡后的代碼如下:

from flask import Flask, g

app = Flask(__name__)

@app.url_defaults
def add_language_code(endpoint, values):
    if 'lang_code' in values or not g.lang_code:
        return
    if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
        values['lang_code'] = g.lang_code

@app.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code', None)

@app.route('/<lang_code>/')
def index():
    ...

@app.route('/<lang_code>/about')
def about():
    ...

國際化的藍(lán)圖 URL

因為藍(lán)圖可以自動給所有 URL 加上一個統(tǒng)一的前綴,所以應(yīng)用到每個函數(shù)就非常方便了。 更進(jìn)一步,因為藍(lán)圖 URL 預(yù)處理器不需要檢查 URL 是否真的需要要一個 'lang_code' 參數(shù),所以可以去除 url_defaults())函數(shù)中的邏輯判斷:

from flask import Blueprint, g

bp = Blueprint('frontend', __name__, url_prefix='/<lang_code>')

@bp.url_defaults
def add_language_code(endpoint, values):
    values.setdefault('lang_code', g.lang_code)

@bp.url_value_preprocessor
def pull_lang_code(endpoint, values):
    g.lang_code = values.pop('lang_code')

@bp.route('/')
def index():
    ...

@bp.route('/about')
def about():
    ...

使用 Distribute 部署

distribute 的前身是 setuptools ,它是一個擴(kuò)展庫,通常用于分發(fā) Python 庫和 擴(kuò)展。它的英文名稱的就是“分發(fā)”的意思。它擴(kuò)展了 Python 自帶的一個基礎(chǔ)模塊安裝 系統(tǒng) distutils ,支持多種更復(fù)雜的結(jié)構(gòu),方便了大型應(yīng)用的分發(fā)部署。它的主要特色:

  • 支持依賴 :一個庫或者應(yīng)用可以聲明其所依賴的其他庫的列表。依賴庫將被自動 安裝。
  • 包注冊 :可以在安裝過程中注冊包,這樣就可以通過一個包查詢其他包的信息。 這套系統(tǒng)最有名的功能是“切入點”,即一個包可以定義一個入口,以便于其他包掛接, 用以擴(kuò)展包。
  • 安裝管理 : distribute 中的 easy_install 可以為你安裝其他庫。你也可以使用早晚會替代 easy_install 的 pip ,它除了安裝包還可以做更多的事。

Flask 本身,以及其他所有在 cheeseshop 中可以找到的庫要么是用 distribute 分發(fā)的, 要么是用老的 setuptools 或 distutils 分發(fā)的。

在這里我們假設(shè)你的應(yīng)用名稱是 yourapplication.py ,且沒有使用模塊,而是一個 。 distribute 不支持分發(fā)標(biāo)準(zhǔn)模塊,因此我們不 討論模塊的問題。關(guān)于如何把模塊轉(zhuǎn)換為包的信息參見大型應(yīng)用方案。

使用 distribute 將使發(fā)布更復(fù)雜,也更加自動化。如果你想要完全自動化處理,請同時閱讀使用 Fabric 部署一節(jié)。

基礎(chǔ)設(shè)置腳本

因為你已經(jīng)安裝了 Flask ,所以你應(yīng)當(dāng)已經(jīng)安裝了 setuptools 或 distribute 。如果 沒有安裝,不用怕,有一個 distribute_setup.py 腳本可以幫助你安裝。只要下載這個腳本,并在你的 Python 解釋器中運行就可以了。

標(biāo)準(zhǔn)聲明: 最好使用 virtualenv 。

你的設(shè)置代碼應(yīng)用放在 setup.py 文件中,這個文件應(yīng)當(dāng)位于應(yīng)用旁邊。這個文件名只是 一個約定,但是最好不要改變,因為大家都會去找這個文件。

是的,即使你使用 distribute ,你導(dǎo)入的包也是 setuptools 。 distribute 完全向后兼容于 setuptools ,因此它使用相同的導(dǎo)入名稱。

Flask 應(yīng)用的基礎(chǔ) setup.py 文件示例如下:

from setuptools import setup

setup(
    name='Your Application',
    version='1.0',
    long_description=__doc__,
    packages=['yourapplication'],
    include_package_data=True,
    zip_safe=False,
    install_requires=['Flask']
)

請記住,你必須顯式的列出子包。如果你要 distribute 自動為你搜索包,你可以使用 find_packages 函數(shù):

from setuptools import setup, find_packages

setup(
    ...
    packages=find_packages()
)

大多數(shù) setup 的參數(shù)可以望文生義,但是 include_package_data 和 zip_safe 可以不容易理解。 include_package_data 告訴 distribute 要搜索一個 MANIFEST.in 文件,把文件內(nèi)容所匹配的所有條目作為包數(shù)據(jù)安裝??梢酝ㄟ^使用這個參數(shù)分發(fā) Python 模塊的靜態(tài)文件和模板(參見分發(fā)資源 )。 zip_safe 標(biāo)志可用于強(qiáng)制或防止創(chuàng)建 zip 壓縮包。通常你不會想要把包安裝為 zip 壓縮文件,因為一些工具不支持壓縮文件,而且壓縮文件比較難以調(diào)試。

分發(fā)資源

如果你嘗試安裝上文創(chuàng)建的包,你會發(fā)現(xiàn)諸如 static 或 templates 之類的文件夾沒有被安裝。原因是 distribute 不知道要為你添加哪些文件。你要做的是:在你的 setup.py 文件旁邊創(chuàng)建一個 MANIFEST.in 文件。這個文件列出了所有應(yīng)當(dāng)添加到 tar 壓縮包的文件:

recursive-include yourapplication/templates *
recursive-include yourapplication/static *

不要忘了把 setup 函數(shù)的 include_package_data 參數(shù)設(shè)置為 True !否則即使把內(nèi)容在 MANIFEST.in 文件中全部列出來也沒有用。

聲明依賴

依賴是在 install_requires 參數(shù)中聲明的,這個參數(shù)是一個列表。列表中的每一項都是一個需要在安裝時從 PyPI 獲得的包。缺省情況下,總是會獲得最新版本的包,但你可以指定最高版本和最低版本。示例:

install_requires=[
    'Flask>=0.2',
    'SQLAlchemy>=0.6',
    'BrokenPackage>=0.7,<=1.0'
]

我前面提到,依賴包都從 PyPI 獲得的。但是如果要從別的地方獲得包怎么辦呢?你只要 還是按照上述方法寫,然后提供一個可選地址列表就行了:

dependency_links=['http://example.com/yourfiles']

請確保頁面上有一個目錄列表,且頁面上的鏈接指向正確的 tar 壓縮包。這樣 distribute 就會找到文件了。如果你的包在公司內(nèi)部網(wǎng)絡(luò)上,請?zhí)峁┲赶蚍?wù)器的 URL 。

安裝 / 開發(fā)

要安裝你的應(yīng)用(理想情況下是安裝到一個 virtualenv ),只要運行帶 install 參數(shù)的 setup.py 腳本就可以了。它會將你的應(yīng)用安裝到 virtualenv 的 site-packages 文件夾下,同時下載并安裝依賴:

$ python setup.py install

如果你正開發(fā)這個包,同時也希望相關(guān)依賴被安裝,那么可以使用 develop 來代替:

$ python setup.py develop

這樣做的好處是只安裝一個指向 site-packages 的連接,而不是把數(shù)據(jù)復(fù)制到那里。這樣在開發(fā)過程中就不必每次修改以后再運行 install 了。

使用 Fabric 部署

Fabric 是一個 Python 工具,與 Makefiles 類似,但是能夠在遠(yuǎn)程服務(wù)器上執(zhí)行命令。如果與適當(dāng)?shù)?Python 包( 大型應(yīng)用 )與優(yōu)良的配置( 配置管理 )相結(jié)合那么 Fabric 將是在外部服務(wù)器上部署 Flask 的利器。

在下文開始之前,有幾點需要明確:

  • Fabric 1.0 需要要被安裝到本地。本教程假設(shè)使用的是最新版本的 Fabric 。

  • 應(yīng)用已經(jīng)是一個包,且有一個可用的 setup.py 文件( 使用 Distribute 部署 )。

  • 在下面的例子中,我們假設(shè)遠(yuǎn)程服務(wù)器使用 mod_wsgi 。當(dāng)然,你可以使用你自己 喜歡的服務(wù)器,但是在示例中我們選擇 Apache + mod_wsgi ,因為它們設(shè)置方便, 且在沒有 root 權(quán)限情況下可以方便的重載應(yīng)用。

創(chuàng)建第一個 Fabfile

fabfile 是控制 Fabric 的東西,其文件名為 fabfile.py ,由 fab 命令執(zhí)行。在這個文件中定義的所有函數(shù)都會被視作 fab 子命令。這些命令將會在一個或多個主機(jī)上運行。這些主機(jī)可以在 fabfile 中定義,也可以在命令行中定義。本例將在 fabfile 中 定義主機(jī)。

下面是第一個例子,比較級。它可以把當(dāng)前的源代碼上傳至服務(wù)器,并安裝到一個預(yù)先存在的 virtual 環(huán)境:

from fabric.api import *

# 使用遠(yuǎn)程命令的用戶名
env.user = 'appuser'
# 執(zhí)行命令的服務(wù)器
env.hosts = ['server1.example.com', 'server2.example.com']

def pack():
    # 創(chuàng)建一個新的分發(fā)源,格式為 tar 壓縮包
    local('python setup.py sdist --formats=gztar', capture=False)

def deploy():
    # 定義分發(fā)版本的名稱和版本號
    dist = local('python setup.py --fullname', capture=True).strip()
    # 把 tar 壓縮包格式的源代碼上傳到服務(wù)器的臨時文件夾
    put('dist/%s.tar.gz' % dist, '/tmp/yourapplication.tar.gz')
    # 創(chuàng)建一個用于解壓縮的文件夾,并進(jìn)入該文件夾
    run('mkdir /tmp/yourapplication')
    with cd('/tmp/yourapplication'):
        run('tar xzf /tmp/yourapplication.tar.gz')
        # 現(xiàn)在使用 virtual 環(huán)境的 Python 解釋器來安裝包
        run('/var/www/yourapplication/env/bin/python setup.py install')
    # 安裝完成,刪除文件夾
    run('rm -rf /tmp/yourapplication /tmp/yourapplication.tar.gz')
    # 最后 touch .wsgi 文件,讓 mod_wsgi 觸發(fā)應(yīng)用重載
    run('touch /var/www/yourapplication.wsgi')

上例中的注釋詳細(xì),應(yīng)當(dāng)是容易理解的。以下是 fabric 提供的最常用命令的簡要說明:

  • run - 在遠(yuǎn)程服務(wù)器上執(zhí)行一個命令
  • local - 在本地機(jī)器上執(zhí)行一個命令
  • put - 上傳文件到遠(yuǎn)程服務(wù)器上
  • cd - 在服務(wù)器端改變目錄。必須與 with 語句聯(lián)合使用。

運行 Fabfile

那么如何運行 fabfile 呢?答案是使用 fab 命令。要在遠(yuǎn)程服務(wù)器上部署當(dāng)前版本的代碼可以使用這個命令:

$ fab pack deploy

但是這個命令需要遠(yuǎn)程服務(wù)器上已經(jīng)創(chuàng)建了 /var/www/yourapplication 文件夾,且 /var/www/yourapplication/env 是一個 virtual 環(huán)境。更進(jìn)一步,服務(wù)器上還沒有創(chuàng)建配置文件和 .wsgi 文件。那么,我們?nèi)绾卧谝粋€新的服務(wù)器上創(chuàng)建一個基礎(chǔ)環(huán)境呢?

這個問題取決于你要設(shè)置多少臺服務(wù)器。如果只有一臺應(yīng)用服務(wù)器(多數(shù)情況下),那么在 fabfile 中創(chuàng)建命令有一點多余。當(dāng)然,你可以這么做。這個命令可以稱之為 setup 或 bootstrap 。在使用命令時顯式傳遞服務(wù)器名稱:

$ fab -H newserver.example.com bootstrap

設(shè)置一個新服務(wù)器大致有以下幾個步驟:

  1. 在 /var/www 創(chuàng)建目錄結(jié)構(gòu):
$ mkdir /var/www/yourapplication
$ cd /var/www/yourapplication
$ virtualenv --distribute env
  1. 上傳一個新的 application.wsgi 文件和應(yīng)用配置文件(如 application.cfg ) 到服務(wù)器上。

  2. 創(chuàng)建一個新的用于 yourapplication 的 Apache 配置并激活它。要確保激活 .wsgi 文件變動監(jiān)視,這樣在 touch 的時候可以自動重載應(yīng)用。( 更多信息參見 mod_wsgi (Apache)

現(xiàn)在的問題是: application.wsgi 和 application.cfg 文件從哪里來?

WSGI 文件

WSGI 文件必須導(dǎo)入應(yīng)用,并且還必須設(shè)置一個環(huán)境變量用于告訴應(yīng)用到哪里去搜索配置。 示例:

import os
os.environ['YOURAPPLICATION_CONFIG'] = '/var/www/yourapplication/application.cfg'
from yourapplication import app

應(yīng)用本身必須像下面這樣初始化自己才會根據(jù)環(huán)境變量搜索配置:

app = Flask(__name__)
app.config.from_object('yourapplication.default_config')
app.config.from_envvar('YOURAPPLICATION_CONFIG')

這個方法在配置管理一節(jié)已作了詳細(xì)的介紹。

配置文件

上文已談到,應(yīng)用會根據(jù) YOURAPPLICATION_CONFIG 環(huán)境變量找到正確的配置文件。 因此我們應(yīng)當(dāng)把配置文件放在應(yīng)用可以找到的地方。在不同的電腦上配置文件是不同的, 所以一般我們不對配置文件作版本處理。

一個流行的方法是在一個獨立的版本控制倉庫為不同的服務(wù)器保存不同的配置文件,然后 在所有服務(wù)器進(jìn)行檢出。然后在需要的地方使用配置文件的符號鏈接(例如: /var/www/yourapplication )。

不管如何,我們這里只有一到兩臺服務(wù)器,因此我們可以預(yù)先手動上傳配置文件。

第一次部署

現(xiàn)在我們可以進(jìn)行第一次部署了。我已經(jīng)設(shè)置好了服務(wù)器,因此服務(wù)器上應(yīng)當(dāng)已經(jīng)有了 virtual 環(huán)境和已激活的 apache 配置。現(xiàn)在我們可以打包應(yīng)用并部署它了:

$ fab pack deploy

Fabric 現(xiàn)在會連接所有服務(wù)器并運行 fabfile 中的所有命令。首先它會打包應(yīng)用得到一個 tar 壓縮包。然后會執(zhí)行分發(fā),把源代碼上傳到所有服務(wù)器并安裝。感謝 setup.py 文件,所需要的依賴庫會自動安裝到 virtual 環(huán)境。

下一步

在前文的基礎(chǔ)上,還有更多的方法可以全部署工作更加輕松:

  • 創(chuàng)建一個初始化新服務(wù)器的 bootstrap 命令。它可以初始化一個新的 virtual 環(huán)境、正確設(shè)置 apache 等等。
  • 把配置文件放入一個獨立的版本庫中,把活動配置的符號鏈接放在適當(dāng)?shù)牡胤健?/li>
  • 還可以把應(yīng)用代碼放在一個版本庫中,在服務(wù)器上檢出最新版本后安裝。這樣你可以方便的回滾到老版本。
  • 掛接測試功能,方便部署到外部服務(wù)器進(jìn)行測試。 使用 Fabric 是一件有趣的事情。你會發(fā)現(xiàn)在電腦上打出 fab deploy 是非常神奇的。 你可以看到你的應(yīng)用被部署到一個又一個服務(wù)器上。

在 Flask 中使用 SQLite 3

在 Flask 中,你可以方便的按需打開數(shù)據(jù)庫連接,并且在環(huán)境解散時關(guān)閉這個連接( 通常是請求結(jié)束的時候)。

以下是一個在 Flask 中使用 SQLite 3 的例子:

import sqlite3
from flask import g

DATABASE = '/path/to/database.db'

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = connect_to_database()
    return db

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

為了使用數(shù)據(jù)庫,所有應(yīng)用都必須準(zhǔn)備好一個處于激活狀態(tài)的環(huán)境。使用 get_db 函數(shù)可以得到數(shù)據(jù)庫連接。當(dāng)環(huán)境解散時,數(shù)據(jù)庫連接會被切斷。

注意:如果你使用的是 Flask 0.9 或者以前的版本,那么你必須使用 flask._app_ctx_stack.top ,而不是 g 。因為 flask.g 對象是綁定到請求的,而不是應(yīng)用環(huán)境。

示例:

@app.route('/')
def index():
    cur = get_db().cursor()
    ...

Note 請記住,解散請求和應(yīng)用環(huán)境的函數(shù)是一定會被執(zhí)行的。即使請求前處理器執(zhí)行失敗或根本沒有執(zhí)行,解散函數(shù)也會被執(zhí)行。因此,我們必須保證在關(guān)閉數(shù)據(jù)庫連接之前 數(shù)據(jù)庫連接是存在的。

按需連接

上述方式(在第一次使用時連接數(shù)據(jù)庫)的優(yōu)點是只有在真正需要時才打開數(shù)據(jù)庫連接。 如果你想要在一個請求環(huán)境之外使用數(shù)據(jù)庫連接,那么你可以手動在 Python 解釋器打開應(yīng)用環(huán)境:

with app.app_context():
    # now you can use get_db()

簡化查詢

現(xiàn)在,在每個請求處理函數(shù)中可以通過訪問 g.db 來得到當(dāng)前打開的數(shù)據(jù)庫連接。為了簡化 SQLite 的使用,這里有一個有用的行工廠函數(shù)。該函數(shù)會轉(zhuǎn)換每次從數(shù)據(jù)庫返回的 結(jié)果。例如,為了得到字典類型而不是元組類型的返回結(jié)果,可以這樣:

def make_dicts(cursor, row):
    return dict((cur.description[idx][0], value)
                for idx, value in enumerate(row))

db.row_factory = make_dicts

或者更簡單的:

db.row_factory = sqlite3.Row

此外,把得到游標(biāo),執(zhí)行查詢和獲得結(jié)果組合成一個查詢函數(shù)不失為一個好辦法:

def query_db(query, args=(), one=False):
    cur = get_db().execute(query, args)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv

上述的方便的小函數(shù)與行工廠聯(lián)合使用與使用原始的數(shù)據(jù)庫游標(biāo)和連接相比要方便多了。

可以這樣使用上述函數(shù):

for user in query_db('select * from users'):
    print user['username'], 'has the id', user['user_id']

只需要得到單一結(jié)果的用法:

user = query_db('select * from users where username = ?',
                [the_username], one=True)
if user is None:
    print 'No such user'
else:
    print the_username, 'has the id', user['user_id']

如果要給 SQL 語句傳遞參數(shù),請在語句中使用問號來代替參數(shù),并把參數(shù)放在一個列表中 一起傳遞。不要用字符串格式化的方式直接把參數(shù)加入 SQL 語句中,這樣會給應(yīng)用帶來 SQL 注入的風(fēng)險。

初始化模式

關(guān)系數(shù)據(jù)庫是需要模式的,因此一個應(yīng)用常常需要一個 schema.sql 文件來創(chuàng)建 數(shù)據(jù)庫。因此我們需要使用一個函數(shù)來其于模式創(chuàng)建數(shù)據(jù)庫。下面這個函數(shù)可以完成這個任務(wù):

def init_db():
    with app.app_context():
        db = get_db()
        with app.open_resource('schema.sql', mode='r') as f:
            db.cursor().executescript(f.read())
        db.commit()

可以使用上述函數(shù)在 Python 解釋器中創(chuàng)建數(shù)據(jù)庫:

>>> from yourapplication import init_db
>>> init_db()

在 Flask 中使用 SQLAlchemy

許多人喜歡使用 SQLAlchemy 來訪問數(shù)據(jù)庫。建議在你的 Flask 應(yīng)用中使用包來代替 模塊,并把模型放入一個獨立的模塊中(參見大型應(yīng)用 )。雖然這不是必須的,但是很有用。

有四種 SQLAlchemy 的常用方法,下面一一道來:

Flask-SQLAlchemy 擴(kuò)展

因為 SQLAlchemy 是一個常用的數(shù)據(jù)庫抽象層,并且需要一定的配置才能使用,因此我們?yōu)槟阕隽艘粋€處理 SQLAlchemy 的擴(kuò)展。如果你需要快速的開始使用 SQLAlchemy ,那么推薦你使用這個擴(kuò)展。

你可以從 PyPI 下載 Flask-SQLAlchemy 。

聲明

SQLAlchemy 中的聲明擴(kuò)展是使用 SQLAlchemy 的最新方法,它允許你像 Django 一樣, 在一個地方定義表和模型然后到處使用。除了以下內(nèi)容,我建議你閱讀聲明的官方文檔。

以下是示例 database.py 模塊:

from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()

def init_db():
    # 在這里導(dǎo)入定義模型所需要的所有模塊,這樣它們就會正確的注冊在
    # 元數(shù)據(jù)上。否則你就必須在調(diào)用 init_db() 之前導(dǎo)入它們。
    import yourapplication.models
    Base.metadata.create_all(bind=engine)

要定義模型的話,只要繼承上面創(chuàng)建的 Base 類就可以了。你可能會奇怪這里為什么不用理會線程(就像我們在 SQLite3 的例子中一樣使用 g 對象)。 原因是 SQLAlchemy 已經(jīng)用 scoped_session 為我們做好了此 類工作。

如果要在應(yīng)用中以聲明方式使用 SQLAlchemy ,那么只要把下列代碼加入應(yīng)用模塊就可以了。 Flask 會自動在請求結(jié)束時或者應(yīng)用關(guān)閉時刪除數(shù)據(jù)庫會話:

from yourapplication.database import db_session

@app.teardown_appcontext
def shutdown_session(exception=None):
    db_session.remove()

以下是一個示例模型(放入 models.py 中):

from sqlalchemy import Column, Integer, String
from yourapplication.database import Base

class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50), unique=True)
    email = Column(String(120), unique=True)

    def __init__(self, name=None, email=None):
        self.name = name
        self.email = email

    def __repr__(self):
        return '<User %r>' % (self.name)

可以使用 init_db 函數(shù)來創(chuàng)建數(shù)據(jù)庫:

>>> from yourapplication.database import init_db
>>> init_db()

在數(shù)據(jù)庫中插入條目示例:

>>> from yourapplication.database import db_session
>>> from yourapplication.models import User
>>> u = User('admin', 'admin@localhost')
>>> db_session.add(u)
>>> db_session.commit()

查詢很簡單:

>>> User.query.all()
[<User u'admin'>]
>>> User.query.filter(User.name == 'admin').first()
<User u'admin'>

人工對象關(guān)系映射

人工對象關(guān)系映射相較于上面的聲明方式有優(yōu)點也有缺點。主要區(qū)別是人工對象關(guān)系映射 分別定義表和類并映射它們。這種方式更靈活,但是要多些代碼。通常,這種方式與聲明 方式一樣運行,因此請確保把你的應(yīng)用在包中分為多個模塊。

示例 database.py 模塊:

from sqlalchemy import create_engine, MetaData
from sqlalchemy.orm import scoped_session, sessionmaker

engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
metadata = MetaData()
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
def init_db():
    metadata.create_all(bind=engine)

就像聲明方法一樣,你需要在請求后或者應(yīng)用環(huán)境解散后關(guān)閉會話。把以下代碼放入你的應(yīng)用模塊:

from yourapplication.database import db_session

@app.teardown_appcontext
def shutdown_session(exception=None):
    db_session.remove()

以下是一個示例表和模型(放入 models.py 中):

from sqlalchemy import Table, Column, Integer, String
from sqlalchemy.orm import mapper
from yourapplication.database import metadata, db_session

class User(object):
    query = db_session.query_property()

    def __init__(self, name=None, email=None):
        self.name = name
        self.email = email

    def __repr__(self):
        return '<User %r>' % (self.name)

users = Table('users', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String(50), unique=True),
    Column('email', String(120), unique=True)
)
mapper(User, users)

查詢和插入與聲明方式的一樣。

SQL 抽象層

如果你只需要使用數(shù)據(jù)庫系統(tǒng)(和 SQL )抽象層,那么基本上只要使用引擎:

from sqlalchemy import create_engine, MetaData

engine = create_engine('sqlite:////tmp/test.db', convert_unicode=True)
metadata = MetaData(bind=engine)

然后你要么像前文中一樣在代碼中聲明表,要么自動載入它們:

users = Table('users', metadata, autoload=True)

可以使用 insert 方法插入數(shù)據(jù)。為了使用事務(wù),我們必須先得到一個連接:

>>> con = engine.connect()
>>> con.execute(users.insert(), name='admin', email='admin@localhost')

SQLAlchemy 會自動提交。

可以直接使用引擎或連接來查詢數(shù)據(jù)庫:

>>> users.select(users.c.id == 1).execute().first()
(1, u'admin', u'admin@localhost')

查詢結(jié)果也是類字典元組:

>>> r = users.select(users.c.id == 1).execute().first()
>>> r['name']
u'admin'

你也可以把 SQL 語句作為字符串傳遞給 execute() 方法:

>>> engine.execute('select * from users where id = :1', [1]).first()
(1, u'admin', u'admin@localhost')

關(guān)于 SQLAlchemy 的更多信息請移步其官方網(wǎng)站 。

上傳文件

是的,這里要談的是一個老問題:文件上傳。文件上傳的基本原理實際上很簡單,基本上是:

  1. 一個帶有 enctype=multipart/form-data 的
    標(biāo)記,標(biāo)記中含有一個
  2. 應(yīng)用通過請求對象的 files 字典來訪問文件。
  3. 使用文件的 save() 方法把文件永久 地保存在文件系統(tǒng)中。

簡介

讓我們從一個基本的應(yīng)用開始,這個應(yīng)用上傳文件到一個指定目錄,并把文件顯示給用戶。 以下是應(yīng)用的引導(dǎo)代碼:

import os
from flask import Flask, request, redirect, url_for
from werkzeug import secure_filename

UPLOAD_FOLDER = '/path/to/the/uploads'
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

首先我們導(dǎo)入了一堆東西,大多數(shù)是淺顯易懂的。werkzeug.secure_filename() 會在稍后解釋。UPLOAD_FOLDER 是上傳文件要儲存的目錄,ALLOWED_EXTENSIONS 是允許上傳的文件擴(kuò)展名的集合。接著我們給應(yīng)用手動添加了一個 URL 規(guī)則。一般現(xiàn)在不會做這個,但是為什么這么做了呢?原因是我們需要服務(wù)器(或我們的開發(fā)服務(wù)器)為我們提供 服務(wù)。因此我們只生成這些文件的 URL 的規(guī)則。

為什么要限制文件件的擴(kuò)展名呢?如果直接向客戶端發(fā)送數(shù)據(jù),那么你可能不會想讓用戶上傳任意文件。否則,你必須確保用戶不能上傳 HTML 文件,因為 HTML 可能引起 XSS 問題(參見跨站腳本攻擊(XSS) )。如果服務(wù)器可以執(zhí)行 PHP 文件,那么還必須確保不允許上傳 .php 文件。但是誰又會在服務(wù)器上安裝 PHP 呢,對不? :)

下一個函數(shù)檢查擴(kuò)展名是否合法,上傳文件,把用戶重定向到已上傳文件的 URL:

def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS

@app.route('/', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        file = request.files['file']
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
            return redirect(url_for('uploaded_file',
                                    filename=filename))
    return '''
    <!doctype html>
    <title>Upload new File</title>
    <h1>Upload new File</h1>
    <form action="" method=post enctype=multipart/form-data>
      <p><input type=file name=file>
         <input type=submit value=Upload>
    </form>
    '''

那么 secure_filename() 函數(shù)到底是有什么用?有一條原則是“ 永遠(yuǎn)不要信任用戶輸入”。這條原則同樣適用于已上傳文件的文件名。所有提交的表單數(shù)據(jù) 可能是偽造的,文件名也可以是危險的。此時要謹(jǐn)記:在把文件保存到文件系統(tǒng)之前總是要 使用這個函數(shù)對文件名進(jìn)行安檢。

進(jìn)一步說明

你可以會好奇 secure_filename() 做了哪些工作,如果不使用 它會有什么后果。假設(shè)有人把下面的信息作為 filename 傳遞給你的應(yīng)用:

filename = "../../../../home/username/.bashrc"

假設(shè) ../ 的個數(shù)是正確的,你會把它和 UPLOAD_FOLDER 結(jié)合在一起,那么用戶 就可能有能力修改一個服務(wù)器上的文件,這個文件本來是用戶無權(quán)修改的。這需要了解 應(yīng)用是如何運行的,但是請相信我,黑客都是病態(tài)的 :)

現(xiàn)在來看看函數(shù)是如何工作的:

>>> secure_filename('../../../../home/username/.bashrc')
'home_username_.bashrc'

現(xiàn)在還剩下一件事:為已上傳的文件提供服務(wù)。 Flask 0.5 版本開始我們可以使用一個 函數(shù)來完成這個任務(wù):

from flask import send_from_directory

@app.route('/uploads/<filename>')
def uploade
下一篇:部署方式