I am practising flask. I have made a simple flask app which sends an email to the admin saying "New User has joined." once someone enters their name and submit on the form.
This is email component:
from flask import current_app, render_template
from flask_mail import Message
from threading import Thread
from app import mail
# Send aync email functions
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
When I run the app, and enter the name on the form and press submit, email doesn't get sent.
And I get an error on the log saying
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.
I have found a work around this but I don't understand why is this current code not working.
This is my application factory:
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
and this is my file creating app instance:
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():
"""Run unit tests."""
import unittest
tests = unittest.TestLoader().discover("tests")
unittest.TextTestRunner(verbosity=2).run(tests)
and these are the configurations:
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,
}
This is my work around:
# Send aync email functions
def send_aysnc_email(app, msg):
with app.app_context():
mail.send(msg)
def send_mail(to, subject, template, **kwargs):
app = current_app._get_current_object()
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
I don't understand why app.app_context()
cannot access the current app instance when provided the current app as argument and works well with current_app._get_current_object()
.
Why cannot
app.app_context()
access the current app instance when provided the current app as an argument?
app_context()
actually returns a proxy-object, which should not be passed to another thread (docs):
Some of the objects provided by Flask are proxies to other objects. The proxies are accessed in the same way for each worker thread, but point to the unique object bound to each worker [...]
Most of the time you don’t have to care about that, but there are some exceptions where it is good to know that this object is actually a proxy: [...] The reference to the proxied object is needed in some situations, such as sending Signals or passing data to a background thread.
That's why you have to use app._get_current_object()
. Since conventionally it's a private method, it could be a good idea to suppress linter's warnings with # noqa
:
def send_mail(to, subject, template, **kwargs):
app = current_app._get_current_object() # noqa: there is no workaround
...