And they have the same order as the order I receive my arguments in the callback. And in Python 3.7+ dictionaries order is guaranteed to be the same as creation / insertion order.
So can I rely on the order of these callback_contexts? It will allow me to write some very clean code if so.
This isn’t officially part of the API until we use something official and intentional like OrderedDict. I don’t imagine that this will change, but it could happen.
Here’s an issue to track progress on this. We’d also probably accept a PR on this too.
I understand for Python 2.7 and pre-(CPython 3.6 / Python 3.7) you can’t guarantee the order due to the the lack of Dictionaries have order. But as long as I am in CPython 3.6+/ Python 3.7+ I can assume same order as order of arguments?
Let me give you some sample code that I generated to demonstrate how attractive this ends up being:
import dash
import dash_html_components as html
from random import randint
NUMBER_OF_BUTTONS = 30
app = dash.Dash(__name__)
buttons = []
for button_num in range(NUMBER_OF_BUTTONS):
buttons.append(
html.Div(
html.Span(
[html.Button('On / Off',
id=f'on-off-button-{button_num}'),
html.Span(' On',
id=f'on-text-{button_num}',
style={'display': 'none',
'color': f'#{randint(0, 255):02X}'
f'{randint(0, 255):02X}'
f'{randint(0, 255):02X}'})]
)
)
)
app.layout = html.Div(buttons)
# Single callback
on_off_outputs = []
on_off_inputs = []
on_off_states = []
for button_num in range(NUMBER_OF_BUTTONS):
on_off_outputs.append(dash.dependencies.Output(f'on-text-{button_num}', 'style'))
on_off_inputs.append(dash.dependencies.Input(f'on-off-button-{button_num}', 'n_clicks'))
on_off_states.append(dash.dependencies.State(f'on-text-{button_num}', 'style'))
@app.callback(output=on_off_outputs, inputs=on_off_inputs, state=on_off_states)
def on_off_change(*args):
if all(v is None for v in dash.callback_context.inputs.values()):
raise dash.exceptions.PreventUpdate
triggered_name = dash.callback_context.triggered[0]['prop_id']
triggered_value = dash.callback_context.triggered[0]['value']
return_values = []
for (input_name, input_value), state_value in zip(dash.callback_context.inputs.items(),
dash.callback_context.states.values()):
if input_name == triggered_name:
if triggered_value % 2 == 1:
state_value['display'] = ''
return_values.append(state_value)
else:
state_value['display'] = 'None'
return_values.append(state_value)
else:
return_values.append(state_value)
return return_values
if __name__ == '__main__':
app.run_server()
In this case the number of buttons is a variable but we have one very simple callback that we didn’t need to do any name look-ups on, just iterate of input and state. I suspect this works so well that unless warn people not to do it if you ever break it in the future you will get bug reports