I'm trying to make an app in the Python Dash framework which lets a user select a name from a list and use that name to populate two other input fields. There are six places where a user can select a name from (the same) list, and so a total of 12 callbacks that need to be performed. My question is, how can I use a single function definition to supply multiple callbacks?
As I've seen other places (here for example), people reuse the same function name when doing multiple callbacks, e.g.
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
This is a ton of identical repetition and is bad if there's a fix I need to implement later. Ideally I'd be able to do something like:
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
@app.callback(
Output('rp-mon2-health', 'value'),
[Input('rp-mon2-name', 'value')]
)
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
However, the above ends up no call back on the first two, only on the last. My code as is, is below.
import json
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output
monster_data = json.loads('''[{
"name": "Ares Mothership",
"health": 14,
"transition": 2
},{
"name": "Cthugrosh",
"health": 7,
"transition": 3
}]''')
monster_names = [{'label': m['name'], 'value': m['name']} for m in monster_data]
monster_names.append({'label': 'None', 'value': ''})
app = dash.Dash(__name__)
def gen_monster(player, i):
name = 'Monster #%d: ' % i
id_gen = '%s-mon%d' % (player, i)
output = html.Div([
html.Label('%s Name ' % name),
html.Br(),
dcc.Dropdown(
options=monster_names,
value='',
id='%s-name' % id_gen
),
html.Br(),
html.Label('Health'),
html.Br(),
dcc.Input(value=11, type='number', id='%s-health' % id_gen),
html.Br(),
html.Label('Hyper Transition'),
html.Br(),
dcc.Input(value=6, type='number', id='%s-state' % id_gen),
], style={'border': 'dotted 1px black'})
return output
app.layout = html.Div(children=[
html.H1(children='Monsterpocalypse Streaming Stats Manager'),
html.Div([
html.Div([
html.Label('Left Player Name: '),
dcc.Input(value='Mark', type='text', id='lp-name'),
gen_monster('lp', 1),
html.Br(),
gen_monster('lp', 2),
html.Br(),
gen_monster('lp', 3)
], style={'width': '300px'}),
html.Br(),
html.Div([
html.Label('Right Player Name: '),
dcc.Input(value='Benjamin', type='text'),
gen_monster('rp', 1),
html.Br(),
gen_monster('rp', 2),
html.Br(),
gen_monster('rp', 3)
], style={'width': '300px'})
], style={'columnCount': 2}),
html.Div(id='dummy1'),
html.Div(id='dummy2')
])
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-state', 'value'),
[Input('rp-mon1-name', 'value')]
)
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['transition']
else:
return 6
if __name__ == '__main__':
app.run_server(debug=True)
If a Dash app has multiple callbacks, the dash-renderer requests callbacks to be executed based on whether or not they can be immediately executed with the newly changed inputs. If several inputs change simultaneously, then requests are made to execute them all.
In Dash, any "output" can have multiple "input" components. Here's a simple example that binds five inputs (the value property of two dcc.
dash-devices is another async port based on quart . It's capable of using websockets even for callbacks, which makes it way faster than either of dash or async-dash .
suppress_callback_exceptions: check callbacks to ensure referenced IDs exist and props are valid. Set to True if your layout is dynamic, to bypass these checks. So there isn't really a difference in the examples you linked on their own.
You could do something like this:
def update_health(monster):
if monster != '':
relevant = [m for m in monster_data if m['name'] == monster]
return relevant[0]['health']
else:
return 11
@app.callback(
Output('rp-mon1-health', 'value'),
[Input('rp-mon1-name', 'value')]
)
def monster_1_callback(*args, **kwargs):
return update_health(*args, **kwargs)
@app.callback(
Output('rp-mon2-health', 'value'),
[Input('rp-mon2-name', 'value')]
)
def monster_2_callback(*args, **kwargs):
return update_health(*args, **kwargs)
@app.callback(
Output('rp-mon3-health', 'value'),
[Input('rp-mon3-name', 'value')]
)
def monster_3_callback(*args, **kwargs):
return update_health(*args, **kwargs)
Now the function that contains the logic is only written once, and the other functions are simple passthroughs that you shouldn't ever need to update.
I had the exact same issue. Loads of callbacks that differed only with the Input and Output ids. The following worked for me (I'll provide an example from my code, but the idea is the same)
def rangeslider_tocalendar(output, input):
@app.callback([Output(output, 'start_date'),
Output(output, 'end_date')],
[Input(input, 'value')])
def repeated_callback(range_slider):
cal_start = datetime.date.fromordinal(range_slider[0])
cal_end = datetime.date.fromordinal(range_slider[1])
return cal_start, cal_end
rangeslider_tocalendar('date-range', 'range-slider')
I wrapped the repeating callbacks in a function rangeslider_tocalendar()
. Then I just called the wrapper function and pass in the input and output ids. Kept spaghetti off my plate.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With