Behavior changes after update

Hi - after updating DASH, our application behaves differently (speakerbench.com). In the application with the name Datasheet Creator (appc.py), we experience that the browser remembers data from previous sessions, or maybe just picks random data from a list in the IMPORT tab.

Refreshing the browser sometimes selects different drivers from the import list. Also, the behavior from the server is different than from running locally (i.e. development on our own PC’s), which makes debugging difficult. Specifically, running locally does not select a driver (at random).

The data persistence is very strong, and even clearing the browser data and cache does not remove the driver choice.

Package Version


dash 2.8.1
dash-bootstrap-components 1.3.1
dash-core-components 2.0.0
dash-daq 0.5.0
dash-html-components 2.0.0
dash-katex 0.0.1
dash-table 5.0.0

Python 3.8

Please help.

With kind regards,
Claus

Hello @cfuttrup,

To me it feels like you have global variables set up. I loaded it, and I am guessing that I got the latest info from whatever someone else entered.

I would think that is why running it locally doesnt cause issues.

Hi jinnyzor (Bryan?)

We are two programmers, I haven’t spoken to Jeff about your reply yet, so this is just a quick shoot on my part. I’ll come back with more.

I think we have global variables. This is what we do in index.py:

app.layout = html.Div([
dcc.Location(id=‘url’,refresh=False),
dcc.Store(id=‘memory’,storage_type=‘memory’),
dcc.Store(id=‘tuning’,storage_type=‘memory’),
html.Div(id=‘page-content’)
])

Is this OK, or problematic?

We use these data containers to make it possible for our users of speakerbench to move between appc.py (Datasheet Creator) and app_box.py (Box Simulation) and the data is kept (not deleted).

Is there a better way?

I don’t see a problem with this, AFAIK the data stays in the browser, but please explain more in-depth…

We are using gunicorn --workers 4

The issue is the fact that an update of DASH modules created a big difference in how the app works. Everything was working fine before the update.

I think our problem is different. Our problem is more related to a small table of data, and these data are read into the browser, more or less at random, which is very strange.

I wonder if our DASH upgrade includes a new bug?

Best regards,
Claus

Correct. :slight_smile:

No, dcc.Stores would not cause the issue you are describing.

It seems like somewhere you have something like df = x and then you use df throughout your app.

It could be a new bug, but it might be related more to this. :slight_smile:

Hi Bryan, and the community

I talked with Jeff last weekend, and we’re still staring at this, but during our talk we didn’t arrive at any conclusion.

We cleaned the code for global variables in 2021, when we were in a dialog with Plotly about a code review, which was executed by Alex Hsu (from Plotly) with Ben Postlethwaite on the side.

I believe there are no bad global variables anymore. Existing global variables are constants i.e. they never change, which also means that if they get mixed up with another user’s session, it doesn’t matter.

Besides we renamed all functions/procedures and variables so each of them have a different name (across the entire code base) to ensure we don’t have duplicates, or scope issues.

Our problem is more tricky as such. Besides, please note the code behaved as desired until we updated DASH to the latest status in January.

The mentioned IMPORT tab was a big headache for a long time, and might still be.
It is implemented with a callback monitor with callback_context (ctx), which is looking for what caused the callback, and if a certain radio-button in the IMPORT list is selected, loads that particular dataset.

My thoughts about this points to a change of DASH, as to how callback_context works, e.g. how it’s initialized (or lack of), since we observe the data is loaded already when appc.py is loaded (initialized, without selection).

Best regards,
Claus

@cfuttrup,

What is the callback that is associated with the radio buttons? Can we see it?

Hi Bryan and the community

Bryan - thank you for showing an interest in our issue.
Yes, of course you can see the callback.
The callback uses a monitor that I will document first - I think we found it in a code example somewhere:

Global variable:

appdebug=False          # used in monitor_cb (which is used in appc.py)

def monitor_cb(cb,ctx):

    if not ctx.triggered:
        x = 'NONE'
    else:
        pid = ctx.triggered[0]['prop_id']
        val = ctx.triggered[0]['value']
        if pid == 'mytable.selected_rows':
            x = 'import'
        elif pid == 'memory.data':
            x = 'memory'
        elif pid == 'upload-paramobj.contents':
            if val is None:
                x = 'NONE'
            else:
                x = 'upload'
        elif pid == 'button_appc.n_clicks':
            x = 'appc-click'
        else:
            x = val

    if appdebug:
        print(cb+': '+str(x))

    return x

As we can see from above code, the x = ‘import’ is what decides that appc.py shall import data from the table in the IMPORT tab,
which if I am correct means that callback is called as soon as the appc.py application is loaded,
and pid contains a value (= mytable.selected_rows) even before anyone has pushed any buttons. This is strange.

Here’s the callback in appc.py:

# When "Apply" is clicked, capture data, process, return data object as output
@app.callback([Output('memory','data'),
               Output('myimport','children')],
              [Input('button_appc','n_clicks'),
               Input('mytable','selected_rows'),
               Input('upload-paramobj','contents')],
              appc_state_all) # appc_state_all -> [ids,datafile,memory]
