合図(Signals) Signals

Changelog

バージョン 0.6 で追加.

Flask 0.6以降、Flask内にsignal処理のサポートが統合されました。このサポートは素晴らしいblinkerライブラリによって提供され、もしblinkerが利用できないときは極端に悪化しないようにフォールバックします。 Starting with Flask 0.6, there is integrated support for signalling in Flask. This support is provided by the excellent `blinker`_ library and will gracefully fall back if it is not available.

signalとは何でしょうか?signalは基本フレームワーク(core framework)や別のFlask拡張の中のどこかで処理が起きたとき通知を送ることで、アプリケーション間の癒着を弱める手助けをします。手短に言えば、signalは何か起きたときに、ある送信者が登録者へ通知を送れるようにします。 What are signals? Signals help you decouple applications by sending notifications when actions occur elsewhere in the core framework or another Flask extensions. In short, signals allow certain senders to notify subscribers that something happened.

Flaskにはいくつかのsignalが付いてきており、さらに他のFlask拡張がもっとsignalを提供するかもしれません。signalは登録者へ通知することが意図されており、登録者がデータに手を加えることは推奨されていないことも覚えておいてください。signalには、組み込みのdecoratorのいくつかが行うことと同じことをしているようなものがあることに気づくでしょう(例: request_startedbefore_request()ととても良く似ています)。しかしながら、どのように働くかについては(signalとdecoratorの間には)違いがあります。例えば、基本(core)decoratorのひとつbefore_request()の処理器(handler)は、特定の順序で実施され、レスポンスを返すことでリクエストを早く中止させることができます。対照的にすべてのsignalの処理器(handler)は実施順序が定義されず、データの変更はしません。 Flask comes with a couple of signals and other extensions might provide more. Also keep in mind that signals are intended to notify subscribers and should not encourage subscribers to modify data. You will notice that there are signals that appear to do the same thing like some of the builtin decorators do (eg: :data:`~flask.request_started` is very similar to :meth:`~flask.Flask.before_request`). However, there are differences in how they work. The core :meth:`~flask.Flask.before_request` handler, for example, is executed in a specific order and is able to abort the request early by returning a response. In contrast all signal handlers are executed in undefined order and do not modify any data.

handlerに対するsignalの大きな利点は、ほんの一瞬だけ安全にsignalへ登録できることです。それらの一時的な登録は、例えばユニットテストに役立ちます。リクエストの一部としてどのテンプレートが表示されたか知りたいとしましょう: signalはまさにそれをできるようにします。 The big advantage of signals over handlers is that you can safely subscribe to them for just a split second. These temporary subscriptions are helpful for unit testing for example. Say you want to know what templates were rendered as part of a request: signals allow you to do exactly that.

signalへの登録(Subscribing to Signals) Subscribing to Signals

signalへ登録するには、signalのconnect()メソッドが使えます。最初の引数は、signalが発行されたときに呼び出される関数であり、必須ではない2番目の引数は送信者を指定します。signalへの登録を中止するには、disconnect()メソッドを使用できます。 To subscribe to a signal, you can use the :meth:`~blinker.base.Signal.connect` method of a signal. The first argument is the function that should be called when the signal is emitted, the optional second argument specifies a sender. To unsubscribe from a signal, you can use the :meth:`~blinker.base.Signal.disconnect` method.

Flaskの基本signal(core Flask signals)のすべてで、送信者はsignalを発行するアプリケーションになります。signalへ登録するときは、本当にすべてのアプリケーションからのsignalを待ち受けたい場合を除いて、確実に送信者も(signal登録時の引数で)提供するようにしてください。これは、Flask拡張を開発している場合は特に当てはまります。 For all core Flask signals, the sender is the application that issued the signal. When you subscribe to a signal, be sure to also provide a sender unless you really want to listen for signals from all applications. This is especially true if you are developing an extension.

例えば、ユニットテストの中で、どのテンプレートが表示されどの変数がテンプレートに渡されたかを決定するために使用できるcontext manager(訳注: このcontextはFlaskのrequest contextやapplication contextではなく、with文で使えるPythonの機能)のヘルパーは以下のようになります: For example, here is a helper context manager that can be used in a unit test to determine which templates were rendered and what variables were passed to the template::

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)

するとこれは容易にtest clientと組み合わせることができます: This can now easily be paired with a test client::

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

もしFlaskが新しい引数をsignalへ導入した場合にも(signal発行時に行われる)呼び出しが失敗しないよう、追加の**extra引数を伴って登録するように確実にしてください。 Make sure to subscribe with an extra ``**extra`` argument so that your calls don't fail if Flask introduces new arguments to the signals.

コードの中で表示用に変換され、withブロック本体中のアプリケーションappによって発行されるすべてのテンプレートは、こうするとtemplates変数の中に記録されるようになります。テンプレートが表示されるときはいつでも、contextと同様にテンプレートオブジェクトは(templates変数に)追加されます。 All the template rendering in the code issued by the application `app` in the body of the ``with`` block will now be recorded in the `templates` variable. Whenever a template is rendered, the template object as well as context are appended to it.

