🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
Flask

FlaskはPython用のマイクロフレームワークであり、Werkzeug・Jinja 2・good intentionsをベースにしています。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

解決済

3回答

2622閲覧

Flaskでメールの送信を非同期で行う際にwith app.app_context()を使う理由がわからない

baboo

総合スコア8

Flask

FlaskはPython用のマイクロフレームワークであり、Werkzeug・Jinja 2・good intentionsをベースにしています。

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

0グッド

1クリップ

投稿2021/02/24 15:20

#質問の内容
Flaskでメールの送信を行うプログラムを書いています。
同期処理の場合と非同期処理の場合とで2種類書いているのですが、
非同期処理の場合のみ、with app.app_context()ブロック内で送信処理を行わなければ下記のようなエラーが出てしまいます。

Python

1This typically means that you attempted to use functionality that needed 2to interface with the current application object in some way. To solve 3this, set up an application context with app.app_context().

なぜ非同期処理の場合のみ、with app.app_context()を利用してapplication contextをスタックへpush?する必要があるのか、がわかっていません。

なおapplication contextに関してはFlaskのドキュメントを一読した状態で、
「リクエストなど特定の処理におけるFlaskアプリの各データにアプリのインスタンスを直接扱うことなくアクセスできる仕組み、と認識しています。

#ソースコード
同期処理の場合

Python

1#!/usr/bin/python3 2 3#===モジュールのインポート 4import os 5from flask import Flask, render_template, session, redirect, url_for 6from flask_mail import Mail, Message 7from threading import Thread 8 9#===Flask用にアプリのインスタンス作成と秘密鍵の設定 10app = Flask(__name__) 11app.config['SECRET_KEY'] = 'hard to guess string' 12 13#===flask_mailの設定 14app.config['MAIL_SERVER'] = 'smtp.googlemail.com' 15app.config['MAIL_PORT'] = 587 16app.config['MAIL_USE_TLS'] = True 17app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') 18app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD') 19app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]' 20app.config['FLASKY_MAIL_SENDER'] = 'your_email@gmail.com' 21app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN') 22 23mail = Mail(app) 24 25def send_email(to, subject, template, **kwargs): 26 msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX']+subject, sender = app.config['FLASKY_MAIL_SENDER'], recipients = [to]) 27 msg.html = render_template(template + '.html', **kwargs) 28 mail.send(msg) 29

非同期処理の場合

Python

1#===flask_mailの設定 2app.config['MAIL_SERVER'] = 'smtp.googlemail.com' 3app.config['MAIL_PORT'] = 587 4app.config['MAIL_USE_TLS'] = True 5app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME') 6app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD') 7app.config['FLASKY_MAIL_SUBJECT_PREFIX'] = '[Flasky]' 8app.config['FLASKY_MAIL_SENDER'] = 'your_email@gmail.com' 9app.config['FLASKY_ADMIN'] = os.environ.get('FLASKY_ADMIN') 10 11mail = Mail(app) 12 13def send_email(to, subject, template, **kwargs): 14 msg = Message(app.config['FLASKY_MAIL_SUBJECT_PREFIX']+subject, sender = app.config['FLASKY_MAIL_SENDER'], recipients = [to]) 15 msg.html = render_template(template + '.html', **kwargs) 16 thr = Thread(target=send_async_email, args=[app, msg]) 17 thr.start() 18 return thr 19 20def send_async_email(app, msg): 21 with app.app_context(): 22 mail.send(msg) 23

どちらもsend_emailを特定のパス内で呼び出すことでメールを送信しています。
お力お貸しいただけますと幸いです!

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

ppaul

2021/02/25 08:50

スレッド、コンテキストという言葉はわかりますか。 わからないなら、そこから説明しなければならないですね。
baboo

2021/02/25 13:21

スレッドは処理単位、くらいにしかわかっていないですね・・・。 処理を独立・並行させる際に非同期でマルチスレッドにする、くらいの認識です。 コンテキストは正直これ!と明言できるほど理解できておらず、ぼんやりと任意の処理における何かの「状態」、といった認識です・・・。 (current_appは任意の処理が走っている際のFlaskアプリの状態、という認識です)
guest

回答3

0

ベストアンサー

スレッドとコンテキストについて説明します。

レストランでスパゲティミートソースを作ることを考えてみましょう。
この作業は以下の作業に分けられます。
1 ミートソースを作る
2 スパゲティを茹でる
3 お皿を準備して、スパゲティを載せ、ミートソースをかける。

1は以下の手順で行います。
1-1 タマネギを炒める。
1-2 挽肉を炒める。
1-3 いためたタマネギと挽肉に固形コンソメとトマトを加えて煮る。
1-4 塩こしょうで味付けする。

2は以下の手順で行います。
2-1 3リットルのお湯を沸騰させる。
2-2 塩30グラムを加える。
2-3 約5分茹でる。(太さと好みにより時間は異なる)

3は以下の手順で行います。
3-1 お皿を人数分用意する。
3-2 お皿にスパゲティを載せる。
3-3 スパゲティの上にミートソースをかける。
3-4 その上に粉チーズをかける。

非同期ではない通常の手順で行うと、1が終わってから2を始め、2が終わってから3を始めます。
これでは時間がかかり過ぎるので、もう少し速く作りたいと思ったとしましょう。

