How to change callback execution order after upload?

Hello

I am writing a dash app using dash_bio. dash_bio does not still support the latest dash version, these are the versions used:

dash==0.40.0
dash-bio==0.1.2
dash-core-components==0.45.0
dash-html-components==0.15.0
dash-renderer==0.21.0
dash-table==3.6.0

Nevertheless, I don’t think that the error is linked to the version, see at the end.

In this app, I upload and process a fil and then do several things. I extracted a “small” part that reproduces my issue. I put the code at the end. There are four callbacks

  • The first callback manages the upload component, store the results in a Store component and set the options of two dropdowns: one for selecting data to plot (dropdown-data) and the other for selecting the column I want to display in the table (data-column-selector).
  • A second callback, get the data from the store and return a table with columns according to the dropdown (data-column-selector).
  • A third callback make a fake plot just for debugging (MAP DATA)
  • The last callback draw a colorscale I need in the true app.

The problem is that the first time the select_table_columns callback is fired, the value of the dropdown data-column-selector is None. I don’t understand why dash executes the first callback (the one which will upload data) at the end and not at the beginning as the other callbacks depend on the outputs of the upload callback. Here after is the console output when the app is running, you can see the order of the callback and then a lot of exceptions corresponding to KeyError because the value is None and thus does not correspond to any column of the DataFrame.

Running on http://127.0.0.1:8050/
Debugger PIN: 122-420-937
 * Serving Flask app "app" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: on
Running on http://127.0.0.1:8050/
Debugger PIN: 958-957-993

PLOT COLORBAR

MAP DATA

SELECTED COLUMNS None

UPLOAD DATA

SELECTED COLUMNS None

SELECTED COLUMNS ['A', 'B']

Traceback (most recent call last):
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/pandas/core/indexes/base.py", line 2657, in get_loc
    return self._engine.get_loc(key)
  File "pandas/_libs/index.pyx", line 108, in pandas._libs.index.IndexEngine.get_loc

  File "pandas/_libs/index.pyx", line 132, in pandas._libs.index.IndexEngine.get_loc

  File "pandas/_libs/hashtable_class_helper.pxi", line 1601, in pandas._libs.hashtable.PyObjectHashTable.get_item

  File "pandas/_libs/hashtable_class_helper.pxi", line 1608, in pandas._libs.hashtable.PyObjectHashTable.get_item

KeyError: None

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/dash/dash.py", line 1073, in dispatch
    response.set_data(self.callback_map[output]['callback'](*args))
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/dash/dash.py", line 969, in add_context
    output_value = func(*args, **kwargs)
  File "/Users/gvallver/git/mosaica/dash/test/app.py", line 170, in select_table_columns
    tab_df = df[values]
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/pandas/core/frame.py", line 2927, in __getitem__
    indexer = self.columns.get_loc(key)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/pandas/core/indexes/base.py", line 2659, in get_loc
    return self._engine.get_loc(self._maybe_cast_indexer(key))
  File "pandas/_libs/index.pyx", line 108, in pandas._libs.index.IndexEngine.get_loc

  File "pandas/_libs/index.pyx", line 132, in pandas._libs.index.IndexEngine.get_loc

  File "pandas/_libs/hashtable_class_helper.pxi", line 1601, in pandas._libs.hashtable.PyObjectHashTable.get_item

  File "pandas/_libs/hashtable_class_helper.pxi", line 1608, in pandas._libs.hashtable.PyObjectHashTable.get_item

KeyError: None
Traceback (most recent call last):
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/pandas/core/indexes/base.py", line 2657, in get_loc
    return self._engine.get_loc(key)
  File "pandas/_libs/index.pyx", line 108, in pandas._libs.index.IndexEngine.get_loc

  File "pandas/_libs/index.pyx", line 132, in pandas._libs.index.IndexEngine.get_loc

  File "pandas/_libs/hashtable_class_helper.pxi", line 1601, in pandas._libs.hashtable.PyObjectHashTable.get_item

  File "pandas/_libs/hashtable_class_helper.pxi", line 1608, in pandas._libs.hashtable.PyObjectHashTable.get_item

