Flask のアプリケーションコンテキストとリクエストコンテキスト

Flask には2種類のコンテキストがある。アプリケーションコンテキストとリクエストコンテキストである。これらを正しく理解することで、current_appgrequestsession の挙動が明確になる。

2種類のコンテキスト

アプリケーションコンテキストcurrent_app, g が利用可能
リクエストコンテキストrequest, session が利用可能(app context も含む)

リクエストコンテキストがプッシュされると、アプリケーションコンテキストも自動的にプッシュされる。

コンテキストのライフサイクル

from flask import Flask, request, g, current_app

app = Flask(__name__)

@app.route('/')
def index():
    # リクエストが来ると、Flask が自動的に両コンテキストをプッシュ
    print(current_app.name)  # アプリケーションコンテキスト
    print(request.path)       # リクエストコンテキスト
    print(g)                  # アプリケーションコンテキストに紐づく
    return 'OK'
    # レスポンス後、両コンテキストが自動的にポップされる

コンテキストスタック

Flask は内部的にスタック構造でコンテキストを管理している。

from flask.globals import _app_ctx_stack, _request_ctx_stack

# 複数のアプリを切り替える場合などにスタックが活用される
with app1.app_context():
    print(current_app.name)  # app1
    with app2.app_context():
        print(current_app.name)  # app2
    print(current_app.name)  # app1 に戻る

手動でコンテキストをプッシュする

リクエスト外(CLI、テスト、バックグラウンドタスク)ではコンテキストを手動でプッシュする。

# アプリケーションコンテキストのみ
with app.app_context():
    print(current_app.config['DEBUG'])
    # request は使えない

# リクエストコンテキストも含む
with app.test_request_context('/path?q=test'):
    print(request.path)  # '/path'
    print(request.args['q'])  # 'test'
    print(current_app.name)  # これも使える

current_app vs app

app を直接インポートすると循環インポートの原因になる。current_app を使えば、現在アクティブなアプリケーションを動的に取得できる。

# 悪い例
from myapp import app

def get_config():
    return app.config['DEBUG']  # 循環インポートの危険

# 良い例
from flask import current_app

def get_config():
    return current_app.config['DEBUG']  # コンテキスト内で呼べば OK

_get_current_object()

LocalProxy から実際のオブジェクトを取得する必要がある場合は _get_current_object() を使う。

# シグナルのハンドラに渡す場合など
real_app = current_app._get_current_object()

コンテキストの理解は Flask の高度な使い方の基礎となる。