New in version 0.6.
Flask 自 0.6 版本開始在內(nèi)部支持信號。信號功能由優(yōu)秀的 blinker 庫提供支持, 如果沒有安裝該庫就無法使用信號功能,但不影響其他功能。
什么是信號?當核心框架的其他地方或另一個 Flask 擴展中發(fā)生動作時,信號通過發(fā)送 通知來幫助你解耦應用。簡言之,信號允許某個發(fā)送者通知接收者有事情發(fā)生了。
Flask 自身有許多信號,其他擴展可能還會帶來更多信號。請記住,信號使用目的是通知 接收者,不應該鼓勵接收者修改數(shù)據(jù)。你會注意到信號的功能與一些內(nèi)建的裝飾器類似( 如 request_started 與 before_request() 非常 相似),但是它們的工作原理不同。例如核心的 before_request() 處理器以一定的順序執(zhí)行,并且可以提前退出請求,返回一個響應。相反,所有的信號處理器是亂序執(zhí)行的,并且不修改任何數(shù)據(jù)。
信號的最大優(yōu)勢是可以安全快速的訂閱。比如,在單元測試中這些臨時訂閱十分有用。 假設(shè)你想知道請求需要渲染哪個模塊,信號可以給你答案。
使用信號的 connect() 方法可以訂閱該信號。該方法的 第一個參數(shù)是當信號發(fā)出時所調(diào)用的函數(shù)。第二個參數(shù)是可選參數(shù),定義一個發(fā)送者。 使用 disconnect() 方法可以退訂信號。
所有核心 Flask 信號的發(fā)送者是應用本身。因此當訂閱信號時請指定發(fā)送者,除非你真的 想要收聽應用的所有信號。當你正在開發(fā)一個擴展時,尤其要注意這點。
下面是一個環(huán)境管理器的輔助工具,可用于在單元測試中辨別哪個模板被渲染了,哪些變量被傳遞給了模板:
from flask import template_rendered
from contextlib import contextmanager
@contextmanager
def captured_templates(app):
recorded = []
def record(sender, template, context, **extra):
recorded.append((template, context))
template_rendered.connect(record, app)
try:
yield recorded
finally:
template_rendered.disconnect(record, app)
上例可以在測試客戶端中輕松使用:
with captured_templates(app) as templates:
rv = app.test_client().get('/')
assert rv.status_code == 200
assert len(templates) == 1
template, context = templates[0]
assert template.name == 'index.html'
assert len(context['items']) == 10
為了使 Flask 在向信號中添加新的參數(shù)時不發(fā)生錯誤,請確保使用一個額外的 **extra 參數(shù)。
在 with 代碼塊中,所有由 app 渲染的模板會被記錄在 templates 變量中。每當有模板被渲染,模板對象及環(huán)境就會追加到變量中。
另外還有一個方便的輔助方法( connected_to() )。它允許臨時把一個使用環(huán)境對象的函數(shù)訂閱到一個信號。因為環(huán)境對象的返回值不能被指定,所以必須把列表作為參數(shù):
from flask import template_rendered
def captured_templates(app, recorded, **extra):
def record(sender, template, context):
recorded.append((template, context))
return template_rendered.connected_to(record, app)
上例可以這樣使用:
templates = []
with captured_templates(app, templates, **extra):
...
template, context = templates[0]
Blinker version 1.1 版本中增加了 connected_to() 方法。
如果相要在你自己的應用中使用信號,那么可以直接使用 blinker 庫。最常見的,也是最推薦的方法是在自定義的 Namespace 中命名信號:
from blinker import Namespace
my_signals = Namespace()
接著可以像這樣創(chuàng)建新的信號:
model_saved = my_signals.signal('model-saved')
信號的名稱應當是唯一的,并且應當簡明以便于調(diào)試??梢酝ㄟ^ name 屬性獲得信號的名稱。
如果你正在編寫一個 Flask 擴展,并且想要妥善處理 blinker 安裝缺失的情況,那么可以使用 flask.signals.Namespace 類。
如果想要發(fā)送信號,可以使用 send() 方法。它的第一個參數(shù)是一個發(fā)送者,其他參數(shù)要發(fā)送給訂閱者的東西,其他參數(shù)是可選的:
class Model(object):
...
def save(self):
model_saved.send(self)
請謹慎選擇發(fā)送者。如果是一個發(fā)送信號的類,請把 self 作為發(fā)送者。如果發(fā)送信號的是一個隨機的函數(shù),那么可以把 current_app._get_current_object() 作為發(fā)送者。
不要把 current_app 作為發(fā)送者傳遞給信號。請使用 current_app._get_current_object() 。因為 current_app 是 一個代理,不是實際的應用對象。
信號在接收時,完全支持請求環(huán)境 。在 request_started 和 request_finished 本地環(huán)境變量 始終可用。因此你可以依賴 flask.g 及其他本地環(huán)境變量。 請注意在 發(fā)送信號 中所述的限制和 request_tearing_down 信號。
Blinker 1.1 版本中你還可以通過使用新的 connect_via() 裝飾器輕松訂閱信號:
from flask import template_rendered
@template_rendered.connect_via(app)
def when_template_rendered(sender, template, context, **extra):
print 'Template %s is rendered with %s' % (template.name, context)
Flask 中有以下信號:
flask.template_rendered
這個信號發(fā)送于一個模板被渲染成功后。信號傳遞的 template 是模板的實例, context 是環(huán)境對象是一個字典。
訂閱示例:
def log_template_renders(sender, template, context, **extra):
sender.logger.debug('Rendering template "%s" with context %s',
template.name or 'string template',
context)
from flask import template_rendered
template_rendered.connect(log_template_renders, app)
flask.request_started
這個信號發(fā)送于請求開始之前,且請求環(huán)境設(shè)置完成之后。因為請求環(huán)境已經(jīng)綁定, 所以訂閱者可以用標準的全局代理,如 request 來操作請求。
訂閱示例:
def log_request(sender, **extra):
sender.logger.debug('Request context is set up')
from flask import request_started
request_started.connect(log_request, app)
flask.request_finished
這個信號發(fā)送于向客戶端發(fā)送響應之前。信號傳遞的 response 為將要發(fā)送的響應。
訂閱示例:
def log_response(sender, response, **extra):
sender.logger.debug('Request context is about to close down. '
'Response: %s', response)
from flask import request_finished
request_finished.connect(log_response, app)
flask.got_request_exception
這個信號發(fā)送于請求進行中發(fā)生異常的時候。它的發(fā)送早于標準異常處理介于。 在調(diào)試模式下,雖然沒有異常處理,但發(fā)生異常時也發(fā)送這個信號。信號傳遞的 exception 是異常對象。
訂閱示例:
def log_exception(sender, exception, **extra):
sender.logger.debug('Got exception during processing: %s', exception)
from flask import got_request_exception
got_request_exception.connect(log_exception, app)
flask.request_tearing_down
這個信號發(fā)送于請求崩潰的時候,不管是否引發(fā)異常。目前,偵聽此信號的函數(shù)在一般崩潰處理器后調(diào)用,但是沒有什么東西可用。
訂閱示例:
def close_db_connection(sender, **extra):
session.close()
from flask import appcontext_tearing_down
request_tearing_down.connect(close_db_connection, app)
在 Flask 版本 0.9 中,這還會傳遞一個 exc 關(guān)鍵字參數(shù),如果這個參數(shù)存在的話。 這個參數(shù)是引發(fā)崩潰的異常的引用。
flask.appcontext_tearing_down 當應用環(huán)境崩潰時發(fā)送這個信號。這個信號總是會發(fā)送,甚至是因為一個異常引發(fā)的 崩潰。偵聽這個信號的函數(shù)會在常規(guī)崩潰處理器后被調(diào)用,但是你無法回饋這個信號。
訂閱示例:
def close_db_connection(sender, **extra):
session.close()
from flask import request_tearing_down
appcontext_tearing_down.connect(close_db_connection, app)
這還會傳遞一個 exc 關(guān)鍵字參數(shù),如果這個參數(shù)存在的話。這個參數(shù)是引發(fā)崩潰的異常的引用。
flask.appcontext_pushed 當一個應用的環(huán)境被壓入時,應用會發(fā)送這個信號。這個信號通常用于在單元測試中臨時鉤接信息。例如可以用于改變 g 對象中現(xiàn)存的資源。
用法示例:
from contextlib import contextmanager
from flask import appcontext_pushed
@contextmanager
def user_set(app, user):
def handler(sender, **kwargs):
g.user = user
with appcontext_pushed.connected_to(handler, app):
yield
在測試代碼中這樣寫:
def test_user_me(self):
with user_set(app, 'john'):
c = app.test_client()
resp = c.get('/users/me')
assert resp.data == 'username=john'
New in version 0.10.
appcontext_popped
當一個應用的環(huán)境被彈出時,應用會發(fā)送這個信號。這個信號通常寫成 appcontext_tearing_down 信號。
New in version 0.10.
flask.message_flashed
當應用閃現(xiàn)一個消息時會發(fā)出這個信號。message參數(shù)是消息內(nèi)容, category 參數(shù)是消息類別。
訂閱示例:
recorded = []
def record(sender, message, category, **extra):
recorded.append((message, category))
from flask import message_flashed
message_flashed.connect(record, app)
New in version 0.10.
? Copyright 2013, Armin Ronacher. Created using Sphinx.