Include Alerts in a long calculation

I have made an app using Dash which allows users to drop an excel-file, perform some calculations using the data within this file and at the end make a nice plot. However, the data needs to meet certain criteria otherwise the programm will not be able to perform the calculations (correctly). In my own offline script I have several if statements with some prints that show me what could be a cause for possible problems, but I also want to show some of these warnings to users to help them in correcting data and parameter settings so the calculations will run correctly.

My idea was to show these warnings using Alerts, however, I can’t find how to do this as the calculation is performed in a single function and contains a loop over the data. Ideally I would end up with something like this:

"Calculations"
if variable > 10:
Call Alert and display it
"Continue Calculations"

The reason why I haven’t succeeded yet is because I’m a bit confused about the callback. To display the alert I call another (standard) function which sets the specific alert open → True, so it gets displayed. However, this function requires an input, but as I calculate this variable within another calculation I can’t store it and turn it into an input. Currently I’m using this to open the alarm as soon as a file is being uploaded, but I want to only open the Alert if I state it in an if statement within the calculation:

@app.callback(
    Output("alert-fade", "is_open"),
    [Input("upload-data", "filename")],
    [State("alert-fade", "is_open")],
)
def toggle_alert(filename, is_open):
    if filename:
        return not is_open
    return is_open

Any ideas on how I can solve this problem with callbacks, without me having to completly rewrite the script and split the main function?

Hello @Danny1,

Check this out.

In the code snippet I gave in the last post, it allows for feedback in the way of a statement about the progress.

You may be able to utilize the progress and running settings to get your desired result.

Hey @jinnyzor, first of all, thanks for the fast reply. What I can see from your post is really helpfull, however, also your example is connected to a button defined in the layout. Whereas in my case, I want to call the display function only when a condition is met. So not caling it by clicking a button, but by meeting set values and then call it from the script.

The whole callback is going to be triggered from conditions, or just the feedback.

This should work from any callback, you just give feedback based upon whatever criteria you set up.

Ah yes, I see. One follow up question about the lines of text that are written, in the mentioned example these are:

set_progress('Loading Data')
set_progress('Successfully Loaded Data, performing additional crunching')
return [f"Clicked {n_clicks} times", '']

Is it possible to list these? So instead of one replacing the other, I want to append all lines so I get an overview of all steps that have occured.

Yup, you could do something like:

feedback = []
if x > y:
     feedback.append('x is greater than y')
set_progress(feedback)

You’d have to decide how you want the list to display.

I tried what you proposed, but now I don’t get updates during the simulation. Only one message at the end. These are the lines of code I used:

@app.long_callback(
    output=[Output("paragraph_id", "children"),
    Output("progress_spinner", "children")],
    inputs=Input("button_id", "n_clicks"),
    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"),
                {"display": "block"},
                {"display": "none"},
        ),
    ],
    cancel=[Input("cancel_button_id", "n_clicks")],
    progress=[Output("progress_bar", "children")],
    prevent_initial_call=True
)
def callback(set_progress, n_clicks):
    feedback = []

    total = 10
    feedback.append('Loading Data \n')
    set_progress(feedback)

    if total > 5:
        feedback.append('Higher \n')
        set_progress(feedback)
    else:
        feedback.append('Lower\n')
        set_progress(feedback)

    feedback.append('Finished Simulation \n')

    return [(feedback), '']

Try this.

import time
import dash
from dash import html, dcc
from dash.long_callback import DiskcacheLongCallbackManager
from dash.dependencies import Input, Output
from dash.exceptions import PreventUpdate

## Diskcache
import diskcache
cache = diskcache.Cache("./cache")
long_callback_manager = DiskcacheLongCallbackManager(cache)

app = dash.Dash(__name__, long_callback_manager=long_callback_manager)

app.layout = html.Div(
    [dcc.Loading(id="progress_spinner"),
        html.Div(
            [
                html.P(id="paragraph_id", children=["Button not clicked"]),
                html.P(id="progress_bar", style={"display": "none"},),
            ]
        ),
        html.Button(id="button_id", children="Run Job!"),
        html.Button(id="cancel_button_id", children="Cancel Running Job!"),
    ]
)

def lnFdback(text):
    return html.Div(text)

@app.long_callback(
    output=[Output("paragraph_id", "children"),
            Output("progress_spinner", "children")],
    inputs=Input("button_id", "n_clicks"),
    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"),
                {"display": "block"},
                {"display": "none"},
        ),
    ],
    cancel=[Input("cancel_button_id", "n_clicks")],
    progress=[Output("progress_bar", "children")],
    prevent_initial_call=True
)
def callback(set_progress, n_clicks):
    if n_clicks > 0:
        feedback = []

        total = 10
        feedback.append(lnFdback('Loading Data'))
        set_progress([feedback])

        time.sleep(3)

        if total > 5:
            feedback.append(lnFdback('Higher'))
        else:
            feedback.append(lnFdback('Lower'))

        set_progress([feedback])

        time.sleep(10)

        feedback.append(lnFdback('Finished Simulation'))

        return [feedback, '']
    else:
        raise PreventUpdate


if __name__ == "__main__":
    app.run_server(debug=True,port=8054)

It would operate too quickly without sleeps, you’d only see the last message.

If your calculations are too quick, then you dont need to set the progress as it is pointless.

Hi @jinnyzor and @Danny1

Nice example of @app.long_callack but just fyi, it’s better to use the new background callback. This is from the dash docs:

We recommend using background callbacks with @dash.callback(…, background=True) instead of long_callback . The background=True argument was introduced in Dash 2.6.0 and addresses several limitations with long_callback , including its incompatibility with dynamically added components and with Pattern Matching Callbacks. long_callback is still safe to use but no longer recommended for versions of Dash later than 2.6.0.

@AnnMarieW,

Thanks! I saw something along those lines, I will try it again, I was having issues with the progress update when I tried it initially.