Bug: dcc.Store component interferes with dcc.Interval update

So, after lots of painful debugging in my app, I found that one of my callbacks, storing a simple binary value in dcc.Store, is somehow interfering with another callback that relies on a dcc.Interval component to fire it off every few seconds or so.

MWE:

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.dependencies import Input, Output, State

import datetime as dt
import pandas as pd
import numpy as np
import subprocess
import sys
import os
from pathlib import Path


###### Setup ######

## Pointing subprocess to active directory
os.chdir(sys.path[0])

app = dash.Dash(dev_tools_hot_reload=True)
app.scripts.config.serve_locally = True
app.config['suppress_callback_exceptions'] = True

fonts = '12px Verdana, Geneva, sans-serif'


###### Main code ######

app.layout = html.Div(children=[

    html.H3('Twitter App'),
    
    html.Div(children=[
                       html.Label('Twitter id(s)',
                                  style={'grid-row':'1 / 2', 'grid-column':'1 / 2'}),
                       dcc.Input('ScreenName_Input', type='text',
                                  style={'grid-row':'2 / 3', 'grid-column':'1 / 2'}),
    
                       html.Label('# of tweets',
                                  style={'grid-row':'1 / 2', 'grid-column':'2 / 3'}),
                       dcc.Input(id='NumberOfTweets', value=10, type='number',
                                  style={'grid-row':'2 / 3', 'grid-column':'2 / 3'}),
        
                       html.Button(id='screenNames_submit_button', children='Submit',
                                   style={'grid-row':'2 / 3', 'grid-column':'3 / 4'}),
        
                       html.Button(id='time_Stream', children='Stream time',
                                   style={'grid-row':'2 / 3', 'grid-column':'4 / 5'}),
        
                       html.Button(id='pauseStream', children='Pause collection',
                                   style={'grid-row':'2 / 3', 'grid-column':'5 / 6'})

    ],
              style={'display':'grid', 'grid-template-rows':'25px 25px 25px 25px', 
                     'grid-auto-columns': '100px 100px 100px 100px 150px'}
            ),
    
    
    html.Div(children=[
             dash_table.DataTable(id='tweet_table', columns=[
                                                {'name': 'Date', 'id': 'Date'},
                                                {'name': 'Author', 'id': 'Author'},
                                                {'name': 'Len', 'id': 'Length'},
                                                {'name': 'Favs', 'id': 'Favorites'},
                                                {'name': "Retw", 'id': 'Retweets'},
                                                {'name': 'Text', 'id': 'Text'}],
                                  
                                   style_cell = {'whiteSpace':'normal'},
                                  
                                   ## Left-aligning test, setting fonts, and setting some column widths
                                   style_cell_conditional=[{'textAlign':'left',
                                                            'font': fonts},
                                                           {'if':{'column_id':'Date'},
                                                            'width':'40px'}],
                                  
                                   ## Bolding of column titles
                                   style_header={'fontWeight':'bold'},
                                  
                                   ## Horizontal and vertical scrolling
                                   style_table={'maxHeight': '400',
                                                'overflowY': 'scroll',
                                                'minWidth':'500',
                                                'maxWidth':'800'},
                                 
                                   ## Enabling selection of multiple tweets for use in analysis on right
                                   row_selectable = 'multi'
                                  
                                 ),
        
            html.Div(children=[  
            
                html.H1(children='Analysis',
                        style={'margin-top':'-40px'}),
            
                dcc.Graph(id='sentiment_graph', 
                          figure={'data': [{'type':'histogram'}]}
                          
                         )
            ])
    ],

              style={'display':'grid', 'grid-template-columns': '1fr 1fr', 'column-gap':'20px',
                     'margin-top':'-25px'}),
    
    
    ## Saving most recent tweet from stream, as well as
    ## name of most recent csv created in quick cache
    html.Div(children=[
        dcc.Store(id='table_exists_or_not'),
        dcc.Store(id='latest_tweet'),
        html.Div(id='temp'),
        html.Div(id='temp2'),
        dcc.Interval(id='table_update', interval=3*1000, n_intervals=0)
    ])
    
])
                      

###### Callbacks ######


## Random complicated callback simply printing time every 3 seconds

@app.callback(
     Output(component_id='tweet_table', component_property='data'),
    [Input(component_id='table_update', component_property='n_intervals')],
    [State(component_id='ScreenName_Input', component_property='value'),
     State(component_id='NumberOfTweets', component_property='value'),
     State(component_id='table_exists_or_not', component_property='data')]
)
def tweet_table(update_interval, screen_names, number_tweets, table_exists_or_not):
 
    print('tweet_table function ran at least, table_exists_or_not: {}'.format(table_exists_or_not))
    print('Time is {}'.format(dt.datetime.now()))
    return None
          

    
## Callback that stops the dcc.Interval working up above.

# @app.callback(
#     Output(component_id='table_exists_or_not', component_property='data'),
#     [Input(component_id='screenNames_submit_button', component_property='n_clicks_timestamp')],
#     [State(component_id='tweet_table', component_property='data')]
# )
# def table_exists_or_not(submit, table_exists):
    
#     if table_exists:
#         print('Table seems to exist: {}'.format(table_exists))
#         return 1
        
#     else:
#         print('Table does not exist: {}'.format(table_exists))
#         return 0


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

