Pattern matching Callback with ALL in output. Is this syntax seriously correct?

Hi there,

simple code, there’s a list of labels and if the list of labels updates, the options for all dropdown menus should update accordingly. I first show you how I intuitively wrote the code and tell you why:

@app.callback(
    Output({'type': 'add-label-radio-input', 'index': ALL}, 'options'),
    Input('label-list', 'children'),
)
def update_label_options(label_list, old_options):
    """updates  options for the label selection when the label list changes.
    The first element of the label list ist the label list header. Therefore the
    list comprehension `labels=...` starts at label_list index 1.

    Args:
        label_list: children from the label list
        current_dataset: current dataset info and data

    Returns:
        updated radio options.
    """
    if not dash.callback_context.triggered[0]['value']:
        raise dash.exceptions.PreventUpdate
    # the props children stuff is getting the value from html.H6 element of the
    # label card.
    labels = [list_item['props']['children'][1]['props']['children']
              for list_item in label_list[1:]] 
    return [{"label": label, "value": label} for label in labels]

My thinking was, that if I have a dynamically generated elements of which I want to update a dynamic number, ALL is obviously what I want to have. ALL to me implies that I want the same output to go to all these components. Therefore I thought that I can just return the desired output, in this case a list of options as shown, and dash will deliver that to all components that the pattern matching matched.

That doesn’t work though… Dash requires a list of outputs equal to the number of matched components that need to be updated. I find that a bit odd.

This is how I got it to work:

@app.callback(
    Output({'type': 'add-label-radio-input', 'index': ALL}, 'options'),
    Input('label-list', 'children'),
    State({'type': 'add-label-radio-input', 'index': ALL}, 'options')
)
def update_label_options(label_list, old_options):
    """updates  options for the label selection when the label list changes.
    The first element of the label list ist the label list header. Therefore the
    list comprehension `labels=...` starts at label_list index 1.

    Args:
        label_list: children from the label list
        current_dataset: current dataset info and data

    Returns:
        updated radio options.
    """
    if not dash.callback_context.triggered[0]['value']:
        raise dash.exceptions.PreventUpdate
    # the props children stuff is getting the value from html.H6 element of the
    # label card.
    labels = [list_item['props']['children'][1]['props']['children']
              for list_item in label_list[1:]]
    options = [{"label": label, "value": label} for label in labels]
    return [options for why_though in range(len(old_options))]

I added the State of the matched components just to find out how many there are and create a list of outputs (all equal) of the required length. This seems to me like bad code ^^
Is this how I am supposed to use ALL matched Output?
Is there a reason for why that is so? Use cases where you want to match ALL outputs and then send different values to these outputs?! I was not able to infer the order in which matched outputs appear in the list as all got the same value anyway.

I would really appreciate some feedback/input on this. Thanks in advance!

cheers,

ZBN

I also encountered this and got a little confused. Here is how I believe it works under the hood, though someone more familiar with pattern matching callbacks should correct me if I’m wrong.

If you provide a single value that is not a list, then that value will be passed to all matching components as we’d expect. However, if we pass a list, I believe Dash is interpreting it as “we will index this list and pass each index to its corresponding component”. I.e. the first item of the list is going to the first item, the second to the second item, and so on.

Since you are returning a list of options, Dash wants to provide each option as the output value. You’ll have to provide a list of list of options to accomplish this.

I get around this a slightly different way:

from dash import callback_context

