Manage many callbacks to the same output object

Good Morning community,
I came from R Shiny and i just started to use Dash ! it’s great!!! Congratulations to all developers.

I’m facing with a logic problem regarding inputs/outputs/states.
Basically, my app should do this steps:

  1. Get a sql table from my server ( df )
  2. Filter this table, based on three user filters. ( 1° callback )
  3. Update some columns values, based on user inputs. ( 2° callback )
  4. Save back user inputs values to sql table. ( 3° callback )

I get an error about duplicate outputs and i’can’t solve it… [dash.exceptions.DuplicateCallbackOutput]

This is my app ( about callbacks script) :

# callback to filter data based on filters selection
@app.callback(
    Output('computed-table', 'data'),
    [Input('ANNO_filter_dropdown', 'value'),
     Input('AP_filter_dropdown', 'value'),
     Input('CANALE_filter_dropdown', 'value')]
     )

def filtered_data(selected_anno, selected_ap, selected_canale):
    dff = df[(df.ANNO_AP == selected_anno) & (df.NUM_AP == selected_ap) & (df.CANALE_AP == selected_canale)]
    return dff.to_dict('records')


########################################################################################################################
# callback to update cells data based on insert into others cells
@app.callback(
    Output('computed-table', 'data'),
    [Input('computed-table', 'data_timestamp')],
    [State('computed-table', 'data')]
    )

def update_columns(timestamp, rows):
    for row in rows:
        try:
            row['Desc_Articolo'] = check.find_desc_art(int(row['Cod_Articolo']))
            row['Costo_Interc'] = check.intercept_cost(int(row['Cod_Articolo']), 20200812)
            row['Stato_Art'] = check.find_stato_art(int(row['Cod_Articolo']))
        except:
            row['Costo_Med_Pond'] = 'NA'
    return rows


########################################################################################################################

# callback to export data
@app.callback(
    Output("output-1", "children"),
    [Input("save-button", "n_clicks")],
    [State("computed-table", "data")]
    )

def filtered_data_to_sql(nclicks, table1):
    if nclicks > 0:
      for value in table1:
        cursor = conn.cursor()
        cursor.execute("UPDATE [Mydb].[dbo].[test_python_table] SET Cessione_Off=? , Pubblico_Off=? WHERE Cod_Articolo=?", (value['Cessione_Off'], value['Pubblico_Off'], value['Cod_Articolo']) )
        conn.commit()
        cursor.close()

    return "Data Submitted"

It is not currently possible to have the same Output for multiple callbacks. Your possible workarounds are

  • Merging the callbacks and checking from dash.callback_context which output to produce.
  • Using CallbackGrouper from dash-extensions. This is currently beta-stage component, but might work in your use case.
1 Like

Thanks np8 for replying.
I’m trying your 2° workaround, using CallbackGrouper from [dash-extensions].
But i get this error :

C:\ProgramData\Anaconda3\python.exe C:/Users/myuser/PycharmProjects/CallbackGrouper/app.py
Traceback (most recent call last):
  File "C:/Users/myuser/PycharmProjects/CallbackGrouper/app.py", line 4, in <module>
    from dash_extensions.callback import CallbackGrouper
  File "C:\ProgramData\Anaconda3\lib\site-packages\dash_extensions\callback.py", line 93, in <module>
    class CallbackCache(CallbackBlueprint):
NameError: name 'CallbackBlueprint' is not defined

What’s wrong ?

It’s probably due to a syntax change being pushed too soon, sorry. If you install the previous version (0.0.23) i think i should work. I am currently working on a new syntax, but its not yet completely ready.

2 Likes

Thanks Emil, it works with dash-extensions==0.0.23.

Here’s another workaround with dash. Use filterable ids, and add a multiple objects that all funnel into the same Div.

In the following example, data-source-state is a Store object that gathers the result of all changes.
Each output callback sends the desired output to a Store object that is inside the Div. All the Store objects in the callbacks should have the same type. Then they all trigger the same callback function, the callback figures which one is triggered and picks that value, and passes it to the funnel output. If for any reason this function is called while none of the objects are actually triggered, then just return the same state.

data_source_combiner = html.Div([
    dcc.Store(id={'type': 'data-source', 'index': 'case1'}),
    dcc.Store(id={'type': 'data-source', 'index': 'case2'}),
    dcc.Store(id={'type': 'data-source', 'index': 'case3'}),
    dcc.Store(id={'type': 'data-source', 'index': 'case4'}),
], id='data-source-combiner-div', style={'display': 'none'})

@app.callback(
    Output('data-source-state', 'data'),
    Input('data-source-combiner-div', 'children'),
    Input({'type': 'data-source', 'index': ALL}, 'data'),
    State('data-source-state', 'data'),
)
def combine_states(triggers, values, current_state):
    children = [t['props']['id']['index'] for t in triggers]
    ctx = dash.callback_context
    propped = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else ''
    propped = re.search('"index":".*?"', propped).group()[9:-1]
    try:
        idx = children.index(propped)
        return values[idx]
    except ValueError:
        return current_state

@app.callback(
    Output({'type': 'data-source', 'index': 'case1'}, 'data'),
    Input('some-element', 'n_clicks'),
)
def a_callback_func(n_clicks):
    if n_clicks:
        return 'case output'