Spec-Zone.ru › Flask 2.0

Сигналы

Журнал изменений

Новое в версии 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–2021 Pallets
Licensed under the BSD 3-clause License.
https://flask.palletsprojects.com/en/2.0.x/signals/

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API