You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 19:54:51