タマネギを炒めながら、挽肉を炒め、お湯を沸騰させながら、お皿の準備をすればもっと速くなりますが、体が一つでは足りません。
そこで、時間を細かく使うことを考えてみます。
まず、タマネギのための鍋に油を入れ、次に挽肉のための鍋に油を入れ、次にお皿の準備をして、タマネギのための鍋に火を付け、次に挽肉のための鍋に火を付け、次にスパゲティを茹でる鍋を準備して、というように、短い時間で作業を切り替えることです。
このように時間で切って短い仕事次々にやっていくと、まるで体が三つか四つあるように仕事ができます。忍術では分身の術といいますが、コンピュータの世界ではマルチプロセスとかマルチスレッドとよびます。

この手順を全部考えながらやるのは大変なので、今はタマネギの時間だよ、今はスパゲティを茹でる時間だよ、というように誰かが指示してくれれば楽です。

マルチプロセスマルチスレッドではOSが仕事の切り替えの指示をしてくれるので、プログラムを刷る人はタマネギの手順、挽き肉の手順、煮込みの手順などをそれぞれ書いておき、それらをどの順番でどのように実行するかを書けばよいようになっています。
マルチスレッドでは作業のための分身をスレッドといいます。
非同期処理を行うというのは、分身つまりスレッドを作って仕事をさせることです。

それぞれのスレッドは手渡された指示書とそのときの条件に従って仕事を進めます。このときの作業内容と作業条件のことを環境とかコンテキストと呼びます。もしも全ての分身(スレッド)が同じ指示書や作業条件を見ていると、2人分のミートソースを作っている最中に3人分の注文が入ると途中から3人分のつもりになって間違った作業をすると困ります。

そこで、作業の始めに掲示板に書かれた作業条件(2人分)を紙に書いておいてそれをみながら作業をする方が安全です。このように紙にメモした作業条件(つまりコピー)をつくるのが
with app.app_context()
の行なのです。
そしてwithブロックを抜けるとき、つまり作業が終わったときにはこのメモはゴミ箱にはいります。

感覚的な説明ですが、それほど間違ってはいません。
詳しいことは、OSの時分割によるプロセスやスレッドの制御の勉強をしてください。

投稿2021/02/27 13:08

ppaul

総合スコア24670

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

baboo

2021/03/01 00:47

こちらとてもわかりやすく教えていただき、よく理解できました! ありがとうございます!
guest

0

気になったので、調べてみました。

手短に言うと、以下の理由でapplication contextがpushされている必要があるようです。

  • flask_mailの実装では、質問のコードにある「mail.send」の中で「flask.current_app」を使用している
  • 「flask.current_app」は、実行中のスレッドで事前にFlaskのapplication contextがpushされている必要がある

少しソースコードとか見た感じでは、以下のような状況になるようです

  1. Flaskクラスのインスタンス(質問のコードでは「app」変数のオブジェクト)はWSGIアプリケーションになる
  2. WSGIアプリケーションでは、実際にHTTPリクエストを処理するときは、Webサーバ(WSGIサーバ)によってHTTPリクエストごとにFlaskインスタンス(appオブジェクト)が作成される
  3. WSGIサーバは複数のHTTPリクエストを並列処理するため、HTTPリクエストごとにスレッドが作成される(WSGIサーバの実装によってはgreenletなどスレッドではないものを使う場合もありますが、ここでは深入りしません)
  4. Flaskの実装では、WSGIサーバからHTTPリクエストの処理をするよう呼び出されたとき、そのときにactiveなスレッドに固有な(他のスレッドからは見えない)dictのようなオブジェクト(Flaskのドキュメントでは「Context Locals」とあります)を作成し、そこにapplication contextなどを格納する
  5. flask_mailで、質問にあるコードでの「mail.send」処理では、内部的に「flask.current_app」を使用している
  6. flask.current_app」は、必要に応じて「Context Locals」のデータにアクセスする
  7. 質問にある非同期のコードでは、「mail.send」を実行するスレッドが、#4で「Context Locals」を作成したスレッドと異なるため、「with app.app_context()」を使わないと「flask.current_app」が#4の「Context Locals」相当の参照先にアクセスできない

with app.app_context()」を実行すると、先ほどの#4のように、そのときactiveなスレッドに「Context Locals」を作成するなどの処理を行うようです

参考:

投稿2021/02/28 22:38

msiz07

総合スコア172

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

以下のリンク先が理解の役に立ったりしませんか?

手作業でのコンテキストの登録(Manually Push a Context)

Using Flask-Mail asynchronously results in “RuntimeError: working outside of application context”

  • 1つめのAnswerのコメント[2](What if we ... possible.)

The Flask Mega-Tutorial Part X: Email Support

  • Asynchronous Emails

個人的には、非同期の場合 別スレッドで動く影響で app(current_app)にアクセスできなくなるのでアクセスできるように頑張っていると理解していますが、それ以上詳しい説明はできないですね・・・。

投稿2021/02/25 04:55

FiroProchainezo

総合スコア2421

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

baboo

2021/02/25 13:19

ご回答ありがとうございます! current_appへアクセスできるスレッドとはまた異なるスレッドを作成している(マルチスレッドにしている)から、ということでしょうか?
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問