Сигналы

Changelog

Добавлено в версии 0.6.

Начиная с версии Flask 0.6, во Flask появилась встроенная поддержка сигнализации. Эта поддержка обеспечивается отличной библиотекой blinker, и если она недоступна, то происходит постепенный откат.

Что такое сигналы? Сигналы помогают вам разделить приложения, отправляя уведомления о действиях, происходящих в других частях основной структуры или в других расширениях Flask. Короче говоря, сигналы позволяют определенным отправителям уведомлять подписчиков о том, что что-то произошло.

Flask поставляется с парой сигналов, а другие расширения могут предоставить больше. Также следует помнить, что сигналы предназначены для уведомления подписчиков и не должны побуждать подписчиков изменять данные. Вы заметите, что есть сигналы, которые, похоже, делают то же самое, что и некоторые встроенные декораторы (например, request_started очень похож на before_request()). Однако есть различия в том, как они работают. Например, основной обработчик before_request() выполняется в определенном порядке и может прервать запрос раньше, вернув ответ. В отличие от него все обработчики сигналов выполняются в неопределенном порядке и не изменяют никаких данных.

Большим преимуществом сигналов перед обработчиками является то, что вы можете безопасно подписаться на них всего на долю секунды. Такие временные подписки полезны, например, для модульного тестирования. Скажем, вы хотите узнать, какие шаблоны были отображены как часть запроса: сигналы позволяют вам сделать именно это.

Подписка на сигналы

Чтобы подписаться на сигнал, можно использовать метод connect() сигнала. Первым аргументом является функция, которая должна быть вызвана при подаче сигнала, необязательный второй аргумент определяет отправителя. Чтобы отписаться от сигнала, можно использовать метод disconnect().

Для всех основных сигналов Flask отправителем является приложение, издавшее сигнал. Когда вы подписываетесь на сигнал, обязательно укажите и отправителя, если только вы не хотите слушать сигналы от всех приложений. Это особенно актуально, если вы разрабатываете расширение.

Например, вот вспомогательный менеджер контекста, который можно использовать в модульном тесте для определения того, какие шаблоны были отображены и какие переменные были переданы шаблону:

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)

Теперь это может быть легко сопряжено с тестовым клиентом:

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

Обязательно подписывайтесь с дополнительным аргументом **extra, чтобы ваши вызовы не были неудачными, если Flask введет новые аргументы в сигналы.

Все отрисовки шаблонов в коде, выдаваемом приложением app в теле блока with, теперь будут записываться в переменную templates. Каждый раз, когда шаблон отрисовывается, к нему добавляется объект шаблона, а также контекст.

Кроме того, существует удобный вспомогательный метод (connected_to()), который позволяет временно подписать функцию на сигнал с менеджером контекста самостоятельно. Поскольку возвращаемое значение менеджера контекста нельзя указать таким образом, в качестве аргумента необходимо передать список:

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)

Приведенный выше пример будет выглядеть следующим образом:

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

Изменения в API Blinker

Метод connected_to() появился в Blinker в версии 1.1.

Создание сигналов

Если вы хотите использовать сигналы в своем собственном приложении, вы можете использовать библиотеку blinker напрямую. Наиболее распространенным случаем использования являются именованные сигналы в пользовательском Namespace. Вот что рекомендуется в большинстве случаев:

from blinker import Namespace
my_signals = Namespace()

Теперь вы можете создавать новые сигналы следующим образом:

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

Имя сигнала здесь делает его уникальным, а также упрощает отладку. Вы можете получить доступ к имени сигнала с помощью атрибута name.

Для разработчиков расширений

Если вы пишете расширение Flask и хотите изящно деградировать для отсутствующих установок мигалки, вы можете сделать это, используя класс flask.signals.Namespace.

Отправка сигналов

Если вы хотите издать сигнал, вы можете сделать это, вызвав метод send(). Он принимает отправителя в качестве первого аргумента и, по желанию, некоторые аргументы ключевых слов, которые передаются подписчикам сигнала:

class Model(object):
    ...

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

Старайтесь всегда выбирать хорошего отправителя. Если у вас есть класс, который испускает сигнал, передайте self в качестве отправителя. Если вы испускаете сигнал от случайной функции, вы можете передать current_app._get_current_object() в качестве отправителя.

Передача прокси-серверов в качестве отправителей

Никогда не передавайте current_app в качестве отправителя сигнала. Вместо этого используйте current_app._get_current_object(). Причина в том, что current_app является прокси, а не реальным объектом приложения.

Сигналы и контекст запросов Flask

Сигналы полностью поддерживают Контекст запроса при получении сигналов. Контекстно-локальные переменные последовательно доступны между request_started и request_finished, поэтому при необходимости вы можете использовать flask.g и другие. Обратите внимание на ограничения, описанные в Отправка сигналов и сигнале request_tearing_down.

Подписки на сигналы на основе декораторов

В Blinker 1.1 вы также можете легко подписаться на сигналы с помощью нового декоратора 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}')

Основные сигналы

Посмотрите в Сигналы список всех встроенных сигналов.

Вернуться на верх