信号

New in version 0.6.

Starting with Flask 0.6, there is integrated support for signalling in Flask. 这个支持由优秀的blinker库提供, 并且当它不可用时会优雅地退回。

What are signals? Signals help you decouple applications by sending notifications when actions occur elsewhere in the core framework or another Flask extensions. In short, signals allow certain senders to notify subscribers that something happened.

Flask comes with a couple of signals and other extensions might provide more. Also keep in mind that signals are intended to notify subscribers and should not encourage subscribers to modify data. You will notice that there are signals that appear to do the same thing like some of the builtin decorators do (eg: request_started is very similar to before_request()). However, there are differences in how they work. The core before_request() handler, for example, is executed in a specific order and is able to abort the request early by returning a response. In contrast all signal handlers are executed in undefined order and do not modify any data.

The big advantage of signals over handlers is that you can safely subscribe to them for just a split second. These temporary subscriptions are helpful for unittesting for example. Say you want to know what templates were rendered as part of a request: signals allow you to do exactly that.

订阅信号

你可以使用信号的connect()方法来订阅信号。The first argument is the function that should be called when the signal is emitted, the optional second argument specifies a sender. 你可以使用disconnect()方法退订信号。

For all core Flask signals, the sender is the application that issued the signal. When you subscribe to a signal, be sure to also provide a sender unless you really want to listen for signals from all applications. This is especially true if you are developing an extension.

For example, here is a helper context manager that can be used in a unittest to determine which templates were rendered and what variables were passed to the template:

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)

This can now easily be paired with a test client:

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

Make sure to subscribe with an extra **extra argument so that your calls don’t fail if Flask introduces new arguments to the signals.

在上面的代码中,with块中由应用app发出的所有模板渲染现在都将记录在templates变量中。Whenever a template is rendered, the template object as well as context are appended to it.

此外,也有一个方便的助手方法(connected_to()) ,它允许你临时地把函数订阅到信号并使用信号自己的上下文管理器。Because the return value of the context manager cannot be specified that way, you have to pass the list in as an argument:

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)

The example above would then look like this:

templates = []
with captured_templates(app, templates, **extra):
    ...
    template, context = templates[0]

Blinker API Changes

connected_to()方法出现在Blinker 1.1版本中。

创建信号

If you want to use signals in your own application, you can use the blinker library directly. 最常见的用法是在自定义的Namespace中命名信号。This is what is recommended most of the time:

from blinker import Namespace
my_signals = Namespace()

Now you can create new signals like this:

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

The name for the signal here makes it unique and also simplifies debugging. 你可以可以用name属性访问信号名。

For Extension Developers

If you are writing a Flask extension and you want to gracefully degrade for missing blinker installations, you can do so by using the flask.signals.Namespace class.

发送信号

如果你想要发出信号,调用send()方法可以做到。It accepts a sender as first argument and optionally some keyword arguments that are forwarded to the signal subscribers:

class Model(object):
    ...

    def save(self):
        model_saved.send(self)

Try to always pick a good sender. 如果你有一个发出信号的类,把self作为发送端。If you are emitting a signal from a random function, you can pass current_app._get_current_object() as sender.

Passing Proxies as Senders

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

信号与 Flask 的请求上下文

Signals fully support The Request Context when receiving signals. 本地上下文的变量在 request_startedrequest_finished 一贯可用, 所以你可以信任 flask.g 和其它需要的东西。Note the limitations described in Sending Signals and the request_tearing_down signal.

基于装饰器的信号订阅

With Blinker 1.1 you can also easily subscribe to signals by using the new connect_via() decorator:

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)

核心信号

所有的内置信号列表,请查看信号