@callback(
    output=dict(
        options=Output({'type': 'input', 'index': ALL}, 'options')
    ),
    ... # remaining inputs and state variables
)
def method(inputs):
    outputs = len(callback_context.outputs_grouping['options'] # provides the full list of output items under 'options' on the current page of the dash app
    ... # the rest of your code
    return {'options': [options for _ in range(outputs)]}

Hi,

thanks for shedding some light into this, that makes a lot of sense. Your solution is definitely better, I’ll change my code accordingly!

I’d like the docs about Pattern matching callbacks to mention this, that would be really nice. There’s no example for an ALL matching Output afaik.

1 Like

Perhaps @adamschroeder or @chriddyp could help get some of this information in the docs.

One additional thing that I just found out!
The return value must look like this:

{'options': [options for _ in range(outputs)]}

because the output grouping expects a dictionary with the key options, and the value being a list with options for each output.
Hm, I guess I have to get used to the output groupings syntax, hehe.

Thanks for your help!

1 Like

Thank you @ZBN_FREEGHOST for your question and thank you @raptorbrad for the solution and suggestion. I shared your documentation recommendation with our technical writer.

Thank you,

Thank you for pointing that out. I updated my answer to reflect the change.

Hi @adamschroeder I was wondering if there is any updates on this one. I am struggling to implement this in the case of the code below.

    @dashapp.callback(
        Output("quantity-icon-1", "icon"),
        Output("quantity-icon-2", "icon"),
        Input("sale-units-radio", "value"),
    )
    def _update_figures(
        sale_units,
    ):
        if sale_units == "ItemExTax" or sale_units == "ItemTotal":
            return (
                "carbon:currency-dollar",
                "carbon:currency-dollar",
            )
        else:
            return (
                "carbon:bottles-01",
                "carbon:bottles-01",
            )

I haven’t been able to get solution by @raptorbrad working

Hello @lex1!

Your callback is not a pattern matching callback. If you don’t use parenthesis in callback head then do not use them in the return statement. Try this:

 @dashapp.callback(
        Output("quantity-icon-1", "icon"),
        Output("quantity-icon-2", "icon"),
        Input("sale-units-radio", "value"),
    )
    def _update_figures(
        sale_units,
    ):
        if sale_units == "ItemExTax" or sale_units == "ItemTotal":
            return "carbon:currency-dollar", "carbon:currency-dollar"
        else:
            return "carbon:bottles-01", "carbon:bottles-01"

If it don’t work please run the code with debug=True and check the error message.

1 Like

Hi thanks for your reply. Sorry I should have put the code I attempted but I only put the starting code. I intend to have dozens of outputs, so I want to pattern match rather than have to write and keep track of dozens of output IDs.

DashIconify(icon="carbon:currency-dollar", id="{'type': 'quantity-icon', 'index': 1} )
DashIconify(icon="carbon:currency-dollar", id="{'type': 'quantity-icon', 'index': 2} )
 @dashapp.callback(
        output=dict(
            icons=Output({'type': 'quantity-icon', 'index': ALL}, 'icons')
        ),
        Input("sale-units-radio", "value"),
    )
    def _update_figures(
        sale_units,
    ):
        outputs = len(callback_context.outputs_grouping['icons'] 
        if sale_units == "ItemExTax" or sale_units == "ItemTotal":
            return {'icons': ["carbon:currency-dollar" for _ in range(outputs)]}
        else:
            return {'icons': ["carbon:bottles-01" for _ in range(outputs)]}

First pylance complaint is ‘Positional argument cannot appear after keyword arguments’ but I’m don’t see how I can just put ‘Output({‘type’: ‘input’, ‘index’: ALL}, ‘icons’)’ even though that would be ideal.

Also if i can avoid having to specify each index, that would be nice too.

hi @lex1
Would you be able to add a complete code example – if your code is too long, just share a minimal reproducible example.

I’d like to run it on my computer.

Ok so I got this working. Input must be above output.

Min reproducible below, however, I don’t really understand what’s happening with the syntax, specifically defining a multi-level dict within a parameter.

It would also be great to refer to class rather than make unique id and indexes (will have many icons across my app).

    import dash
    from dash import callback_context, ALL
    from dash.dependencies import Input, Output
    from dash_iconify import DashIconify
    import dash_mantine_components as dmc

    app = dash.Dash(__name__)

    app.layout = dmc.Stack(
        [
            DashIconify(
                icon="carbon:currency-dollar", id={"type": "quantity-icon", "index": 1}
            ),
            DashIconify(
                icon="carbon:currency-dollar", id={"type": "quantity-icon", "index": 2}
            ),
            dmc.SegmentedControl(
                id="sale-units-radio",
                value="Quantity",
                data=[
                    {
                        "value": "ItemTotal",
                        "label": "Total",
                    },
                    {
                        "value": "ItemExTax",
                        "label": "Ex. Tax",
                    },
                    {"value": "Quantity", "label": "Quantity"},
                ],
                persistence=True,
            ),
        ],
        align="center",
        spacing="xl",
    )


    @app.callback(
        Input("sale-units-radio", "value"),
        output=dict(icons=Output({"type": "quantity-icon", "index": ALL}, "icon")),
    )
    def _update_figures(
        sale_units,
    ):
        outputs = len(callback_context.outputs_grouping["icons"])
        if sale_units == "ItemExTax" or sale_units == "ItemTotal":
            return {"icons": ["carbon:currency-dollar" for _ in range(outputs)]}
        else:
            return {"icons": ["carbon:bottles-01" for _ in range(outputs)]}


    if __name__ == "__main__":
        app.run_server(debug=True)

I’m going to add to the noise on this post in case someone is looking for this:
I have multiple DataTables on the screen that need to be updated from a DB.
The tables are generated dynamically as machines register themselves with the system.
Each machine has a table and is indexed by hostname:

@callback(Output('table-list', 'children'), Input('host-list-store', 'data'))
def update_tables(hostnames: list[str]):
    ret = []
    for hostname in hostnames:
        tbl = DataTable(id={'type':'host-table', 'index': honstname}, columns=...)
        ret.append(tbl)
    return ret

Note that I’m using the hostname as the index and not some int.

In the interval callback to update the datable data I use Output with “index”: ALL to update all the tables at once.

@callback(Output({'type': 'host-table', 'index': ALL}, 'data'), Input('table-update-timer', 'n_interval'))
def update_table_data(_):
    ret = []
    for output in dash.ctx.outputs_list:  # <-- This list is in the order you need to provide the outputs
        tag = output.get('id')  # 'id' is the default tag
        hostname = tag.get('index')
        if not hostname: continue
        table_data: list = get_host_data(hostname)
        ret.append(table_data)
    return ret

Using this method the user can add arbitrary elements to the UI and the Outputs will adjust.