1. ホーム
  2. python

[解決済み] Flaskのコンテキストスタックの目的は何ですか?

2022-05-03 08:44:07

質問

リクエスト/アプリケーションコンテキストがどのように機能するのか、なぜそのように設計されたのかを十分に理解しないまま、しばらく使っています。リクエストやアプリケーションコンテキストに関して、"stack" の目的は何でしょうか?これらは2つの別々のスタックなのか、それとも両方とも1つのスタックの一部なのか? リクエストコンテキストはスタックにプッシュされるのか、それともスタックそのものなのか? 複数のコンテキストを互いにプッシュ/ポップすることができますか? もしそうなら、なぜそうしたいのでしょう?

質問ばかりで申し訳ありませんが、Request ContextとApplication Contextのドキュメントを読んでも、まだ混乱しています。

どのように解決するのですか?

複数のアプリ

アプリケーションコンテキスト(とその目的)は、Flask が複数のアプリケーションを持つことができることに気づくまでは、実に分かりにくいものです。1つの WSGI Python インタプリタから複数の Flask アプリケーションを実行させたい状況を想像してみてください。ここではBlueprintsの話ではなく、全く別のFlaskアプリケーションの話をしています。

のように設定することができます。 Flaskドキュメンテーションのquot;Application Dispatching"のセクション。 の例です。

from werkzeug.wsgi import DispatcherMiddleware
from frontend_app import application as frontend
from backend_app import application as backend

application = DispatcherMiddleware(frontend, {
    '/backend':     backend
})

フロントエンドとバックエンドの2つの全く異なるFlaskアプリケーションが作成されていることに注意してください。言い換えれば Flask(...) アプリケーションのコンストラクタが 2 回呼び出され、Flask アプリケーションのインスタンスが 2 つ作成されています。

コンテクスト

Flask を使っていると、様々な機能にアクセスするためにグローバル変数を使用することになることがよくあります。例えば、次のようなコードがあると思います。

from flask import request

次に、ビューの中で request を使用して、現在のリクエストの情報にアクセスします。明らかに request は通常のグローバル変数ではなく、実際には コンテキストローカル の値です。言い換えれば、舞台裏で何らかのマジックが働いているのです。 request.path を取得し path 属性から request オブジェクトの結果が異なる。 request.path .

実際、複数のスレッドでFlaskを実行しても、Flaskは賢いのでリクエストオブジェクトを分離しておくことができます。そうすることで、2つのスレッドが、それぞれ異なるリクエストを処理しながら、同時に request.path で、それぞれのリクエストに対して正しい情報を取得します。

まとめる

Flask は同じインタプリタ内で複数のアプリケーションを扱うことができること、そして Flask では "context local" グローバルが使えるので "current" が何であるかを決定するメカニズムが必要であることはすでに見てきたとおりです。 リクエスト を行うため)。 request.path ).

これらのアイデアをまとめると、Flask は "current" アプリケーションが何であるかを判断する何らかの方法を持たなければならないことも理解できるはずです!

また、以下のようなコードもあるのではないでしょうか。

from flask import url_for

のように request の例では url_for 関数には、現在の環境に依存するロジックがあります。しかし、この場合、どのアプリが現在のアプリとみなされるかにロジックが大きく依存することは明らかです。上記のフロントエンド/バックエンドの例では、"frontend" と "backend" の両方のアプリが "/login" ルートを持つ可能性があるので、以下のようになります。 url_for('/login') は、ビューがフロントエンドアプリとバックエンドアプリのどちらのリクエストを処理しているかに応じて、異なるものを返す必要があります。

質問にお答えしますと...

<ブロッククオート

スタックの目的は何ですか? アプリケーションのコンテキストは?

Request Contextのドキュメントから。

リクエストコンテキストは内部でスタックとして管理されているので は何度もプッシュとポップを繰り返すことができます。これは 内部リダイレクトのようなものです。

つまり、通常、リクエストやアプリケーションのスタックにあるアイテムは0個か1個ですが、それ以上ある可能性もあるのです。

与えられた例は、リクエストが"内部リダイレクト"の結果を返すようにする場合です。ほとんどの場合、ユーザーに対してリダイレクトを発行してリソース B を指し示します。

これらは2つの別々のスタックですか、それとも1つのスタックの一部ですか?

それらは 2つの独立したスタック . しかし、これは実装の詳細です。重要なのはスタックがあることではなく、いつでも "current"アプリやリクエスト(スタックの一番上)を取得できることです。

<ブロッククオート

リクエストコンテキストはスタックにプッシュされるのか、それともスタックそのものなのか?

リクエストコンテキストは、リクエストコンテキストスタックの1つのアイテムです。同様に、"アプリコンテキスト"と"アプリコンテキストスタック"も同様です。

<ブロッククオート

複数のコンテキストをPush/Popすることは可能ですか?もしそうなら。 なぜそうしたいのでしょうか?

Flaskアプリケーションでは、通常このようなことは行いません。例えば、内部リダイレクトを行う場合です(上記)。しかしその場合でも、結局は Flask に新しいリクエストを処理させることになるでしょうから、Flask があなたのためにすべてのプッシュ/ポッピングを行うことになります。

しかし、スタックを自分で操作したいケースもあります。

リクエストの外側でコードを実行する

典型的な問題として、Flask-SQLAlchemy 拡張を使って、SQL データベースとモデル定義をセットアップする際に、以下のようなコードを使うことがあります...。

app = Flask(__name__)
db = SQLAlchemy() # Initialize the Flask-SQLAlchemy extension object
db.init_app(app)

そして、彼らは appdb の値は、シェルから実行されるべきスクリプトの中で使用されます。例えば、 "setup_tables.py" スクリプトの場合...

from myapp import app, db

# Set up models
db.create_all()

この場合、Flask-SQLAlchemy 拡張が知っているのは app アプリケーションで使用されますが create_all() を実行すると、アプリケーションコンテキストが存在しないことを示すエラーがスローされます。このエラーは正当なものです。 create_all メソッドを使用します。

なぜ、このように with app.app_context() の呼び出しは、ビューで同様の関数を実行するときに使用されます。なぜなら、Flask は実際の Web リクエストを処理するときに、すでにアプリケーションコンテキストの管理を代行しているからです。この問題は、ビュー関数(または他のそのようなコールバック)の外側で、例えば、単発のスクリプトでモデルを使用するときにのみ、本当に出てきます。

解決策は、アプリケーションコンテキストを自分でプッシュすることで、これは次のようにして行うことができます。

from myapp import app, db

# Set up models
with app.app_context():
    db.create_all()

これは、新しいアプリケーションコンテキストをプッシュします(アプリケーションの app 複数のアプリケーションが存在する可能性があることを忘れないでください)。

テスト

スタックを操作したいもう一つのケースは、テストのためです。リクエストを処理し、その結果をチェックするユニットテストを作成することができます。

import unittest
from flask import request

class MyTest(unittest.TestCase):
    def test_thing(self):
        with app.test_request_context('/?next=http://example.com/') as ctx:
            # You can now view attributes on request context stack by using `request`.

        # Now the request context stack is empty