def cb_appc_mem(n,r,c,manufacturer,brand,model,provider,date,comments,volt,git,
                re,leb,rss,ke,le,bl,mms,c0,r0,f0,beta,lamb,sd,dd,
                cms,rms,l3,qms,qes,qts,fs,vas,
                temp,pres,ah,cs,rho,xmax,
                fname,spk):
    x = monitor_cb('appc_cb_mem  ',callback_context)    # monitoring what caused the callback
    return appc_update_spk(x,r,c,manufacturer,brand,model,provider,date,comments,volt,git,
                   re,leb,rss,ke,le,bl,mms,c0,r0,f0,beta,lamb,sd,dd,
                   cms,rms,l3,qms,qes,qts,fs,vas,
                   temp,pres,ah,cs,rho,xmax,
                   fname,spk)

In the above comment it says when ‘Apply’ is clicked, but the comment is old/incomplete.
Actually monitor_cb is looking to see what caused the callback, and appc_update_spk then acts according to that.

All the variables (re, leb and so on) are elements in a loudspeaker datasheet,
which is loaded from the library of IMPORTS. For our problem, they can be ignored.

The IMPORT tab is one among several tabs in appc.py … below you see what it looks like in the code,
and it uses a global variable named tardict, which contains all the data in the IMPORT table
(but no information about which is selected, if any).
Same logic as before, this dictionary is a ‘constant’ and the content doesn’t change, never.
The fact that this data is loaded from a .tgz file is just our rudimentary version of a database
(which noone can write to - any changes here require we do it manually).

# The tardata information needs to be global (stored in tardict)
tardata = tarfile.open('assets/all.tgz')

tardict = []
for x in tardata.getmembers():
    f = tardata.extractfile(x)
    a = space_add(json.load(f))
    tardict.append(a)

tardata.close()

#=======================================================================
# Assemble layout
#
def build_editortabs():
    editortabs = []
    editortabs.append(dcc.Tab(label='General',children=build_generaltab(),
                             style=tab_style,selected_style=tab_style_sel))
    editortabs.append(dcc.Tab(label='Advanced',children=build_advancedtab(),
                             style=tab_style,selected_style=tab_style_sel))
    editortabs.append(dcc.Tab(label='Simple',children=build_simpletab(),
                             style=tab_style,selected_style=tab_style_sel))
    editortabs.append(dcc.Tab(label='Import',children=build_importtab(tardict),
                             style=tab_style,selected_style=tab_style_sel))
    editortabs.append(dcc.Tab(label='JSON Summary',children=html.Div(id='tab-summary'),
                             style=tab_style,selected_style=tab_style_sel))
    editortabs.append(dcc.Tab(label='Readme',children=readme_appc(),
                             style=tab_style,selected_style=tab_style_sel))
    return editortabs

I hope this explains how we have implemented this functionality.

With kind regards,
Claus

Have you tried adding prevent_initial_call=True to that callback?

Hi Bryan, et al.

Tonight I tried to add above mentioned boolean to the callback - see code below - and restarted gunicorn on the server, but after a first initial run I get the same error again.

@app.callback([Output('memory','data'),
               Output('myimport','children')],
              [Input('button_appc','n_clicks'),
               Input('mytable','selected_rows'),
               Input('upload-paramobj','contents')],
              appc_state_all, prevent_initial_call=True) # appc_state_all -> [ids,datafile,memory]
def cb_appc_mem(n,r,c,manufacturer,brand,model,provider,date,comments,volt,git,
                re,leb,rss,ke,le,bl,mms,c0,r0,f0,beta,lamb,sd,dd,
                cms,rms,l3,qms,qes,qts,fs,vas,
                temp,pres,ah,cs,rho,xmax,
                fname,spk):
    x = monitor_cb('appc_cb_mem  ',callback_context)    # monitoring what caused the callback
    return appc_update_spk(x,r,c,manufacturer,brand,model,provider,date,comments,volt,git,
                   re,leb,rss,ke,le,bl,mms,c0,r0,f0,beta,lamb,sd,dd,
                   cms,rms,l3,qms,qes,qts,fs,vas,
                   temp,pres,ah,cs,rho,xmax,
                   fname,spk)

Your proposal implies looking into advanced callback: Advanced Callbacks | Dash for Python Documentation | Plotly
This page hints at when above boolean doesn’t work. I’ll keep searching …

Best regards,
Claus

Should your x return as a straight, ‘None’? Because right now, it returns as a concatenated string with the ‘appc_cb_mem’.

Hi Bryan and the community

Today I tried to load speakerbench into various browsers, and it looks like things are working as expected now.

Bryan - I’ll look into your comment anyway, see if we can improve our code + I will consider updating our way to monitor what caused the callback - it looks to me like things have improved in DASH since we coded this bit a couple of years ago.

Cheers,
Claus