tweet_table only prints the time for me on an interval if the bottom callback is commented out (as it is currently).

How can I fix this bug, as I need both callbacks in my application?

Is this maybe occurring because table_exists_or_not is an output of my second callback and a State input of my first, so the default None being sent into my first callback somehow screws up dcc.Interval?

After further debugging, it seems that this callback,

```
@app.callback(
     Output(component_id='tweet_table', component_property='data'),
    [Input(component_id='table_update', component_property='n_intervals')],
    [State(component_id='ScreenName_Input', component_property='value'),
     State(component_id='NumberOfTweets', component_property='value'),
     State(component_id='table_exists_or_not', component_property='data')]
)
def tweet_table(update_interval, screen_names, number_tweets, table_exists_or_not):
 
    print('tweet_table function ran at least, table_exists_or_not: {}'.format(table_exists_or_not))
    print('Time is {}'.format(dt.datetime.now()))
    return None
```

is the culprit.

Any new callback I create that has my Data Table, tweet_table, as an output, causes the callback controlled by Interval to fail.

Is there any way to get around this irksome bug?

After even more debugging (forgive my haphazard attempts so far), I have nailed down the actual bug: the existence of an Output and a State/Input (either one) referring to the same Data Table (in any way), causes the Interval component to become confused and not work.

Here is my final MWE:

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_table
from dash.dependencies import Input, Output, State

import datetime as dt

###### Setup ######

app = dash.Dash(dev_tools_hot_reload=True)
app.scripts.config.serve_locally = True
app.config['suppress_callback_exceptions'] = True


###### Main code ######

app.layout = html.Div(children=[

    html.H3('Twitter App'),
    
    html.Div(children=[
                       html.Label('Twitter id(s)',
                                  style={'grid-row':'1 / 2', 'grid-column':'1 / 2'}),
                       dcc.Input('ScreenName_Input', type='text',
                                  style={'grid-row':'2 / 3', 'grid-column':'1 / 2'}),
    
                       html.Label('# of tweets',
                                  style={'grid-row':'1 / 2', 'grid-column':'2 / 3'}),
                       dcc.Input(id='NumberOfTweets', value=10, type='number',
                                  style={'grid-row':'2 / 3', 'grid-column':'2 / 3'}),
        
                       html.Button(id='screenNames_submit_button', children='Submit',
                                   style={'grid-row':'2 / 3', 'grid-column':'3 / 4'}),
        
                       html.Button(id='time_Stream', children='Stream time',
                                   style={'grid-row':'2 / 3', 'grid-column':'4 / 5'}),
        
                       html.Button(id='pauseStream', children='Pause collection',
                                   style={'grid-row':'2 / 3', 'grid-column':'5 / 6'})

    ],
              style={'display':'grid', 'grid-template-rows':'25px 25px 25px 25px', 
                     'grid-auto-columns': '100px 100px 100px 100px 150px'}
            ),
    
    
    html.Div(children=[
             dash_table.DataTable(id='tweet_table', columns=[
                                                {'name': 'Date', 'id': 'Date'},
                                                {'name': 'Author', 'id': 'Author'},
                                                {'name': 'Len', 'id': 'Length'},
                                                {'name': 'Favs', 'id': 'Favorites'},
                                                {'name': "Retw", 'id': 'Retweets'},
                                                {'name': 'Text', 'id': 'Text'}]
                                 ),
        
            html.Div(children=[  
            
                html.H1(children='Analysis',
                        style={'margin-top':'-40px'}),
            
                dcc.Graph(id='sentiment_graph', 
                          figure={'data': [{'type':'histogram'}]}
                          
                         )
            ])
    ],

              style={'display':'grid', 'grid-template-columns': '1fr 1fr', 'column-gap':'20px',
                     'margin-top':'-25px'}),
    
    
    html.Div(children=[
        dcc.Store(id='table_exists_or_not'),
        dcc.Interval(id='table_update', interval=3*1000, n_intervals=0)
    ])
    
])
                      

###### Callbacks ######

## First callback
@app.callback(
     Output(component_id='tweet_table', component_property='data'),
    [Input(component_id='table_update', component_property='n_intervals')],
    [State(component_id='ScreenName_Input', component_property='value'),
     State(component_id='NumberOfTweets', component_property='value'),
     State(component_id='table_exists_or_not', component_property='data')]
)
def tweet_table_1(update_interval, screen_names, number_tweets, table_exists_or_not):

    print('Time is {}'.format(dt.datetime.now()))
    return None


## Second callback
@app.callback(
    Output(component_id='table_exists_or_not', component_property='data'),
    [Input(component_id='screenNames_submit_button', component_property='n_clicks_timestamp')],
    [State(component_id='tweet_table', component_property='data_timestamp')]
)
def table_exists_or_not(button_click, table_exists):
    
    if table_exists:
        print('Table seems to exist: {}'.format(table_exists))
        return 1
        
    else:
        print('Table does not exist: {}'.format(table_exists))
        return 0    


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

I am opening a github issue here, to further pursue resolving this bug.

As I suggest in the github issue, try changing your first callback to return an empty list rather than None. None doesn’t appear to be a valid value for the data attribute of the DataTable component.

As I replied in the github issue, that ended up working perfectly. Thanks again for the help ^^!

1 Like