信号

0.6 新版功能.

Changelog

Flask 自 0.6 版本开始在内部支持信号。信号功能由优秀的 blinker 库提供支持, 如果没有安装该库就无法使用信号功能,但不影响其他功能。

什么是信号?当核心框架或其他flask扩展中的其他地方发生操作时,信号可以通过发送通知来帮助您分离应用程序。简而言之,信号允许某些发送者通知接收者有事情发生了。

Flask 自身有许多信号,其他扩展可能还会带来更多信号。请记住,信号使用目的是通知接收者,不应该鼓励接收者修改数据。你会注意到信号的功能与一些内建的装饰 器类似(如 request_started 与 before_request非常相似),但是它们的工作原理不同。例如 核心的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]

Blinker API的变化

Blinker version 1.1 版本中增加了 connected_to 方法。

创建信号

如果你想在自己的应用程序中使用信号,可以直接使用Blinker库。最常见的,也 是最推荐的方法是在自定义的 Namespace 中命名信号:

from blinker import Namespace
my_signals = Namespace()

现在您可以创建这样的新信号:

model_saved = my_signals.signal('model-saved')

信号的名称应当是唯一的,并且应当简明以便于调试。可以通过 name 属性获得信号的名称。

扩展开发人员注意:

如果你正在编写一个Flask扩展,并且想要妥善处理 blinker 安装缺失的情况,那么可以使用 flask.signals.Namespace 类。

发送信号

如果想要发出信号,可以使用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信号。

信号订阅装饰器

在Blinker 1.1版本中,你还可以通过使用新的 .NamedSignal.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)

核心信号

所有内置信号请参阅 Signals 。