シグナル、という機能が気になりました。
どうやら外部モジュールを利用した昨日みたいです。
flaskには、Signalという機能があります。リクエストコンテキストの項目を勉強していたら、不意にでてきましたので、シグナルとはどのようなものなのかをチュートリアルを通して勉強していきたいと思います。
こんな人の役に立つかも
・flaskアプリ開発を勉強している人
・flaskのSignalについて勉強をしている人
Signalについて
Signalという機能はFlaskのバージョン0.6から追加されたそうです。Signalは、flaskのコアフレームワークまたは、拡張機能で何かしらのアクションが発生した時に、外部のプログラムや機能にアクションの発生を通知することができる機能です。
flaskには、基本となるSignalがあらかじめ付属していますが、他の拡張機能がもっと多くのSignalを提供してくれるということです。
基本的なflaskに付属しているSignalには次のページのものがあります。
基本となるSignalの一つに、「request_started」がありますが、これが発生するタイミングは、デコレータである「before_request」に似ています。デコレータは、特定の順番で実行され、returnで値を返すことができるので、リクエストを早期に中断できるという違いがあります。
Signalの機能を利用するには、追加でblinkerというモジュールをインストールする必要があります。
flask内部のプログラムとして実行されるのがデコレータで、外部に通知して外部の機能が勝手に動くのがSignalというイメージでしょうか。
Signalの実用的な利用方法
Signalの利用方法の一つに、テストプログラムで、リクエストをした時にレンダリングされたテンプレートを保存して知ることができる、というようなものがあるようです。
チュートリアルのプログラムだけだと、動作しないので、以前作成したミニマムなflaskアプリにこの機能を実装して検証してみたいと思います。
minimumなflaskアプリをclone
任意の階層に移動して、以前の記事で作成したミニマムなflaskアプリをcloneします。
※私は基本的に「/Python」階層にプロジェクトフォルダとして作成しますので、Pythonフォルダに移動しています。
$ cd Python
$ git clone https://github.com/perfectpanda-works/panda-flask.git
cloneで、「panda-flask」というフォルダごとコピーされてきますので、「panda-flask」は好きな名前に変更してください。今回は「signal_test」というフォルダにしました。
アプリを動作させるため、ダウンロードした階層内にPython仮想環境のvenvの設定を行い、requestments.txtから必要なPythonモジュールをインストールします。
mac
$ cd signal_test
$ Python3 -m venv venv
$ . venv/bin/activate
(venv)$ pip install -r requestments.txt
windows
> cd signal_test
> py -3 -m venv venv
> venv¥Scripts¥activate
(venv)> pip install -r requestments.txt
とりあえず、アプリが動作するか、確認してみます。
(venv)$ export FLASK_APP=min_app
(venv)$ flask run
ブラウザでlocalhost:5000/にアクセスして、次のように表示されれば完了です。
ターミナル、または、コマンドプロンプトで「Ctrl + C」を押してサーバーを停止します。
テストプログラムの作成
Signalを利用するためにBlinkerというモジュールをインストールしておきます。
$ pip install blinker
テストプログラムは、次の階層にある「test_first.py」を編集します。
「test_first.py」に次のプログラムを追加します。
from flask import template_rendered
from contextlib import contextmanager
#①Pythonのコンテキストマネージャー
@contextmanager
def captured_templates(app):
recorded = []
#ここにconnectで呼び出す関数を定義、引数は仕様??
def record(sender, template, context, **extra):
recorded.append((template, context))
template_rendered.connect(record, app)
try:
#帰り値はrecordednのリストです。
yield recorded
finally:
#終了時に必ずdisconnectするようになります。
template_rendered.disconnect(record, app)
#②Signalを使ってテンプレートを取得
def test_signal():
#ここでコンテキストマネージャを利用します。
with captured_templates(min_app.app) as templates:
rv = min_app.app.test_client().get('/')
assert rv.status_code == 200
assert len(templates) == 1
template, context = templates[0]
assert template.name == 'index.html'
#contextの中身を確認
print(context)
#ここの使い方がいまいちわかっていません^^;
assert len(context['session']) == 10
①のコンテキストマネージャーは、Pythonのコンテキストマネージャという機能で、withブロックで利用できるような機能を作成できます。ここで「connect」というメソッドを利用してすぐ上のrecordという関数をsignalに接続しています。appは、送信者を表しています。
「template_rendered」にconnectすることで、テンプレートがレンダリングされたタイミングのシグナルになります。
recordは引数「sender,template,context,**extra」ととりますが、もうflaskで決まっている引数のようです。templateにはレンダリングしたテンプレートの名前などが入ってきて、contextにはg、request、sessionが入ってくるようです。(printで確認しました。)
①でこのようにSignalと接続するコンテキストマネージャを作成することで、②のwithブロックのようにテストプログラムで利用することができます。
②では、withブロックでcaptured_templatesを利用することで、ブロックが呼び出されるときに、signalがrecord関数に接続されます。(ファイルオープンの時と同じイメージです。)withブロックの中では、「min_app.app.test_client().get(‘/’)」のようにリクエストを送信すると、signalが送信され、record関数によりrecordedリストにtemplateやcontextの情報が追加されていきます。
テストで利用するときには、template[0]のようにrecordedのリストが利用できますので、その中に格納されている「template」と「context」をそれぞれ代入して利用するようです。
context変数については、チュートリアルでは「context[‘items’]」となっていますが、itemsという項目はないので、よくわかりませんでした。print文で確認したところ、contextには「g」「request」「session」が格納されているようで、今回はとりあえず「session」に格納されている文字数を確認するようなプログラムにしておきました。
最後のsessionは何も入っていないので、このようにエラーになると思います。
URLをいくつか経由するようなテストを模擬的にやってみます。次のようなテストプログラムを「test_first.py」に追加しました。
def test_signal2():
with captured_templates(min_app.app) as templates:
rv = min_app.app.test_client().get('/')
assert rv.status_code == 200
rv2 = min_app.app.test_client().get('/')
assert rv2.status_code == 200
assert len(templates) == 1
2回ルートディレクトリをGETしているだけなのですが、それぞれ200のステータスコードが返ってきていることを確認しています。これで、signalが2回送信されているはずなので、最後のtemplatesには、recordedリストへ2回分のデータが記録されるはずです。(あえて==1としてエラーを出すようにしました。)
templates(recordedのリスト)にはどうやら2個のデータが存在しているようですね。正常に2回のアクセスを取得することができました。
テストは、色々できすぎてよくわからなくなってきました^^;
まずはチュートリアルにあるような部分を中心にやるといいのかな