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)