Describe your context
I am trying to use a Store that could be written by multiple callbacks (to circumvent the 1 output can only be updated by one callback). The logic (hack?) looks sound and works except that I get a problem with a callback that is triggered with
dash.callback_contract.triggered == [{'prop_id': '.', 'value': None}] each time an unrelated Div is updated (ie not only on the first firing at the beginning of the app) and is moreover triggered twice.
Here is the sample app with the get_multi_store function generating a Div with multiple stores (one core and one per writer).
importdashimportdash_core_componentsasdccimportdash_html_componentsashtmlfromdash.dependenciesimportInput, Output, Stateapp=dash.Dash()
defget_multi_store(id_store, id_writers, mode="replace"):
"""Generate a store that can be written by multiple writers. For input, use Input(id_store, "data"). For output by writer 'x', use Output(id_store+"-x", "data"). If mode=="replace", the data of the store is replaced when written to it. If mode=="append", the data of the store is appended with the new data. """assertmodein {"replace", "append"}
N=len(id_writers)
id_container=f"{id_store}-container"id_store_root=id_storeid_store_writers= [f"{id_store}-{id_writer}"forid_writerinid_writers]
defget_layout(data):
"""Generate the layout with the root read-only store and the writable stores"""return [dcc.Store(id=id_store_root, data=data)] + [
dcc.Store(id=id_store_writer) forid_store_writerinid_store_writers
]
@app.callback(Output(id_container, "children"), [Input(id_store_writer, "modified_timestamp") forid_store_writerinid_store_writers], [State(id_store_writer, "data") forid_store_writerinid_store_writers]+ [State(id_store_root, "data")], )defupdate_stores(*args):
"""Callback to handle the update of the root store based on the writer stores"""# process args to retrieve the different input/state components# extract data from root store*args, data_root=args# reorganise stores data as [(ts1, data1), ...]stores_info=list(zip(args[:N], args[N:]))
# keep stores with timestamp defined and sort them by the timestampstores_info=sorted(
((ts, data) for (ts, data) instores_infoiftsisnotNone), key=lambdaitem: item[0]
)
# print(stores_info)# print(dash.callback_context.triggered)ifnotstores_info:
# no store updatedreturndash.no_update# in function of mode, update the data_rootifmode=="replace":
# replace the data_root by the more recent data store writets, new_data=stores_info[-1]
asserttsifnew_data==data_root:
returndash.no_updatedata_root=new_dataelse:
# append to data_root all data store writes (chronologically)data_root.extend(dataforts, datainstores_infoifts)
# rerender the layout with the new datalayout=get_layout(data_root)
returnlayoutinitial_data=Noneifmode=="replace"else []
layout=html.Div(id=id_container, children=get_layout(initial_data))
returnlayoutapp.layout=html.Div(
[
dcc.Input(id="a", value="0", type="number"),
dcc.Input(id="b", value="0", type="number"),
dcc.Input(id="c", value="0", type="number"),
html.Div(id="output-store"),
get_multi_store("scenario", ["a", "b", "c"], mode="append"),
]
)
# display output store (for debugging purposes)@app.callback(Output(component_id="output-store", component_property="children"), [Input(component_id="scenario", component_property="data")],)defdisplay_store(c):
returnstr(c)
# update a with b@app.callback(Output(component_id="scenario-c", component_property="data"), [Input(component_id="c", component_property="value")],)defset_c(c):
return {"c": c}
# update store with b@app.callback(Output(component_id="scenario-b", component_property="data"), [Input(component_id="b", component_property="value")],)defset_b(b):
return {"b": b}
# update store with a and update c (which triggers and update of store with c)@app.callback( [Output(component_id="c", component_property="value"),Output(component_id="scenario-a", component_property="data"), ], [Input(component_id="a", component_property="value")],)defset_a_and_c(a):
print(dash.callback_context.triggered)
# prints [{'prop_id': '.', 'value': None}]returnfloat(a) /2, {"a": a}
if__name__=="__main__":
app.run_server(debug=True)- replace the result of
pip list | grep dash below
dash 1.18.1
dash-core-components 1.14.1
dash-html-components 1.1.1
dash-renderer 1.8.3
Describe the bug
After the initialization of the app, the Store "scenario" holds the value:
[{'a': '0'}, {'b': '0'}, {'c': 0}]
When increasing the Input "a", the store changes to (as the input 'a' and the input 'c' are successively changed)
[{'a': '0'}, {'b': '0'}, {'c': 0}, {'a': 1}, {'c': 0.5}]
However, I see that the callback set_a_and_c is triggered once when the input 'a' is changed (OK) and twice afterwards with the line print(dash.callback_context.triggered) printing [{'prop_id': '.', 'value': None}]
Expected behavior
I would expect the callback set_a_and_c to be only triggered once with dash.callback_context.triggered==[{'prop_id': 'a.value', 'value': 1}].
Moreover, if I change the input 'c', I also see that the callback set_a_and_c is called twice with the dash.callback_context.triggered==[{'prop_id': 'a.value', 'value': 1}].
If I update the input 'b', then the callback set_a_and_c is called once with the dash.callback_context.triggered==[{'prop_id': 'a.value', 'value': 1}].
Describe your context
I am trying to use a Store that could be written by multiple callbacks (to circumvent the 1 output can only be updated by one callback). The logic (hack?) looks sound and works except that I get a problem with a callback that is triggered with
dash.callback_contract.triggered == [{'prop_id': '.', 'value': None}]each time an unrelated Div is updated (ie not only on the first firing at the beginning of the app) and is moreover triggered twice.Here is the sample app with the
get_multi_storefunction generating a Div with multiple stores (one core and one per writer).pip list | grep dashbelowif frontend related, tell us your Browser, Version and OS
Describe the bug
After the initialization of the app, the Store "scenario" holds the value:
[{'a': '0'}, {'b': '0'}, {'c': 0}]
When increasing the Input "a", the store changes to (as the input 'a' and the input 'c' are successively changed)
[{'a': '0'}, {'b': '0'}, {'c': 0}, {'a': 1}, {'c': 0.5}]
However, I see that the callback
set_a_and_cis triggered once when the input 'a' is changed (OK) and twice afterwards with the lineprint(dash.callback_context.triggered)printing[{'prop_id': '.', 'value': None}]Expected behavior
I would expect the callback
set_a_and_cto be only triggered once withdash.callback_context.triggered==[{'prop_id': 'a.value', 'value': 1}].Moreover, if I change the input 'c', I also see that the callback
set_a_and_cis called twice with thedash.callback_context.triggered==[{'prop_id': 'a.value', 'value': 1}].If I update the input 'b', then the callback
set_a_and_cis called once with thedash.callback_context.triggered==[{'prop_id': 'a.value', 'value': 1}].