KeyError: None

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 2463, in __call__
    return self.wsgi_app(environ, start_response)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 2449, in wsgi_app
    response = self.handle_exception(e)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1866, in handle_exception
    reraise(exc_type, exc_value, tb)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 2446, in wsgi_app
    response = self.full_dispatch_request()
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1951, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1820, in handle_user_exception
    reraise(exc_type, exc_value, tb)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/_compat.py", line 39, in reraise
    raise value
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1949, in full_dispatch_request
    rv = self.dispatch_request()
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/flask/app.py", line 1935, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/dash/dash.py", line 1073, in dispatch
    response.set_data(self.callback_map[output]['callback'](*args))
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/dash/dash.py", line 969, in add_context
    output_value = func(*args, **kwargs)
  File "/Users/gvallver/git/mosaica/dash/test/app.py", line 170, in select_table_columns
    tab_df = df[values]
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/pandas/core/frame.py", line 2927, in __getitem__
    indexer = self.columns.get_loc(key)
  File "/anaconda3/envs/dash-mosaica/lib/python3.7/site-packages/pandas/core/indexes/base.py", line 2659, in get_loc
    return self._engine.get_loc(self._maybe_cast_indexer(key))
  File "pandas/_libs/index.pyx", line 108, in pandas._libs.index.IndexEngine.get_loc

  File "pandas/_libs/index.pyx", line 132, in pandas._libs.index.IndexEngine.get_loc

  File "pandas/_libs/hashtable_class_helper.pxi", line 1601, in pandas._libs.hashtable.PyObjectHashTable.get_item

  File "pandas/_libs/hashtable_class_helper.pxi", line 1608, in pandas._libs.hashtable.PyObjectHashTable.get_item

KeyError: None

This is now the code of the app. I do not know if I have to change the callbacks or if there is something about the Store component that I do not clearly understand.

#!/usr/bin/env python3
# -*- coding=utf-8 -*-

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

import pandas as pd
import numpy as np

# plotly colorscales
# must be lowercase for matplotlib
COLORSCALES = ["Blues",
               "Greens",
               "Greys",
               "Hot",
               "Jet",
               "Rainbow",
               "RdBu",
               "Reds",
               "Viridis",
               "YlGnBu",
               "YlOrRd"]

# ---- Set up App ----
app = dash.Dash(__name__)

# Layout
# ------------------------------------------------------------------------------

footer = ""
header = ""

# --- Body: main part of the app ---
body = html.Div(className="container", children=[

    # --- store components for the data
    dcc.Store(id="data-storage", storage_type="memory"),

    # --- upload
    html.H4("Upload xyz file"),
    dcc.Upload(
        id='file-upload',
        children=html.Div(
            className="upload-area",
            children=[
                ' Drag and Drop or ', html.A('Select File')]
        ),
    ),
    html.Div(id="submit_done", className="mt-5"),

    html.Div(children=[
        # --- dash bio Molecule 3D Viewer
        html.Div(id="dash-bio-container", children=[
            html.H4("Structure Viewer"),

            # --- controls
            html.Div(className="control-label", children="Select data"),
            dcc.Dropdown(
                className="control",
                id='dropdown-data',
                placeholder="Select data"
            ),
            html.Div(className="control-label", children="Select colormap"),
            dcc.Dropdown(
                className="control",
                id='dropdown-colormap',
                options=[{"label": cm, "value": cm}
                         for cm in COLORSCALES],
                value="Viridis"
            ),
            html.Div(id="dash-bio-viewer", children=[
                dcc.Graph(id="graph")
            ]),
            dcc.Graph(id='colorbar', config=dict(displayModeBar=False))
        ]),

        # --- Data table
        html.Div(id="data-table-container", children=[
            html.H4("Table"),
            dcc.Dropdown(
                id="data-column-selector",
                multi=True,
            ),
            html.P("Click on atoms to highlight the corresponding lines."),
            html.Div(children=[
                dash_table.DataTable(
                    id="data-table",
                    editable=False,
                    style_cell={'maxWidth': '60px',
                                'width': '60px',
                                'minWidth': '60px',
                                'whiteSpace': 'normal'},
                    style_header={'backgroundColor': 'rgba(60, 93, 130, .25)',
                                  'fontWeight': 'bold',
                                  "border": "1px solid gray",
                                  "fontFamily": "sans-serif"},
                    style_data_conditional = [{'if': {'row_index': 'odd'},
                               'backgroundColor': 'rgba(60, 93, 130, .05)'}],
                    style_data={'border': '1px solid gray'},
                    style_table={"overflowX": "scroll",
                                 "maxHeight": "800px",
                                 "overflowY": "scroll"},
                    # need higher version of dash_table, incompatible with dash-bio
                    # fixed_rows={'headers': True, 'data': 0}
                )
            ]),
        ])
    ])
])

app.layout = html.Div([header, body, footer])


