本文介紹Flask的信號機制,講述信號的用途,並給出建立信號、訂閱信號、發送信號的方法。html
Flask信號(signals, or event hooking)容許特定的發送端通知訂閱者發生了什麼(既然知道發生了什麼,那咱們能夠知道接下來該作什麼了)。python
Flask提供了一些信號(核心信號)且其它的擴展提供更多的信號。信號是用於通知訂閱者,而不該該鼓勵訂閱者修改數據。相關信號請查閱文檔。web
信號依賴於Blinker庫。flask
Flask鉤子(一般出如今藍圖或應用程序現存的方法中,好比一些內置裝飾器,例如before_request
)不須要Blinker庫而且容許你改變請求對象(request
)或者響應對象(response
)。這些改變了應用程序(或者藍圖)的行爲。好比before_request()
裝飾器。安全
信號看起來和鉤子作一樣的事情。然而在工做方式上它們存在不一樣。譬如核心的before_request()
處理程序以特定的順序執行,而且能夠在返回響應以前放棄請求。相比之下,全部的信號處理器是無序執行的,而且不修改任何數據。session
通常來講,鉤子用於改變行爲(好比,身份驗證或錯誤處理),而信號用於記錄事件(好比記錄日誌)。app
由於信號依賴於Blinker庫,請確保已經安裝。函數
若是你要在本身的應用中使用信號,你能夠直接使用Blinker庫。最多見的使用狀況是命名一個自定義的Namespace
的信號。這也是大多數時候推薦的作法:單元測試
pythonfrom blinker import Namespace my_signals = Namespace()
如今你能夠像這樣建立新的信號:測試
pythonmodel_saved = my_signals.signal('model-saved')
這裏使用惟一的信號名而且簡化調試。能夠用name
屬性來訪問信號名。
對擴展開發者:
若是你正在編寫一個Flask擴展,你想優雅地減小缺乏Blinker安裝的影響,你能夠這樣作使用flask.signals.Namespace
類。
你可使用信號的connect()
方法來訂閱信號。第一個參數是信號發出時要調用的函數,第二個參數是可選的,用於肯定信號的發送者。一個信號能夠擁有多個訂閱者。你能夠用disconnect()
方法來退訂信號。
對於全部的核心Flask信號,發送者都是發出信號的應用。當你訂閱一個信號,請確保也提供一個發送者,除非你確實想監聽所有應用的信號。這在你開發一個擴展的時候尤爲正確。
好比這裏有一個用於在單元測試中找出哪一個模板被渲染和傳入模板的變量的助手上下文管理器:
pythonfrom 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)
如今能夠輕鬆地與測試客戶端配對:
pythonwith 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
確保訂閱使用了一個額外的 **extra
參數,這樣當 Flask 對信號引入新參數時你的調用不會失敗。
代碼中,從 with
塊的應用 app
中流出的渲染的全部模板如今會被記錄到templates
變量。不管什麼時候模板被渲染,模板對象的上下文中添加上它。
此外,也有一個方便的助手方法(connected_to()
) ,它容許你臨時地用它本身的上下文管理器把函數訂閱到信號。由於上下文管理器的返回值不能按那種方法給定,則須要把列表做爲參數傳入:
pythonfrom 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)
上面的例子看起來像這樣:
pythontemplates = [] with captured_templates(app, templates, **extra): ... template, context = templates[0]
若是你想要發送信號,你能夠經過調用 send()
方法來達到目的。它接受一個發件者做爲第一個參數和一些可選的被轉發到信號用戶的關鍵字參數:
pythonclass Model(object): ... def save(self): model_saved.send(self)
永遠嘗試選擇一個合適的發送者。若是你有一個發出信號的類,把 self
做爲發送者。若是你從一個隨機的函數發出信號,把current_app._get_current_object()
做爲發送者。
在Blinker 1.1中經過使用新的connect_via()
裝飾器你也可以輕易地訂閱信號:
pythonfrom 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)
template_rendered
信號是Flask核心信號。
當一個模版成功地渲染,這個信號會被髮送。這個信號與模板實例 template
和上下文的詞典(名爲 context
)一塊兒調用。
信號發送:
pythondef _render(template, context, app): """Renders the template and fires the signal""" rv = template.render(context) template_rendered.send(app, template=template, context=context) return rv
訂閱示例:
pythondef 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)
user_logged_in
是Flask-User中定義的信號,當用戶成功登入以後,這個信號會被髮送。
發送信號:
python# Send user_logged_in signal user_logged_in.send(current_app._get_current_object(), user=user)
下面這個例子追蹤登陸次數和登陸IP:
python# This code has not been tested from flask import request from flask_user.signals import user_logged_in @user_logged_in.connect_via(app) def _track_logins(sender, user, **extra): user.login_count += 1 user.last_login_ip = request.remote_addr db.session.add(user) db.session.commit()
信號可讓你在一瞬間安全地訂閱它們。例如,這些臨時的訂閱對測試頗有幫助。使用信號時,不要讓信號訂閱者(接收者)發生異常,由於異常會形成程序中斷。