3

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.

bigblind
  • 12,539
  • 14
  • 68
  • 123
Stuart
  • 51
  • 1
  • 5
  • do you try `app = Flask(__name__) celery = Celery(__name__) celery.conf.add_defaults(app.config)`? – Joe Feb 23 '13 at 11:11
  • Ok, we're getting somewhere. Now the worker raises: `[2013-02-23 15:59:52,093: ERROR/MainProcess] Received unregistered task of type '__main__.send_email'` And KeyError: '__main__.send_email'` Which is strange given that `print celery.tasks` shows `<@task: __main__.send_email>` Which perhaps implies it *is* registered. – Stuart Feb 23 '13 at 16:02

2 Answers2

1

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)

...
aduuna
  • 11
  • 2
0

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.

Community
  • 1
  • 1
Stuart
  • 51
  • 1
  • 5
  • You can also send the msg object as a parameter. However, you probably want to use a retry to avoid losing the e-mail by sending it again afterwards when something goes wrong (e.g. connection is lost). – H.D. May 29 '13 at 06:13