本文介绍Flask的信号机制,讲述信号的用途,并给出建立信号、订阅信号、发送信号的方法。html
Flask信号(signals, or event hooking)容许特定的发送端通知订阅者发生了什么(既然知道发生了什么,那咱们能够知道接下来该作什么了)。python
Flask提供了一些信号(核心信号)且其它的扩展提供更多的信号。信号是用于通知订阅者,而不该该鼓励订阅者修改数据。相关信号请查阅文档。web
信号依赖于Blinker库。flask
Flask钩子(一般出如今蓝图或应用程序现存的方法中,好比一些内置装饰器,例如before_request
)不须要Blinker库而且容许你改变请求对象(request
)或者响应对象(response
)。这些改变了应用程序(或者蓝图)的行为。好比before_request()
装饰器。安全
信号看起来和钩子作一样的事情。然而在工做方式上它们存在不一样。譬如核心的before_request()
处理程序以特定的顺序执行,而且能够在返回响应以前放弃请求。相比之下,全部的信号处理器是无序执行的,而且不修改任何数据。session
通常来讲,钩子用于改变行为(好比,身份验证或错误处理),而信号用于记录事件(好比记录日志)。app
由于信号依赖于Blinker库,请确保已经安装。函数
若是你要在本身的应用中使用信号,你能够直接使用Blinker库。最多见的使用状况是命名一个自定义的Namespace
的信号。这也是大多数时候推荐的作法:单元测试
pythonfrom blinker import Namespace my_signals = Namespace()
如今你能够像这样建立新的信号:测试
pythonmodel_saved = my_signals.signal('model-saved')
这里使用惟一的信号名而且简化调试。能够用name
属性来访问信号名。
对扩展开发者:
若是你正在编写一个Flask扩展,你想优雅地减小缺乏Blinker安装的影响,你能够这样作使用flask.signals.Namespace
类。
你可使用信号的connect()
方法来订阅信号。第一个参数是信号发出时要调用的函数,第二个参数是可选的,用于肯定信号的发送者。一个信号能够拥有多个订阅者。你能够用disconnect()
方法来退订信号。
对于全部的核心Flask信号,发送者都是发出信号的应用。当你订阅一个信号,请确保也提供一个发送者,除非你确实想监听所有应用的信号。这在你开发一个扩展的时候尤为正确。
好比这里有一个用于在单元测试中找出哪一个模板被渲染和传入模板的变量的助手上下文管理器:
pythonfrom 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)
如今能够轻松地与测试客户端配对:
pythonwith 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()
) ,它容许你临时地用它本身的上下文管理器把函数订阅到信号。由于上下文管理器的返回值不能按那种方法给定,则须要把列表做为参数传入:
pythonfrom 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)
上面的例子看起来像这样:
pythontemplates = [] with captured_templates(app, templates, **extra): ... template, context = templates[0]
若是你想要发送信号,你能够经过调用 send()
方法来达到目的。它接受一个发件者做为第一个参数和一些可选的被转发到信号用户的关键字参数:
pythonclass Model(object): ... def save(self): model_saved.send(self)
永远尝试选择一个合适的发送者。若是你有一个发出信号的类,把 self
做为发送者。若是你从一个随机的函数发出信号,把current_app._get_current_object()
做为发送者。
在Blinker 1.1中经过使用新的connect_via()
装饰器你也可以轻易地订阅信号:
pythonfrom 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)
template_rendered
信号是Flask核心信号。
当一个模版成功地渲染,这个信号会被发送。这个信号与模板实例 template
和上下文的词典(名为 context
)一块儿调用。
信号发送:
pythondef _render(template, context, app): """Renders the template and fires the signal""" rv = template.render(context) template_rendered.send(app, template=template, context=context) return rv
订阅示例:
pythondef 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)
user_logged_in
是Flask-User中定义的信号,当用户成功登入以后,这个信号会被发送。
发送信号:
python# Send user_logged_in signal user_logged_in.send(current_app._get_current_object(), user=user)
下面这个例子追踪登陆次数和登陆IP:
python# This code has not been tested from flask import request from flask_user.signals import user_logged_in @user_logged_in.connect_via(app) def _track_logins(sender, user, **extra): user.login_count += 1 user.last_login_ip = request.remote_addr db.session.add(user) db.session.commit()
信号可让你在一瞬间安全地订阅它们。例如,这些临时的订阅对测试颇有帮助。使用信号时,不要让信号订阅者(接收者)发生异常,由于异常会形成程序中断。