Background callback with dash extensions and flask

I had the same issue, but I have a potential workaround where I can get dash_extensions, background_callback_manager, and flask to work together, but I’m not sure if it will work in more complex flask applications. Basically, instead of instantiating the dash app inside of flask with app = init_dash(app), I turned it around and instantiate the flask app inside of the dash app, and then grab the flask server for dash. In this way, I can still access both Flask and Dash routes.

In flask_app.py:

import flask
from werkzeug.serving import run_simple


def create_flask_app():
    app = flask.Flask(__name__)

    @app.route("/")
    def home():
        return "Hello, Flask!"

    return app


if __name__ == "__main__":
    run_simple("localhost", 8050, create_flask_app())

and in dash_app.py

import time

from celery import Celery
from dash import CeleryManager
from dash_extensions.enrich import (
    DashProxy,
    Input,
    Output,
    RedisBackend,
    ServersideOutputTransform,
    callback,
    html,
)

from flask_app import create_flask_app

# *** INITIALIZE CELERY ***

REDIS_URL = "redis://127.0.0.1:6379/0"
REDIS_HOST = "127.0.0.1"


celery_app = Celery(__name__, broker=REDIS_URL, backend=REDIS_URL)
background_callback_manager = CeleryManager(celery_app)

# *** INSTANTIATE FLASK APP, AND GRAB THE SERVER ***
server = create_flask_app()

URL_BASE = "/bkgd/"

app = DashProxy(
    __name__,
    server=server,
    routes_pathname_prefix=URL_BASE,
    background_callback_manager=background_callback_manager,
    transforms=[ServersideOutputTransform(backends=[RedisBackend(host=REDIS_HOST)])],
)

app.layout = html.Div(
    [
        html.Div(
            [
                html.P(id="paragraph_id", children=["Button not clicked"]),
                html.Progress(id="progress_bar", value="0"),
            ]
        ),
        html.Button(id="button_id", children="Run Job!"),
        html.Button(id="cancel_button_id", children="Cancel Running Job!"),
    ]
)


@callback(
    output=Output("paragraph_id", "children"),
    inputs=Input("button_id", "n_clicks"),
    background=True,  # triggers dash to use background callbacks here
    running=[
        (Output("button_id", "disabled"), True, False),
        (Output("cancel_button_id", "disabled"), False, True),
        (
            Output("paragraph_id", "style"),
            {"visibility": "hidden"},
            {"visibility": "visible"},
        ),
        (
            Output("progress_bar", "style"),
            {"visibility": "visible"},
            {"visibility": "hidden"},
        ),
    ],
    cancel=Input("cancel_button_id", "n_clicks"),
    progress=[Output("progress_bar", "value"), Output("progress_bar", "max")],
    prevent_initial_call=True,
)
def update_progress(set_progress, n_clicks):
    total = 5
    print("\nUpdating progress!!!")
    for i in range(total + 1):
        set_progress((str(i), str(total)))
        time.sleep(1)

    return f"Clicked {n_clicks} times"


app.register_celery_tasks()  # required for dash_extensions to use celery

if __name__ == "__main__":
    app.run(debug=True)

in requirements.txt

dash
dash_extensions
celery
redis

I have a redis server running on my system. I first run celery worker with:

celery -A dash_app:celery_app worker --loglevel=INFO --concurrency=2

and then launch the app with:

python dash_app.py

I’ve published the codebase here: GitHub - brentkendrick/dash_extensions_flask

Unfortunately, while most of the background callback functions work, the “cancel” option does not currently work when using dash_extensions DashProxy, as apparently others have also found: Dash-extensions, Serverside(), cancel of background callback. Not sure if @Emil is able to chime in on a potential fix, but in any case I love using the dash_extensions ServersideOutputTransform function for pushing large dataframes around in my callbacks and avoiding the json serialization.

I haven’t yet tested the workaround by @TimC, but will give it a try and see if it resolves the issue with the background callback “cancel” function.