Signals
Changelog
New in version 0.6.
What are signals? Signals help you decouple applications by sendingnotifications when actions occur elsewhere in the core framework oranother Flask extensions. In short, signals allow certain senders tonotify subscribers that something happened.
Flask comes with a couple of signals and other extensions might providemore. Also keep in mind that signals are intended to notify subscribersand should not encourage subscribers to modify data. You will notice thatthere are signals that appear to do the same thing like some of thebuiltin decorators do (eg: request_started
is very similarto before_request()
). However, there are differences inhow they work. The core before_request()
handler, forexample, is executed in a specific order and is able to abort the requestearly by returning a response. In contrast all signal handlers areexecuted in undefined order and do not modify any data.
The big advantage of signals over handlers is that you can safelysubscribe to them for just a split second. These temporarysubscriptions are helpful for unit testing for example. Say you want toknow what templates were rendered as part of a request: signals allow youto do exactly that.
Subscribing to Signals
To subscribe to a signal, you can use theconnect()
method of a signal. The firstargument is the function that should be called when the signal is emitted,the optional second argument specifies a sender. To unsubscribe from asignal, you can use the disconnect()
method.
For all core Flask signals, the sender is the application that issued thesignal. When you subscribe to a signal, be sure to also provide a senderunless you really want to listen for signals from all applications. This isespecially true if you are developing an extension.
For example, here is a helper context manager that can be used in a unit testto determine which templates were rendered and what variables were passedto 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 yourcalls don’t fail if Flask introduces new arguments to the signals.
All the template rendering in the code issued by the application _app_in the body of the with
block will now be recorded in the _templates_variable. Whenever a template is rendered, the template object as well ascontext are appended to it.
Additionally there is a convenient helper method(connected_to()
) that allows you totemporarily subscribe a function to a signal with a context manager onits own. Because the return value of the context manager cannot bespecified 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
The connected_to()
method arrived in Blinkerwith version 1.1.
Creating Signals
If you want to use signals in your own application, you can use theblinker library directly. The most common use case are named signals in acustom Namespace
.. This is what is recommendedmost 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 simplifiesdebugging. You can access the name of the signal with thename
attribute.
For Extension Developers
If you are writing a Flask extension and you want to gracefully degrade formissing blinker installations, you can do so by using theflask.signals.Namespace
class.
Sending Signals
If you want to emit a signal, you can do so by calling thesend()
method. It accepts a sender as firstargument and optionally some keyword arguments that are forwarded to thesignal subscribers:
- class Model(object):
- ...
- def save(self):
- model_saved.send(self)
Try to always pick a good sender. If you have a class that is emitting asignal, pass self
as sender. If you are emitting a signal from a randomfunction, you can pass current_app._get_current_object()
as sender.
Passing Proxies as Senders
Never pass current_app
as sender to a signal. Usecurrent_app._get_current_object()
instead. The reason for this isthat current_app
is a proxy and not the real applicationobject.
Signals and Flask’s Request Context
Signals fully support The Request Context when receiving signals.Context-local variables are consistently available betweenrequest_started
and request_finished
, so you canrely on flask.g
and others as needed. Note the limitations describedin Sending Signals and the request_tearing_down
signal.
Decorator Based Signal Subscriptions
With Blinker 1.1 you can also easily subscribe to signals by using the newconnect_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)
Core Signals
Take a look at Signals for a list of all builtin signals.