📣 Wildcards & Dynamic Callback Support - In Development, Looking for Feedback

Hello everybody -
We are working on a big new feature in Dash: “Wildcard Callbacks”. This feature will enable you to write callbacks that listen to or update a dynamic number of elements.

This was one of the deep, longstanding issues with Dash, originally discussed 2 years ago in Dynamic Controls and Dynamic Output Components.

We’d love your feedback. You can comment on the implementation or try it out in our pull request on GitHub: https://github.com/plotly/dash/pull/1103

6 Likes

Is there a simpler example? I’m not groking this entirely, for example in this callback:

@app.callback(
    Output({"item": ANY}, "style"),
    [Input({"item": ANY, "action": "done"}, "value")]
)

Input mostly looks the same to me and I would describe as “All elements that have id with item as a key and action as a key with it’s value done”, but Output means 1 of 3 possible things to me:

  1. All elements that have id with item as a key (including circular references)

  2. All elements that have id with item as a key and no other keys in their id

  3. All elements that have id with item as a key but automatically excluding circular references if input and output are the same or create circular references somewhere in the possible Directed Graph.

Am I getting Input right and are one of my guesses at what Output means correct?

(2) is the interpretation we are using - keys must match exactly between the id and the wildcard pattern. There has been some discussion internally about possibly allowing you to relax that constraint as a way to make these callbacks even more general, but I think even if we eventually do that it would be an additional opt-in feature.

And re: the meaning of the Input, there was a lot of confusion about ANY vs ALL - so we changed ANY to MATCH, and I’ve updated the examples in the PR correspondingly.

ALL means every id that matches the pattern is included in the input (or output) so these items will be lists of values rather than single values.

MATCH means there will be a separate invocation of the callback for each distinct value found with that key, and every input & output gets a matching value. So each time the callback is called it will just see a single value: Input({"item": 0, "action": "done"}, "value") will be the input to determine Output({"item": 0}, "style"), and Input({"item": 1, "action": "done"}, "value") will be the input to determine Output({"item": 1}, "style") etc.

Thanks for the info, I’m starting to get the hang of this. I’ll try and create a small demo myself.

I would say it’s all not very super intuitive vs. the rest of Dash, but it’s a complex problem, so a few simple examples as well as deeper examples would help a lot once live :slight_smile:

Hello! @chriddyp

I am confused. Is this like calling an object propiertie but not in the callback?

Let me be more specific.

I am in the need to generate checklists depending of the value of a dropdown.

