Señales

Las señales son una forma ligera de notificar a los suscriptores determinados eventos durante el ciclo de vida de la aplicación y de cada solicitud. Cuando se produce un evento, emite la señal, que llama a cada suscriptor.

Las señales son implementadas por la librería Blinker. Consulte su documentación para obtener información detallada. Flask proporciona algunas señales integradas. Las extensiones pueden proporcionar las suyas propias.

Muchas señales reflejan las devoluciones de llamada basadas en decoradores de Flask con nombres similares. Por ejemplo, la señal request_started es similar al decorador before_request(). La ventaja de las señales sobre los manejadores es que pueden suscribirse temporalmente, y no pueden afectar directamente a la aplicación. Esto es útil para pruebas, métricas, auditoría, y más. Por ejemplo, si quieres saber qué plantillas fueron renderizadas en qué partes de qué peticiones, hay una señal que te notificará esa información.

Núcleo de las señales

Véase Señales para una lista de todas las señales incorporadas. La página Application Structure and Lifecycle también describe el orden en que se ejecutan las señales y los decoradores.

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

Si quieres usar señales en tu propia aplicación, puedes usar la librería blinker directamente. El caso de uso más común son las señales con nombre en una Namespace personalizada. Esto es lo que se recomienda la mayoría de las veces:

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

También puedes suscribirte fácilmente a señales utilizando el decorador connect_via():

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}')