# callbacks
# ------------------------------------------------------------------------------

@app.callback(
    [Output("data-storage", "data"),
     Output("dropdown-data", "options"),
     Output("data-column-selector", "options"),
     Output("data-column-selector", "value")],
    [Input("file-upload", "contents")]
)
def upload_data(content):
    """
    Uploads the data from an xyz file and store them in the store component.
    Then set up the dropdowns, the table and the molecule viewer.
    """
    print("\n\nUPLOAD DATA")

    # read file
    # bla bla with content

    # comute data
    df = pd.DataFrame({k: np.random.random(20) for k in "ABCDEF"})

    # all data for the store component
    all_data = df.to_dict("records")

    # dropdown for table columns
    tab_options = [{"label": name, "value": name} for name in df]
    values = ["A", "B"]

    # options for dropdown for mapped values
    options = [{"label": name, "value": name} for name in df
               if name not in ["C", "F"]]

    return all_data, options, tab_options, values


@app.callback(
    [Output("data-table", "data"),
     Output("data-table", "columns")],
    [Input("data-storage", "modified_timestamp"),
     Input("data-column-selector", "value")],
    [State("data-storage", "data")]
)
def select_table_columns(ts, values, data):
    """
    Select columns for the table
    """

    df = pd.DataFrame(data)

    print("\n\nSELECTED COLUMNS", values)
    tab_df = df[values]
    columns = [{"name": i, "id": i} for i in tab_df]
    data = tab_df.to_dict("records")

    return data, columns


@app.callback(
    Output('graph', 'figure'),
    [Input('dropdown-data', 'value'),
     Input('dropdown-colormap', "value")],
    [State("data-storage", "data")]
)
def map_data_on_atoms(selected_data, cm_name, data):
    """
    Map the selected data on the structure using a colorscale to draw the atoms.
    """

    print("\n\nMAP DATA")

    df = pd.DataFrame(data)

    if selected_data:
        trace = [
            go.Scatter(
                y=df[selected_data],
                mode="lines+markers",
                line=dict(dash="dash"),
                marker=dict(
                    size=20,
                    color=df[selected_data], 
                    colorscale=cm_name),
                hoverinfo="skip",
            ),
        ]
    else:
        trace = []

    figure = go.Figure(
        data=trace,
        layout=go.Layout(
            width=500, height=500,
        )
    )
    return figure


@app.callback(
    Output("colorbar", "figure"),
    [Input('dropdown-data', 'value'),
     Input('dropdown-colormap', 'value')],
    [State("data-storage", "data")]
)
def plot_colorbar(selected_data, cm_name, data):
    """
    Display a colorbar according to the selected data mapped on to the structure.
    """

    print("\n\nPLOT COLORBAR")

    if selected_data:
        npts = 100
        values = pd.DataFrame(data)[selected_data].values
        values = np.linspace(np.nanmin(values), np.nanmax(values), npts)

        trace = [
            go.Contour(
                z=[values, values],
                x0=values.min(),
                dx=(values.max() - values.min()) / npts,
                colorscale=cm_name,
                autocontour=False,
                showscale=False,
                contours=go.contour.Contours(coloring="heatmap"),
                line=go.contour.Line(width=0),
                hoverinfo="skip",
            ),
        ]
        figure = go.Figure(
            data=trace,
            layout=go.Layout(
                width=550, height=100,
                xaxis=dict(showgrid=False, title=selected_data),
                yaxis=dict(ticks="", showticklabels=False),
                margin=dict(l=0, t=0, b=40, r=0, pad=0)
            )
        )
    else:
        figure = go.Figure(
            data=[],
            layout=go.Layout(
                width=550, height=100,
                xaxis=dict(ticks="", showticklabels=False, showgrid=False,
                           title=selected_data, zeroline=False),
                yaxis=dict(ticks="", showticklabels=False, showgrid=False,
                           title=selected_data, zeroline=False),
                margin=dict(l=5, t=0, b=40, r=5, pad=0)
            )
        )

    return figure


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

I also try to run the above app in another environment with the following version of dash:

dash==1.0.1
dash-core-components==1.0.0
dash-html-components==1.0.0
dash-renderer==1.0.0
dash-table==4.0.1

I got the same error.

To prevent updates frrom triggering down the callback tree, raise dash.exceptions.PreventUpdate. You can check if it’s the first callback with a None check. So, something like:

if your_arg is None:
    raise dash.exceptions.PreventUpdate

@chriddyp thank you very much