How to elegantly handle a very large number of Input/State in callbacks?


#1

My dashboard has a bunch of filters, I was looking for a way to handle them neatly in my code. It looks really untidy to have 23 inputs to a function.
Is there a way to have an array of inputs?

@app.callback(
    Output("div", "children"),
        [Input("plot-button", "n_clicks")],
        [State('input1', 'on'),
         State('input2','value'),
         State('input3','value'),
         State('input4','start_date'),
         State('input5','end_date'),
         State('input6','values'),
         State('input7','value'),
         State('input8','value'),
         State('input9','value'),
         State('input10','options'),
         State('input11','value'),
         State('input12','value'),
         State('input13','value'),
         State('input14','value'),
         State('input15','value'),
         State('input16','value'),
         State('input17','value'),
         State('input18','value'),
         State('input19','value'),
         State('input20','value'),
         State('input21','value'),
         State('input22','value'),])
def generate_graph(nclicks, input1, input2, input3, input4, input5, input6, input7, input8, input9, input10, input11, input12, input13, input14, input15, input16, input17, input18, input19, input20, input21, input22):
    //some code
    return

#2

Yes, just do:

def generate_graph(*args):
    ...

*args will be the array of input and state values.


#3

An extension to Philippe’s approach is to have the list of input and states external to the function then form a kwarg dictionary in the function itself so you can refer to inputs via their name rather than position in the args list.

A nice advantage of this is that it these input/state lists can be reused for other callbacks with the same input but different output.

inputs = [Input("plot-button", "n_clicks")]

states = [[State('input1', 'on'),
         State('input2','value'),
         State('input3','value'),
         State('input4','start_date'),
        .........
         State('input19','value'),
         State('input20','value'),
         State('input21','value'),
         State('input22','value'),]

@app.callback(
Output("div", "children"),
inputs,
states)
def generate_graph(*args):

    input_names = [item.component_id for item in inputs + states]

    kwargs_dict = dict(zip(input_names, args))

    //some code

   ....kwargs_dict['input10']....

   //some code

    return

#4

If you want to avoid having this boilerplate in every callback function then you can wrap your callback functions in a handler that does it for you!

PS. I hope plot.ly does this for us in the future, hint hint @chriddyp

def create_handler(callback_fn, inputs):

    input_names = [item.component_id for item in inputs]

    def handler(*args):
        kwargs_dict = dict(zip(input_names, args))
        return callback_fn(**kwargs_dict)

    return handler

inputs = [Input("plot-button", "n_clicks")]

states = [[State('input1', 'on'),
         State('input2','value'),
        .........
         State('input22','value'),]

def generate_graph(**kwargs):

    //some code
   ....kwargs['input10']....
   //some code
    return

# Register the callback with the wrapped function
app.callback(
    Output(component_id='cluster-table', component_property='children'),
    inputs,
    states,
    )(create_handler(generate_graph, inputs + states))

#5

I like this approach, maybe we could have an option on the callback to have named argument instead or positional arguments.


#6

Thanks @sjtrny, this looks neat.


#7

Just to tidy this up into a proper decorator

def dash_kwarg(inputs):

    def accept_func(func):

        @wraps(func)
        def wrapper(*args):
            input_names = [item.component_id for item in inputs]
            kwargs_dict = dict(zip(input_names, args))
            return func(**kwargs_dict)

        return wrapper

    return accept_func

inputs = [Input("plot-button", "n_clicks")]

states = [[State('input1', 'on'),
         State('input2','value'),
        .........
         State('input22','value'),]

@app.callback(
    Output(component_id='cluster-table', component_property='children'),
    inputs,
    states,
)
@dash_kwarg(inputs + states)
def generate_graph(**kwargs):

    //some code
   ....kwargs['input10']....
   //some code
    return