Señales

Signals are a lightweight way to notify subscribers of certain events during the lifecycle of the application and each request. When an event occurs, it emits the signal, which calls each subscriber.

Signals are implemented by the Blinker library. See its documentation for detailed information. Flask provides some built-in signals. Extensions may provide their own.

Many signals mirror Flask’s decorator-based callbacks with similar names. For example, the request_started signal is similar to the before_request() decorator. The advantage of signals over handlers is that they can be subscribed to temporarily, and can’t directly affect the application. This is useful for testing, metrics, auditing, and more. For example, if you want to know what templates were rendered at what parts of what requests, there is a signal that will notify you of that information.

Núcleo de las señales

See Señales for a list of all built-in signals. The Application Structure and Lifecycle page also describes the order that signals and decorators execute.

Suscripción a las señales

Para suscribirse a una señal, se puede utilizar el método connect() de una señal. El primer argumento es la función que debe ser llamada cuando se emite la señal, el segundo argumento opcional especifica un emisor. Para darse de baja de una señal, puedes utilizar el método disconnect().

Para todas las señales del núcleo de Flask, el remitente es la aplicación que emitió la señal. Cuando te suscribas a una señal, asegúrate de proporcionar también un remitente a menos que realmente quieras escuchar las señales de todas las aplicaciones. Esto es especialmente cierto si estás desarrollando una extensión.

Por ejemplo, aquí hay un gestor de contexto de ayuda que se puede utilizar en una prueba de unidad para determinar qué plantillas se han renderizado y qué variables se han pasado a la plantilla:

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)

Ahora se puede emparejar fácilmente con un cliente de prueba:

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

Asegúrate de suscribirte con un argumento extra **extra para que tus llamadas no fallen si Flask introduce nuevos argumentos en las señales.

Toda la renderización de plantillas en el código emitido por la aplicación app en el cuerpo del bloque with se registrará ahora en la variable templates. Cada vez que se renderiza una plantilla, el objeto de la plantilla así como el contexto se añaden a ella.

Además, existe un práctico método de ayuda (connected_to()) que permite suscribir temporalmente una función a una señal con un gestor de contexto propio. Debido a que el valor de retorno del gestor de contexto no puede ser especificado de esa manera, tienes que pasar la lista como un argumento:

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)

El ejemplo anterior quedaría así:

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

Creación de señales

If you want to use signals in your own application, you can use the blinker library directly. The most common use case are named signals in a custom Namespace. This is what is recommended most of the time:

from blinker import Namespace
my_signals = Namespace()

Ahora puedes crear nuevas señales así:

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

El nombre de la señal aquí la hace única y también simplifica la depuración. Puedes acceder al nombre de la señal con el atributo name.

Envío de señales

Si quieres emitir una señal, puedes hacerlo llamando al método send(). Acepta un remitente como primer argumento y, opcionalmente, algunos argumentos de palabras clave que se reenvían a los suscriptores de la señal:

class Model(object):
    ...

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

Intenta elegir siempre un buen emisor. Si tienes una clase que está emitiendo una señal, pasa self como emisor. Si estás emitiendo una señal desde una función aleatoria, puedes pasar current_app._get_current_object() como emisor.

Pasar proxies como remitentes

Nunca pase current_app como remitente de una señal. Utiliza current_app._get_current_object() en su lugar. La razón es que current_app es un proxy y no el objeto real de la aplicación.

Señales y contexto de solicitud de Flask

Las señales son totalmente compatibles con El contexto de la solicitud cuando se reciben señales. Las variables locales de contexto están disponibles de forma consistente entre request_started y request_finished, por lo que puede confiar en flask.g y otros según sea necesario. Tenga en cuenta las limitaciones descritas en Envío de señales y la señal request_tearing_down.

Suscripciones de señales basadas en decoradores

You can also easily subscribe to signals by using the connect_via() decorator:

from flask import template_rendered

@template_rendered.connect_via(app)
def when_template_rendered(sender, template, context, **extra):
    print(f'Template {template.name} is rendered with {context}')