'How to prevent a Dash app influenced by user during a long_callback, Python3?
I have made a app: Dash is used to make the browser-based gui and the backend calculation is made in purely Python3.
Since the calculation can take from seconds to hours or even days depending on the user's study case, I used the decorator of long_callback from Dash. The GUI follows simple user logic:
- When the run button is clicked, the button is disabled to not only inform the user that the app is doing the calculation, but also prevent the user from clicking the button again when the app is still doing the calculation.
- When the app is still doing the calculation, the user should be able to use other parts of the app, just except the run-button. And the on-going calculation in the backend should not be influenced.
I have made a simplified code to demonstrate my issue.
- First, I enter an arbitrary value, e.g. 10 in the entry.
- Then, I click the run-button. The button is disabled and the app runs like expected.
- Before the run finishes (i.e. before the run-button becomes enabled), I changed the entry from 10 to 20, the output message shows the number
20instead of10. How is it possible? After clicking the run-button, any further operation on the GUI should not influence the call that is already started. Could you please show me how to implement it? Thanks.
import time
import dash
from dash import html, dcc
from dash.long_callback import DiskcacheLongCallbackManager
from dash.dependencies import Input, Output, State
# 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(
[
html.Div([html.P(id="paragraph_id", children=["Button not clicked"])]),
html.Button(id="button_id", children="Run Job!"),
dcc.Input(id='entry_id')
]
)
@app.long_callback(
output=Output("paragraph_id", "children"),
inputs=dict(
n_clicks=Input("button_id", "n_clicks"),
entry_text=State("entry_id", "value"),
),
running=[
(Output("button_id", "disabled"), True, False),
],
prevent_initial_call=True,
)
def callback(n_clicks, entry_text):
if not n_clicks:
raise dash.exceptions.PreventUpdate
time.sleep(3.0) # Here 3 seconds is just an example. My actual code can run days.
return [f"Clicked {n_clicks} times, entered {entry_text}"]
if __name__ == "__main__":
app.run_server(debug=True)
Solution 1:[1]
If you wish the State to not affect an existing callback execution, set the disabled of that component to True during the execution, like how you disable the button during the callback computation, like so:
@app.long_callback(
output=Output("paragraph_id", "children"),
inputs=dict(
n_clicks=Input("button_id", "n_clicks"),
entry_text=State("entry_id", "value"),
),
running=[
(Output("button_id", "disabled"), True, False),
(Output("entry_id", "disabled"), True, False),
],
prevent_initial_call=True,
)
def callback(n_clicks, entry_text):
if not n_clicks:
raise dash.exceptions.PreventUpdate
time.sleep(3.0) # Here 3 seconds is just an example. My actual code can run days.
return [f"Clicked {n_clicks} times, entered {entry_text}"]
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | Daniel Al Mouiee |
