はじめに
いまさらながら Flask について整理していきます。「Flaskとかいうやつを使って、試しにアプリ開発にトライしてみたい」くらいの感覚の人を対象にしています。
Flaskのバージョンは 0.12.2 です。
この記事では、Blueprint について紹介していきます。その他のFlaskまとめシリーズはこちらから。
Blueprints?
Blueprintとは、アプリケーションの機能を分割して実装するためのものです。公式ドキュメントでは、大きなプロジェクトを整理するための方法としてBlueprintが推奨されています。
Blueprintを用いた実装は、以下の2ステップで行われます。
- Blueprintを実装する
- 実装したBlueprintをFlaskのアプリケーションに登録する
簡単な例
1. Blueprintを実装する
Blueprintを使わないのであれば、以下のように views.py に関数を定義していくかと思います。
# views.py from flask import Flask # Flaskのアプリケーションオブジェクト app = Flask(__name__) @app.route('/func1/a') def func1_a(): return 'func1_a' @app.route('/func1/b') def func1_b(): return 'func1_b' @app.route('/func2/a') def func2_a(): return "func2_a" if __name__ == '__main__': app.run(debug=True)
views.py の func1 と func2 が異なる機能の塊であるとみなし、Blueprintに置き換えます。func1/views.py と func2/views.py というファイルを作成し、それぞれBlueprintを実装します。
/FlaskAppDirectory ├── app.py ├── func1 │ ├──__init__.py │ └── views.py └── func2 ├──__init__.py └── views.py
# func1/views.py from flask import Blueprint # func1のBlueprint func1 = Blueprint('func1', __name__, url_prefix='/func1') @app.route('/a') def func1_a(): return 'func1_a' @app.route('/b') def func1_b(): return 'func1_b'
# func2/views.py from flask import Blueprint # func2のBlueprint func2 = Blueprint('func2', __name__, url_prefix='/func2') @app.route('/a') def func2_a(): return 'func2_a'
2. 実装したBlueprintをFlaskのアプリケーションに登録する
続いて、作成したBlueprintをflask.register_bluepirnt()
でアプリケーションに登録します。なお、Blueprintの登録を解除する関数はありません。つまり、一度登録したBlueprintを動的に削除することはできません。
# app.py from flask import Flask from func1.views import func1 from func2.views import func2 app = Flask(__name__) # blueprintをアプリケーションに登録 app.register_blueprint(func1) app.register_blueprint(func2) if __name__ == '__main__': app.run(debug=True)
これで、http://xxx/func1/a、http://xxx/func1/b、http://xxx/func2/a にアクセスできるようになっています。
この例だけ見ると、「views.pyに定義されている関数を別のファイルに分けるだけの機能」に見えてしまうかもしれませんが、そうではありません。Blueprintごとに、テンプレートや静的ファイルなどの設定もできます。テンプレートと静的ファイルに関する設定については後述します。
設定項目の詳細が知りたい場合は、公式ドキュメントを参照ください。
テンプレートの走査パスの追加
Flaskではrender_template()
実行時にtemplates
ディレクトリ以下に対象のテンプレートがあるかどうかを確認します。ファイルがあればレンダリングして返し、なければTemplateNotFound
の例外が投げられます。
Blueprintのtemplate_folder
オプションで、テンプレートを走査するディレクトリを追加できます。たとえば、以下のようにそれぞれのBlueprintで対象のディレクトリを追加できます。
# func1/views.py func1 = Blueprint('func1', __name__, url_prefix='/func1', template_folder='func1_templates') # func2/views.py func2 = Blueprint('func2', __name__, url_prefix='/func2', template_folder='func2_templates')
アプリケーションの構成は以下のようなものを想定しています。各ディレクトリにtemplatesディレクトリとテンプレートを追加しています。
/FlaskAppDirectory ├── app.py ├── func1 │ ├── __init__.py │ ├── views.py │ └── func1_templates │ └── func1_a.html └── func2 ├── __init__.py ├── views.py └── func2_templates └── func2_a.html
このtemplate_folder
の指定は、Blueprintで閉じたものではありません。つまり、func2/views.py で func1_templates 以下のファイルを指定して render_template()
を実行してもTemplateNotFound
は発生せずレンダリングされます。てっきりBlueprintごとに設定されるのだと思っていましたが、そうではありません。この挙動を受けて、「テンプレートの走査パスの追加」という題目にしています。
Blueprintの構成例
公式ドキュメントでは以下のようなディレクトリ構成で記載されています。
/FlaskAppDirectory ├── app.py └── blueprints └── func1 ├── __init__.py ├── views.py └── templates └── func1 └── func1_a.html
# func1/views.py import os from flask import Blueprint, render_template func1 = Blueprint('func1', __name__, url_prefix='/func1', template_folder='templates') @func1.route('/a') def func1_a(): return render_template(os.path.join(func1.name, 'func1_a.html'))
このサンプルのように、テンプレートを配置するディレクトリはすべて templates としたほうがわかりやすいように思います。また、templates以下に機能名のディレクトリを1つ作り、その配下にテンプレートを配置したほうが良いです。templates以下に同じ到達パス、かつ、同じファイルがあるとrender_template()
で問題が発生する可能性があるためです。
静的ファイルの走査パスとURLの追加
テンプレート同様、Blueprint生成時のオプションで静的ファイルのパスを追加できます。静的ファイルのパスは、static_folder
オプションで追加することができます。
以下の func3 をregister_blueprint()
で登録すると、 http://xxx/func3/static のRoutingが定義され、アクセスできるようになります。
# func3/views.py func3 = Blueprint('func3', __name__, url_prefix='func3', static_folder='./static')
/FlaskAppDirectory ├── app.py ├── static └── func3 ├── __init__.py ├── static │ └── test.css ├── templates └── views.py
URL生成ルールについて
url_prefix
と static_url_path
の組み合わせで以下のようなルールで http://xxx/ 以降のURLが生成されます。
url_prefix | static_url_path | 結果 |
---|---|---|
なし | なし | なし |
あり | なし | {url_prefix}/{static_folder} |
なし | あり | {static_url_path} |
あり | あり | {url_prefix}{static_url_path} |
注目すべきは4つ目の「あり」「あり」の状態です。url_prefix
とstatic_url_path
の間に/
がありません。url_prefix = a
、static_url_path = b
の場合、'/ab'となります。つまり、この2つの値はパス結合ではなく、単純に文字列結合されています。
私はurl_prefix
ありのパターンを使っています。機能の分割を目的としているので、生成されるURLも機能ごとに分離しておくのが筋だと考えています。url_prefix
が設定されていれば、静的ファイルのURLが被りにくくなるとも思います。
以下のようなケースでURLが被ってしまった場合、static/test.css が優先され、func3/static/test.css には到達できません。
# func3/views.py func3 = Blueprint('func3', __name__, static_folder='./static', static_url_path='/static')
/FlaskAppDirectory ├── app.py ├── static │ └── test.css └── func3 ├── __init__.py ├── static │ └── test.css ├── templates └── views.py
func3以下のstaticのディレクトリ名を別名に変更することで回避可能です。しかし、静的ファイル置き場=static と名前が統一されている方がわかりやすいと思います。以上から、url_prefix
とstatic_url_path
でURLを設定するのがシンプルで良いのではないでしょうか。
アプリケーションオブジェクトの取得
「Blueprintにはなく、アプリケーションオブジェクトにある機能を使いたい」という場合があります。たとえば、Loggerの取得です。
Blueprintでない場合、Loggerは以下のように取得できます。
from flask import Flask app = Flask(__name__) @app.route('/') def index(): app.logger.debug('debug') return 'Flask case'
Blueprintでも同様にログ出力をしたいケースがありますが、BlueprintはLoggerを持っていません。なので、Flaskのアプリケーションオブジェクトから Logger を取得しなければなりません。
current_app
でFlaskのアプリケーションオブジェクトを取得できます。つまり、Loggerはcurrent_app.logger
で取得できます。
from flask import Blueprint, current_app func4 = Blueprint('func4', __name__) @func4.route('/') def index(): logger = current_app.logger logger.debug('debug') return 'Blueprint case'