Сигналы
Журнал изменений
В версии 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.2.x/signals/