If I select “option1” I generate 2 checklist (There are shown in tabs) if I select “option 2” I generate 5 checklist( (each one shown in a tab). The need is to obtain the value of each checklist generated (boxes selected). This task is normally done in the callback header, where you can put each checklist as a State (better as State since input will launch an action, and the selection of checkboxes is passive, at least in my page), but if I put then as entry argument in the callback, there will be inconsistencies since “option 1” of dropdown only generates ids “checklist_1” and “checklist_2”. and “option 2” generates “checklist from 1 to 5”, so I can not put all the State() 's because in option 1 an error will appear since checklist_3/4/5 are not generated (at least in option 1, but remember this is only a small example, options are more and number could change to bigger ones).

Like a pythonist way yo solve this maybe something like:

NOTE: I know how many are generated because of a function that consult a database

@app.callback(...)
def function(...):
     value_required = [app.layout['all-tabs']['tabs_1']['checklist_'+str(i)]['value']  for i in range(get_number_of_generated_checklists)]
     .
     .
    .

Does this thread want to improve this? I know this will leave “state” kind of obsolete since I can refer to objects directly in the function without reference then in the callback header.

EDIT:
I review the you code @chriddyp and see something similar to:

app.layout['all-tabs']['tabs_1']['checklist_'+str(i)]['value']

I tried in a dropdown but it does not update with the value selected.

scene: dropdown and buttom. The buttom just make a print:

Note: Toast is just for the ouput of the callback.
Note2: I know this scenario could be done by a callback, but for the previous scenario the need of calling an object inside a function only by the name and propiertie without puttin in the header, is high.

dcc.Dropdown(id='dropdown-1',
                        options=[
                            {'label': i, 'value': i} for i in range(5)
                                ],
                        )
html.Div(id='div-enviar',children=[
                                        dbc.Button("click-me", id="but1", color="primary", className="mr-1")
                                        ]


@app.callback(  [Output("positioned-toast", "is_open")]
                ,inputs=[Input("but1", "n_clicks")])
def click_me(n_clicks):
     print(app.layout['dropdown-1'].options)
     return False

if : print(app.layout['dropdown-1'].options)
the print goes as expected. a list of dictionaries with keys label and value.

then, I suppose that I could call a value so I did:
print(app.layout['dropdown-1'].value)
and the answer is key_error ‘value’ not found.

I thought that was a mistake… but then i tried:
print(app.layout['dropdown-1'])
and clicked the buttom before selecting an option, and after. The answer was the same, The object like a dictionary of the propierties wrotten in code before deploying in the web browser.

So now i am wondering if there is a line needed to “update” the layout, because in the web-broser, the propiertie “value” appears in the object dropdown-1.

Thanks for any clue on solving this!
E.M.

Because the Dash backend is stateless, app.layout does not respond to changes the user makes in the app. (One way to think about this: there may be many users of the app at the same time, all doing different things with it, but there’s only one app.layout on the server side.) So yes, State is still necessary.

It does sound to me as though wildcards would be useful for your case - you could give your checklists IDs like {"list": "region"}, {"list": "country"}, {"list": "sector"}, {"list": "company_size"} etc and in the callback definition Input({"list": ALL}, "value") would give you a list of all the value props (ie a list of lists).

We’re also adding new items to dash.callback_context, tentatively called inputs_list and states_list, that list all the IDs provided to the callback, since that’s no longer known when you’re writing the callback, so you can match up the callback args to the components they came from.

Hello @alexcjohnsonthanks for you answer.

I do not get it easily. Maybe you could help me with an example.

This is how I generate the checklists . This is inside a for where “categoria” is an iterator of a list with type String. This list is obtained from a database so it is replicable any time.
The variable Dict is built before this ‘FOR’.

for categoria in sorted(list(Dict.keys())):
     .
     .
     .
     .
     dbc.Card(dbc.CardBody([ dcc.Checklist(id={'list':'checklist_'+categoria},
                                          options=Dict[categoria]['reportes']
                                          )
                           ]
                          )
             ,className="mt-3",
             )
     .
     .
     .
     return TABS

TABS is a dbc.Tabs()

Remember this checklist are generated by an action (a selection os a dropdown). They are not build in the beging.

With this, how may I built the header of the callback?

I was unable to get the example app to run.

import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, MATCH, ALL

app = dash.Dash(__name__)

app.layout = html.Div([
    html.Div('Dash To-Do list'),
    dcc.Input(id="new-item"),
    html.Button("Add", id="add"),
    html.Button("Clear Done", id="clear-done"),
    html.Div(id="list-container"),
    html.Hr(),
    html.Div(id="totals")
])

style_todo = {"display": "inline", "margin": "10px"}
style_done = {"textDecoration": "line-through", "color": "#888"}
style_done.update(style_todo)


@app.callback(
    [
        Output("list-container", "children"),
        Output("new-item", "value")
    ],
    [
        Input("add", "n_clicks"),
        Input("new-item", "n_submit"),
        Input("clear-done", "n_clicks")
    ],
    [
        State("new-item", "value"),
        State({"item": ALL}, "children"),
        State({"item": ALL, "action": "done"}, "value")
    ]
)
def edit_list(add, add2, clear, new_item, items, items_done):
    triggered = [t["prop_id"] for t in dash.callback_context.triggered]
    adding = len([1 for i in triggered if i in ("add.n_clicks", "new-item.n_submit")])
    clearing = len([1 for i in triggered if i == "clear-done.n_clicks"])
    new_spec = [
        (text, done) for text, done in zip(items, items_done)
        if not (clearing and done)
    ]
    if adding:
        new_spec.append((new_item, []))
    new_list = [
        html.Div([
            dcc.Checklist(
                id={"item": i, "action": "done"},
                options=[{"label": "", "value": "done"}],
                value=done,
                style={"display": "inline"}
            ),
            html.Div(text, id={"item": i}, style=style_done if done else style_todo)
        ], style={"clear": "both"})
        for i, (text, done) in enumerate(new_spec)
    ]
    return [new_list, "" if adding else new_item]


@app.callback(
    Output({"item": MATCH}, "style"),
    [Input({"item": MATCH, "action": "done"}, "value")]
)
def mark_done(done):
    return style_done if done else style_todo


@app.callback(
    Output("totals", "children"),
    [Input({"item": ALL, "action": "done"}, "value")]
)
def show_totals(done):
    count_all = len(done)
    count_done = len([d for d in done if d])
    result = "{} of {} items completed".format(count_done, count_all)
    if count_all:
        result += " - {}%".format(int(100 * count_done / count_all))
    return result


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

The initial div renders, but the buttons don’t work.

dash-dynamic-test

Steps to reproduce:

  1. create a fresh conda environment
  2. clone the repo and check out the 475-wildcards branch
  3. run python setup.py install
  4. copy the above code to a file dash-dynamic-test.py
  5. run python /path/to/dash-dynamic-test.py from outside the repo
1 Like

To try out unreleased code in dash-renderer it’s necessary to build the renderer and install it in editable mode. This is described in https://github.com/plotly/dash/blob/dev/CONTRIBUTING.md - the PR itself contains only the source code, not the final bundle.

I would like to create and destroy callbacks in execution time. I hope this in next update.

best news i’ve got in a while this will make a huge impact in my whole BI infrastructure thanks a lot.

1 Like

when this functionality is planned to be in production ?

Yeah I would like to add I have an application that creates multiple Dash apps at multiple Flask end points, I dynamically create and destroy this Dash apps at runtime so users can completely customize their application with different components that have differently associated callbacks. It’s not pretty…

There’s definitely going to be a large chance of a rewrite with this as dynamic callback support allows things which are just not possible using standard Dash approaches :slightly_smiling_face:

1 Like

@Damian i have something a little bit similar actually i have a set of reports that handle the same filters so i have to dinamically set every callback for every graph or table based on multiple dictionaries that i handle on my project so i hace a function that iterates through them and set callbacks. maybe this will be a better approach since its a mess when a new graph is added dinamically.

1 Like

Haven’t tried it out yet but want to share my excitement to. I have a list of marketing experiments that changes for each sales lead. (e.g. different experiments can be run on different leads, the params are set in Dash) I landed here because I’m trying to figure out how to handle the various different inputs

I wanted to try out the PR, so i tried to follow the instructions on how to build here. However, i encounter an issue during the npm run build:js step,

> dash-renderer@1.2.4 build:js /home/emher/Projects/dash/dash-renderer
> webpack --build release

/home/emher/Projects/dash/dash-renderer/webpack.config.js:46
    ...defaults
    ^^^
SyntaxError: Unexpected token ...

Is this a known issue? Or have i done something wrong?