关于 Flask 信号的一点小笔记

Flask中的信号及其含义

信号

  • 信号通过发送发生在核心框架的其他地方或 Flask 扩展的动作时的通知来帮助你解耦应用。简而言之,信号允许特定的发送端通知订阅者发生了什么

订阅信号

  • 使用 connect() 方法来订阅信号。该函数的第一个参数是信号发出时要调用的函数,第二个参数可选,用于确定信号的发送端。
  • 使用 disconnect() 方法来退订。
  • 对于所有的核心 Flask 信号,发送端都是发送信号的应用。当你订阅一个信号,请确保也提供一个发送端,除非你确实想监听全部应用的信号。这在你开发一个扩展的时候尤其正确。

创建信号

  • 在定义的 Namespace 中命名信号。这也是大多时候推荐的做法:
1
2
from blinker import Namespace
my_signals = Namespace()

-创建信号:

1
model_saved = mysignals.signal('model-saved')
  • 这里使用唯一的信号名,简化调试。可以用 name 属性来访问信号名。

发送信号

  • 调用 send() 方法,发出信号。 它接受发送端作为第一个参数,和一些推送到信号订阅者的可选关键字参数:
1
2
3
4
class Model(object):
...
def save(self):
model_saved.send(self)
  • 永远尝试选择一个合适的发送端。如果你有一个发出信号的类,把 self 作为发送端。如果你从一个随记的函数发出信号,把 current_app._get_current_object() 作为发送端。

  • 传递代理作为发送端: 永远不要向信号传递 current_app 作为发送端,使用 current_app._get_current_object() 作为替代。这样的原因是, current_app 是一个代理,而不是真正的对象。

信号与 Flask 的请求上下文

  • 信号在接收时,完全支持请求上下文。上下文本地的变量在 request_started 和 request_finished 一贯可用,所以你可以信任 flask.g 和其他需要的东西。

基于装饰器的信号订阅

  • 可以在 Blinker 1.1 中容易地用新的 connect_via() 装饰器订阅信号:
1
2
3
4
5
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)

核心信号

  1. flask.template_rendered:模板渲染成功的时候发送,这个信号与模板实例template、上下文的字典一起用。
1
2
3
4
5
6
7
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)
  1. flask.request_started:建立请求上下文后,在请求处理开始前发送,订阅者可以用 request 之类的标准全局代理访问请求。
1
2
3
4
5
def log_request(sender, **extra):
sender.logger.debug('Request context is set up')

from flask import request_started
request_started.connect(log_request, app)
  1. flask.request_finished:在响应发送给客户端之前发送,可以传递 response 。
1
2
3
4
5
6
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)
  1. flask.got_request_exception:在请求处理中抛出异常时发送,异常本身会通过 exception 传递到订阅的函数。
1
2
3
4
5
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)
  1. flask.request_tearing_down:在请求销毁时发送,它总是被调用,即使发生异常。
1
2
3
4
5
def close_db_connection(sender, **extra):
session.close()

from flask import request_tearing_down
request_tearing_down.connect(close_db_connection, app)
  1. flask.appcontext_tearing_down:在应用上下文销毁时发送,它总是被调用,即使发生异常。
1
2
3
4
5
def close_db_connection(sender, **extra):
session.close()

from flask import request_tearing_down
appcontext_tearing_down.connect(close_db_connection, app)
  1. flask.appcontext_pushed: 这个信号在应用上下文压入栈时发送。发送者是应用对象。这通常在单元测试中为了暂时地钩住信息比较有用。例如这可以用来提前在 g 对象上设置一些资源。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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('/user/me')
assert resp.data == 'username=john'
  1. flask.appcontext_popped: 这个信号在应用上下文弹出栈时发送。发送者是应用对象。这通常在 appcontext_tearing_down 信号发送后发送
  2. flask.message_flashed: 这个信号在应用对象闪现一个消息时发送。消息作为 message 命名参数发送,分类则是 category 参数。
1
2
3
4
5
recorded = []
def record(sender, message, category, **extra):
recorded.append((message, category))
from flask import message_flashed
message_flashed.connect(record, app)