Flask应用工厂模式下邮件组件无法访问App实例的疑问
Flask异步邮件发送的应用上下文错误分析与解决
我正在练习Flask开发,搭建了一个用户提交表单后向管理员发送「新用户已加入」邮件的简单应用,采用应用工厂模式构建。
原邮件组件代码
from flask import current_app, render_template from flask_mail import Message from threading import Thread from app import mail # 异步发送邮件函数 def send_aysnc_email(app, msg): with app.app_context(): mail.send(msg) def send_mail(to, subject, template, **kwargs): app = current_app msg = Message( app.config["FLASKY_MAIL_SUBJECT_PREFIX"] + subject, sender=app.config["FLASKY_MAIL_SENDER"], recipients=[to], ) msg.body = render_template(template + ".txt", **kwargs) msg.html = render_template(template + ".html", **kwargs) thr = Thread(target=send_aysnc_email, args=[app, msg]) thr.start() return thr
运行报错信息
Exception in thread Thread-3 (send_aysnc_email): Traceback (most recent call last): File "/usr/lib/python3.10/threading.py", line 1016, in _bootstrap_inner 127.0.0.1 - - [02/Sep/2023 14:39:29] "POST / HTTP/1.1" 302 - self.run() File "/usr/lib/python3.10/threading.py", line 953, in run self._target(*self._args, **self._kwargs) File "/home/cadbay53/Desktop/practice/flask/app/emails.py", line 9, in send_aysnc_email with app.app_context(): File "/home/cadbay53/Desktop/practice/flask/venv/lib/python3.10/site-packages/werkzeug/local.py", line 311, in __get__ obj = instance._get_current_object() File "/home/cadbay53/Desktop/practice/flask/venv/lib/python3.10/site-packages/werkzeug/local.py", line 508, in _get_current_object raise RuntimeError(unbound_message) from None RuntimeError: Working outside of application context. This typically means that you attempted to use functionality that needed the current application. To solve this, set up an application context with app.app_context(). See the documentation for more information.
错误提示翻译:
该错误意味着你尝试使用了需要当前应用实例的功能。解决方法是通过app.app_context()创建应用上下文,更多信息可参考官方文档。
解决方法
将send_mail函数中的app = current_app修改为app = current_app._get_current_object()后,问题得到解决。以下是对问题根源的解释:
相关项目代码
应用工厂代码
from flask import Flask, render_template from flask_bootstrap import Bootstrap from flask_mail import Mail from flask_moment import Moment from flask_sqlalchemy import SQLAlchemy from config import config bootstrap = Bootstrap() mail = Mail() moment = Moment() db = SQLAlchemy() def create_app(config_name): app = Flask(__name__) app.config.from_object(config[config_name]) config[config_name].init_app(app) bootstrap.init_app(app) mail.init_app(app) moment.init_app(app) db.init_app(app) from .main import main as main_blueprint app.register_blueprint(main_blueprint) return app
实例创建代码
import os from app import create_app from app.models import User, Role from flask_migrate import Migrate from app import db app = create_app("default") migrate = Migrate(app, db) @app.shell_context_processor def make_shell_context(): return dict(db=db, User=User, Role=Role) @app.cli.command() def test(): """运行单元测试。""" import unittest tests = unittest.TestLoader().discover("tests") unittest.TextTestRunner(verbosity=2).run(tests)
配置代码
import os basedir = os.path.abspath(os.path.dirname(__file__)) class Config: SECRET_KEY = "***" MAIL_SERVER = "smtp.gmail.com" MAIL_PORT = 587 MAIL_USE_TLS = True MAIL_USERNAME = "***@***.com" MAIL_PASSWORD = "****" FLASKY_MAIL_SUBJECT_PREFIX = "[Flasky]" FLASKY_MAIL_SENDER = "Flasky Admin <***@***.com>" FLASKY_ADMIN = "***@***.com" SQLALCHEMY_TRACK_MODIFICATIONS = False @staticmethod def init_app(app): pass class DevelopmentConfig(Config): DEBUG = True SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(basedir, "data-dev.sqlite") class TestConfig(Config): TESTING = True SQLALCHEMY_DATABASE_URI = "sqlite:///" class ProductionConfig(Config): SQLALCHEMY_DATABASE_URI = "sqlite:///" + os.path.join(basedir, "data.sqlite") config = { "development": DevelopmentConfig, "testing": TestConfig, "production": ProductionConfig, "default": DevelopmentConfig, }
问题根源解释
current_app是Flask提供的本地代理对象,它仅在当前线程/请求的应用上下文中指向真实的Flask应用实例。当你直接赋值app = current_app并传递给子线程时,子线程没有继承主线程的应用上下文,代理对象无法找到对应的真实App实例,调用app.app_context()就会抛出上下文错误。
而current_app._get_current_object()方法会直接返回代理背后的真实Flask应用实例,将这个真实实例传递给子线程后,就能在子线程中通过它创建新的应用上下文,正常执行邮件发送操作。
内容的提问来源于stack exchange,提问作者Adnan Bayadwala
相关产品推荐
相关产品推荐

