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.