Señales

Changelog

Nuevo en la versión 0.6.

A partir de Flask 0.6, hay soporte integrado para la señalización en Flask. Este soporte es proporcionado por la excelente biblioteca blinker y se retirará con gracia si no está disponible.

¿Qué son las señales? Las señales te ayudan a desacoplar las aplicaciones enviando notificaciones cuando se producen acciones en otra parte del núcleo del framework o en otras extensiones de Flask. En resumen, las señales permiten a ciertos remitentes notificar a los suscriptores que algo ha sucedido.

Flask viene con un par de señales y otras extensiones podrían proporcionar más. También hay que tener en cuenta que las señales están pensadas para notificar a los suscriptores y no deben animar a los suscriptores a modificar los datos. Notarás que hay señales que parecen hacer lo mismo que algunos de los decoradores incorporados (por ejemplo: request_started es muy similar a before_request()). Sin embargo, hay diferencias en su funcionamiento. El manejador principal before_request(), por ejemplo, se ejecuta en un orden específico y es capaz de abortar la solicitud antes de tiempo devolviendo una respuesta. En cambio, todos los manejadores de señales se ejecutan en un orden indefinido y no modifican ningún dato.

La gran ventaja de las señales sobre los manejadores es que puedes suscribirte a ellas de forma segura durante una fracción de segundo. Estas suscripciones temporales son útiles para las pruebas unitarias, por ejemplo. Digamos que quieres saber qué plantillas se renderizaron como parte de una solicitud: las señales te permiten hacer exactamente eso.

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]

Cambios en la API Blinker

El método connected_to() llegó a Blinker con la versión 1.1.

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 un Namespace. 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.

Para los desarrolladores de extensiones

Si estás escribiendo una extensión de Flask y quieres degradar con gracia las instalaciones de parpadeo que faltan, puedes hacerlo utilizando la clase flask.signals.Namespace.

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

Con Blinker 1.1 también puedes suscribirte fácilmente a las señales utilizando el nuevo 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}')

Núcleo de las señales

Echa un vistazo a Señales para ver una lista de todas las señales incorporadas.