3

I've got a simple Python Flask/Celery app that's running into issues with flashing messages after a delay. I'm using Celery's delay() function to call a function that sleeps for 5 seconds, then flashes a message. Despite using with app.app_context() in my background function, Celery reports:

RuntimeError: Working outside of request context

I've also tried the @copy_current_request_context decorator, as mentioned by this question, but then I get

RuntimeError: This decorator can only be used at local scopes when a request context is on the stack. For instance within view functions

app.py:

from flask import Flask, flash, render_template, request
from celery import Celery
import time

app = Flask(__name__)
app.config['CELERY_BROKER_URL'] = 'redis://localhost:6379/0'
app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:6379/0'

celery = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
celery.conf.update(app.config)

@celery.task
def my_background_task():
    with app.app_context():
        time.sleep(5)
        flash("Background task complete.")

@app.route("/", methods=["GET", "POST"])
def index():
    if request.method == "POST":
        task = my_background_task.delay()
        return render_template("index.html")
    else:
        return render_template("index.html")

if __name__ == "__main__":
    app.run(debug=True,host='0.0.0.0')

templates/index.html:

<!doctype html>
{% with messages = get_flashed_messages() %}
    {% if messages %}
        <ul class=flashes>
        {% for message in messages %}
            <li>{{ message }}</li>
        {% endfor %}
        </ul>
    {% endif %}
{% endwith %}

{% block body %}
<form method="POST">
    <input type="submit" name="submit" value="Submit request">
</form>
{% endblock %}

I'm running the app with three terminal windows: running Redis with redis-server, running Celery with celery worker -A app.celery --loglevel=info, and running Python with python app.py.

Shawn Mehan
  • 4,513
  • 9
  • 31
  • 51
DylanSp
  • 1,379
  • 13
  • 27

1 Answers1

2

The problem is that celery tasks are run by celery, they're not part of a request done by a browser or any other client so they will always be unbound since they don't know to what client they're supposed to be replying.

Running background tasks isn't really meant to interact with clients but to trigger other work (such as sending emails), unless we're using websockets or server-sent events.

Luis Orduz
  • 2,887
  • 1
  • 13
  • 19
  • the call to 'flash("...")' has to be done by 'index()' - and there's more: "Calling a task _instantly_ returns an AsyncResult instance. This can be used to check the state of the task, wait for the task to finish, or get its return value (or if the task failed, to get the exception and traceback)." – Jeff K Dec 28 '17 at 23:49