Сигналы
Журнал изменений
Новая версия 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 и хотите обеспечить плавную работу при отсутствии blinker, вы можете сделать это, используя класс 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}')
Встроенные сигналы
Обратитесь к Сигналам для получения списка всех встроенных сигналов.
© 2007–2022 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.1.x/signals/