Flask1.0.2系列(十一) 信号

英文原文地址:http://flask.pocoo.org/docs/1.0/signals/

若有翻译错误或者不尽人意之处,请指出,谢谢~


        (新增于版本0.6。)

        从Flask版本0.6开始,Flask集成了信号支持。这个支持源于blinker库的支持,并且会在其不可用的时候优雅地退出。

        什么是信号呢?当动作在核心框架的其他地方或者在其他Flask扩展中发生时,信号会发送通知,以此来达到应用程序解耦的目的。简单来说,信号允许特定的发送端去通知订阅端到底发生了什么事。

        Flask自带了几个信号,而其他扩展组件可能提供更多。还需记住的是,信号是用来通知订阅者的,而不是用来鼓励订阅者修改数据的。你可能注意到,之前讲解过的某些东西就像是这里所说的信号一样,比如内置的修饰符的行为(例如:request_startedbefore_request()非常相似)。然而,他们的工作原理却不相同。before_request()程序的核心,例如,它是按照特定的顺序执行,并且通过返回一个响应来提前终止请求。相反,所有的信号处理程序都以未定义的顺序执行,并且不能修改任何数据。

        信号相比于处理程序最大的优势在于,你仅需一秒钟就可以安全地订阅信号。这些临时订阅对于单元测试是非常有用的。比如你想要知道哪个模板被作为请求的一部分进行渲染得:信号能让你达到这个目的。


1. 订阅信号

        为了订阅一个信号,你可以使用信号的connect()函数。其第一个参数是一个方法,当信号发送出,这个方法将被调用;而第二个参数是个可选参数,其指向一个发送者。为了取消订阅一个信号,你可以使用disconnect()函数。

        对于所有核心的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

        确保订阅时有一个额外的参数“**extra”,这样当Flask为信号引入新参数时,你的调用不至于失败。

        在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]

        注意:connected_to()函数是Blinker1.1版本新增的。


2. 创建信号

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

from blinker import Namespace


my_singals = Namespace()

        现在你就可以像这样创建一个新的信号:

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

        这里的名称要具有唯一性,这样可以简化调试。你可以使用信号的name属性来访问信号的名称。

        给扩展开发者:

        如果你正在编写Flask的扩展组件,并且你希望在没有安装blinker时优雅地降级,你可以使用flask.signals.Namespace类。


3. 发送信号

        如果你想要发送一个信号,你可以调用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是一个代理对象,而不是真正的应用程序对象。


4. 信号和Flask的请求上下文

        当接收到信号时,信号能充分支持请求上下文(后面章节会讲到)。上下文局部变量在request_startedrequest_finished之间是始终可用的,因此你可以使用flask.g和其他需要的东西。注意,不要忘了上一节(发送信号)和request_tearing_down信号所描述的局限性。


5. 基于修饰器的信号订阅

        使用Blinker1.1版本,你还可以通过使用新的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)


6. 核心信号

        内置信号请前往Signals进行阅读。

猜你喜欢

转载自blog.csdn.net/regandu/article/details/80181004
今日推荐