I've got a Flask app where celery works fine and Flask-Mail on its own works fine as well.
from celery import Celery
from flask_mail import Mail, Message
app = Flask(__name__)
mail = Mail(app)
celery = Celery('main_app',
broker='mongodb://localhost',
backend='mongodb://localhost')
@celery.task
def cel_test():
return 'cel_test'
@app.route('/works_maybe')
def works_maybe():
return cel_test.delay()
SO FAR, SO GOOD
cel_test works fine with the celery worker; everything shows up in mongo.
But here's where it gets weird. The "signup" plus mail method works 100% without @celery.task
, but blows up when it becomes a task.
@celery.task
def send_email(some_arg, name, email):
msg = Message(…message details..)
return mail.send(msg)
@app.route("/signup", methods=['POST'])
def signup():
return send_email.delay(...stuff for the message…)
THE TRACE
R = retval = fun(*args, **kwargs)
File "/Users/username/pymods/virtualenvs/directory/lib/python2.7/site-packages/celery-3.0.15-py2.7.egg/celery/task/trace.py", line 415, in __protected_call__
return self.run(*args, **kwargs)
File "/Users/username/pymods/directory/directory/main_app/main_app.py", line 43, in send_email
something = 'a string in the Message'),
File "/Users/username/pymods/virtualenvs/directory/lib/python2.7/site-packages/flask/templating.py", line 123, in render_template
ctx.app.update_template_context(context)
AttributeError: 'NoneType' object has no attribute 'app'
Could someone explain why in one case celery works great but when I involve mail.send(msg) it breaks?
Perhaps there is something I need to learn with python more generally?
Any thoughts, if at least as to approach to this type of issue would be greatly appreciated.
Update
The bug is in the render_template portion of the send_email
task.
@celery.task
def send_email(some_arg, name, email):
msg = Message(
subject='hello',
body=render_template('email.txt',
name=name,
some_arg=some_arg),
recipients=[email]
)
return mail.send(msg)
When I remove body=render_template
, kablaam, it works.
I've got from flask import render_template
. Perhaps render_template
can't work like this?
Strangely, without Celery, the send_email
plus render_template
works perfect.
Hackish Success
When I force the app_context
with another function everything works:
def create_email(some_arg, name, email):
with app.test_request_context('/send_email'):
return render_template('email.txt',
name=name,
some_arg=some_arg)
and then toss it in the send_email
task so
body = render_template('email.txt'…
becomes
body= create_email(some_arg, name)
And we're home free.
A lot of things done in flask are bound to the application context. For example, the render_template
function, it needs to know where your application stores its templates. The session
variable wants to know your application's data store or caching system. The request
object, and your mail.send
require some application context when being called.
If you want to call them outside the scope of your flask application, like in your celery task, do it within the app context like so:
...
with app.app_context():
do_some_context_bound_actions()
msg = Messgae(...)
user_name = app.session["user"].name
msg.html = render_template("mail/welcome.html", name=user_name)
mail.send(msg)
...
来源:https://stackoverflow.com/questions/15036660/flask-mail-breaks-celery