さらに自分でcontext managerを使って関数をsignalへ一時的に登録できるようにする便利な支援メソッド(connected_to())があります。そのやり方ではcontext managerの戻り値は指定できない(訳注: with文のasで変数を指定できないことをいっていると思います)ため、引数としてリスト(訳注: 以下の例の「recorded」引数)を渡す必要があります: Additionally there is a convenient helper method (:meth:`~blinker.base.Signal.connected_to`) that allows you to temporarily subscribe a function to a signal with a context manager on its own. Because the return value of the context manager cannot be specified that way, you have to pass the list in as an argument::

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)

すると、先の例は以下のようになります: The example above would then look like this::

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

BlinkerのAPIの変化 Blinker API Changes

connected_to()メソッドは、バージョン1.1からBlinkerにやってきました。 The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker with version 1.1.

signalの作成 Creating Signals

もし自分独自のアプリケーションでsignalを使いたい場合は、blinkerライブラリを直接使用できます。最もありがちな使用状況(use case)は、独自のNamespace中の名前付きsignalです。これはほとのどのときに推奨されるものです: If you want to use signals in your own application, you can use the blinker library directly. The most common use case are named signals in a custom :class:`~blinker.base.Namespace`.. This is what is recommended most of the time::

from blinker import Namespace
my_signals = Namespace()

すると、以下のように新しいsignalを作成できます: Now you can create new signals like this::

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

ここでのsignalへの名前は、signalを一意なものにし、デバッグをシンプルにもします。name属性を使ってsignal名にアクセスできます。 The name for the signal here makes it unique and also simplifies debugging. You can access the name of the signal with the :attr:`~blinker.base.NamedSignal.name` attribute.

Flask拡張開発者向け情報(For Extension Developers) For Extension Developers

もしFlask拡張を書いていてblinkerがインストールされていないときに機能低下(degrade)を小さくしたいときは、flask.signals.Namespaceクラスを使うことで可能です。 If you are writing a Flask extension and you want to gracefully degrade for missing blinker installations, you can do so by using the :class:`flask.signals.Namespace` class.

signalの送信 Sending Signals

もしsignalを発行したい場合、send()メソッドを呼び出すことで可能です。それは最初の引数として送信者を受け付け、オプションでsignal登録者へ転送されるいくらかのキーワード引数を受け付けます: If you want to emit a signal, you can do so by calling the :meth:`~blinker.base.Signal.send` method. It accepts a sender as first argument and optionally some keyword arguments that are forwarded to the signal subscribers::

class Model(object):
    ...

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

適切な送信者を常に取り上げるようにしてください。もしsignalを発行するクラスがある場合は、送信者としてselfを渡してください。もし無規則に関数からsignalを発行している場合は、current_app._get_current_object()を送信者として渡せます。 Try to always pick a good sender. If you have a class that is emitting a signal, pass ``self`` as sender. If you are emitting a signal from a random function, you can pass ``current_app._get_current_object()`` as sender.

送信者としてのproxyの引き渡し(Passing Proxies as Senders) Passing Proxies as Senders

signaleへ送信者としてcurrent_appは決して渡さないでください。current_app._get_current_object()を代わりに使用してください。その理由は、current_appはproxyであって本物のapplicationオブジェクトではないためです。 Never pass :data:`~flask.current_app` as sender to a signal. Use ``current_app._get_current_object()`` instead. The reason for this is that :data:`~flask.current_app` is a proxy and not the real application object.

signalとFlaskのrequest context Signals and Flask's Request Context

signalはリクエストのコンテキスト(The Request Context)を、signalを受信するとき完全にサポートします。context-local変数はrequest_startedrequest_finishedの間は一貫して利用可能であるため、必要に応じてflask.gなどに頼ることができます。signalの送信の中の説明やrequest_tearing_downのsignalの制限に注意してください。 Signals fully support :ref:`request-context` when receiving signals. Context-local variables are consistently available between :data:`~flask.request_started` and :data:`~flask.request_finished`, so you can rely on :class:`flask.g` and others as needed. Note the limitations described in :ref:`signals-sending` and the :data:`~flask.request_tearing_down` signal.

decoratorに基づくsignal登録 Decorator Based Signal Subscriptions

Blinker 1.1では新しいconnect_via() decoratorを使ってsignalへ容易に登録することもできます: With Blinker 1.1 you can also easily subscribe to signals by using the new :meth:`~blinker.base.NamedSignal.connect_via` decorator::

from flask import template_rendered

@template_rendered.connect_via(app)
def when_template_rendered(sender, template, context, **extra):
    print 'Template %s is rendered with %s' % (template.name, context)

基本signal(Core Signals) Core Signals

すべての組み込みsignalのリストについては、Signalsを見てください。 Take a look at :ref:`core-signals-list` for a list of all builtin signals.