信号¶
信号是一种轻量级方法,用于在应用程序和每个请求的生命周期中通知订阅者某些事件。当事件发生时,它会发出呼叫每个订阅者的信号。
信号由 Blinker 类库。有关详细信息,请参阅其文档。烧瓶提供了一些内置信号。扩展可以提供它们自己的。
许多信号与基于装饰器的回调具有相似的名称。例如, request_started
信号类似于 before_request()
装饰师。与处理程序相比,信号的优势在于它们可以临时订阅,并且不会直接影响应用程序。这对于测试、度量、审计等非常有用。例如,如果您想知道在什么请求的哪些部分呈现了哪些模板,就会有一个信号通知您该信息。
核心信号¶
看见 信号 获取所有内置信号的列表。这个 应用程序结构和生命周期 页面还描述了信号和装饰器执行的顺序。
订阅信号¶
要订阅信号,可以使用connect方法订阅该信号。该方法第一个参数是发出信号时所调用的函数,第二个为可选参数,指定一个发送者,可以使用disonnect方法取消订阅信号。
所有核心Flask信号的发送者是应用本身。当您订阅一个信号时,一定要同时指定一个发送者,除非您真的想收听应用程序的所有信号。如果您正在开发扩展,尤其要注意这点。
例如,下面是一个情境管理器的辅助工具,可以在单元测试中用于确定哪个模板被渲染了,哪些变量被传递给了模板。
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 在向信号中添加新的参数时不发生错误,请确保使用一个额外的 **extra 参数。
在 with 代码块中,所有由 app 渲染的模板会被记录在 templates 变量中。每当有模板被渲染,模板对象及环境就会追加到变量中。
另外还有一个方便的辅助方法( connected_to` ),它允许临时把一个使用环境对象的函数订阅到一个信号。因为环境对象的返回值不能被指定,所以必须把列表作为参数:
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]
创建信号¶
如果您想在自己的应用程序中使用信号,可以直接使用Binker库。最常见的用例是自定义中的命名信号 Namespace
。这是大多数情况下的建议::
from blinker import Namespace
my_signals = Namespace()
现在您可以创建这样的新信号:
model_saved = my_signals.signal('model-saved')
信号的名称应当是唯一的,并且应当简明以便于调试。可以通过 name 属性获得信号的名称。
发送信号¶
如果想要发出信号,可以使用send方法。它把发送方作为第一个参数,其他参数是要发送给接受者的东西,并且其他参数是可选的:
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
以及其他需要的。请注意中描述的限制 发送信号 以及 request_tearing_down
信号。
信号订阅装饰器¶
您还可以通过使用 connect_via()
装饰师::
from flask import template_rendered
@template_rendered.connect_via(app)
def when_template_rendered(sender, template, context, **extra):
print(f'Template {template.name} is rendered with {context}')