Global variable Updated by Hidden Div Lagging

Hello all, :smiley:

i’m trying to update my data which is an output from an SQL query but while the query run the dash gets frozen until the import is complete, does anyone have an idea on how to make the output of data available for the app until the moment in which the data frame refresh is over?

In the example below you can see i introduced an time.sleep(3) to simulate the delay caused by the query, notice how the RadioItems are unresponsive (its callback are no longer reachable) while time.sleep is running.

import dash
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc
from datetime import datetime
import numpy as np
import pandas as pd
import time

app = dash.Dash(__name__)

def compute_expensive_data():
    t=datetime.now()
    d = {'time' : pd.Series(np.array([t.minute, t.second]), index=['minute', 'second'])}
    dat = pd.DataFrame(d).to_json()
    time.sleep(3)
    return  dat

dat = compute_expensive_data()
print(dat)

app.layout = html.Div([
        html.H3('Original Time: Minute = ' + str(pd.read_json(dat)['time']['minute']) + ': Second = ' + str(pd.read_json(dat)['time']['second'])),
        html.Div(id='title-line-children'),
        dcc.RadioItems(
            id='time-dropdown',
            options=[
                {'label': 'Minute', 'value': 'minute'}, {'label': 'Second', 'value': 'second'},
            ],
            value='minute'
        ), 
        # Hidden div inside the app that stores the intermediate value
        html.Div(id='intermediate-value', style={'display': 'none'}, children = dat),
        dcc.Interval(
            id='interval-component',
            interval=10*1000, # 20 seconds in milliseconds
            n_intervals=0
        )
    ])

@app.callback(
    Output('title-line-children', 'children'),
    [Input('time-dropdown', 'value'), Input('intermediate-value', 'children')])
def render(value,dat1):
    if value == 'minute':
        printStr = str(pd.read_json(dat1)['time']['minute'])
        outStr = 'Minute = ' + printStr
    elif value == 'second':
        printStr = str(pd.read_json(dat1)['time']['second'])
        outStr = 'Second = ' + printStr
    return outStr

@app.callback(Output('intermediate-value', 'children'),
              [Input('interval-component', 'n_intervals')])
def update_global_var(n):
    return compute_expensive_data()

if __name__ == '__main__':
    app.run_server(debug=False)

Hi @fabotic,

If I understood correctly, you want the user to see the current value “Original Time”, which is calculated expensively at the moment of the user’s loading of the page.
Then you want to the user to be able to request the new value, and display minutes and seconds. You need to keep both so the user can compare the values and see the difference.
At the same time, you need two important conditions:

  1. The original time has to be computed instantaneously and separately for each user
  2. Updating the value should happen independently, without interfering with the app’s running

The way you wrote it, the original value will always be the same (as computed the moment you first ran the app), and it will never change.

Global variables are risky and can cause unexpected behavior so watch out.

The solution for this is to put the expensive computation inside the callback function. This way it is computed for each user separately, and does not interfere with the app.

Assuming my understanding is correct! :slight_smile: here is the modified code that works (dat is computed on demand, no hidden div or intermediate value):

import dash
from dash.dependencies import Input, Output
import dash_html_components as html
import dash_core_components as dcc
from datetime import datetime
import numpy as np
import pandas as pd
import time

app = dash.Dash(__name__)

def compute_expensive_data():
    t=datetime.now()
    d = {'time' : pd.Series(np.array([t.minute, t.second]), index=['minute', 'second'])}
    dat = pd.DataFrame(d).to_json()
    time.sleep(3)
    return dat


app.layout = html.Div([
        html.H3(id='original-time'),
        html.Div(id='title-line-children'),
        dcc.RadioItems(
            id='time-dropdown',
            options=[
                {'label': 'Minute', 'value': 'minute'}, {'label': 'Second', 'value': 'second'},
            ],
            value='minute'
        ),
        html.Div(id='nothing'), # this does nothing, just so the value is dynamically computed. A bit hacky I know!
        dcc.Interval(
            id='interval-component',
            interval=10*1000, # 20 seconds in milliseconds
            n_intervals=0
        )
    ])

@app.callback(Output('original-time', 'children'),
              [Input('nothing', 'children')])
def display_current_time(value):
    dat = compute_expensive_data()  # this will be computed when the user loads the page (remains constant throughout the session)
    return 'Original Time: Minute = ' + str(pd.read_json(dat)['time']['minute']) + ': Second = ' + str(pd.read_json(dat)['time']['second'])

@app.callback(
    Output('title-line-children', 'children'),
    [Input('time-dropdown', 'value')])
def render(value):
    dat1 = compute_expensive_data()  # this is computed on demand when the user interacts with the radio buttons
    if value == 'minute':
        printStr = str(pd.read_json(dat1)['time']['minute'])
        outStr = 'Minute = ' + printStr
    elif value == 'second':
        printStr = str(pd.read_json(dat1)['time']['second'])
        outStr = 'Second = ' + printStr
    return outStr

if __name__ == '__main__':
    app.run_server(debug=True)

Also, if you really need to store data, you can use the dcc.Store component, which is especially built for that.

Let me know if this works. Good luck!

Thanks for the reply but notice that when the aplication is running compute_expensive_data() definition it stops updating the dash based on the RadioItems, you see?
Keep switching between buttons while the app run and notice how it stops updating the outStr object.

Thanks in advance!