plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.53k stars 2.07k forks source link

Pattern-Matching Callbacks ALL have inconsistent order in dash 2.18.1 #3063

Open luewh opened 2 weeks ago

luewh commented 2 weeks ago

dash==2.18.1 dash-core-components==2.0.0 dash-html-components==2.0.0 dash-bootstrap-components==1.6.0

BUG reappear in some circumstances | dash 2.18.1

Image

code :

from dash import Dash, dcc, html
from dash import callback, ALL
from dash.dependencies import Input, Output, State
import dash_bootstrap_components as dbc

ip = "localhost"
port = "8848"

processes = ["optimize", "resize", "upscale"]

videoSizesDict = [
    {"label":"480p/SD", "width":854, "height":480},
    {"label":"720p/HD", "width":1280, "height":720},
    {"label":"1080p/FHD", "width":1920, "height":1080},
    {"label":"1440p/2K", "width":2560, "height":1440},
    {"label":"2160p/4K", "width":3840, "height":2160},
    {"label":"4320p/8K", "width":7680, "height":4320},
]

upscaleFactor = [2, 3, 4]

app = Dash(__name__)

app.layout = html.Div([
    dcc.Dropdown(
        processes,
        value=processes[0],
        id="dropdown_processes",
        style={"color": "black"},
    ),
    html.Div(id="div_processParamUI"),
    html.Div(id="text"),
])

def qualityInputUI():
    return [
        html.Div("Div"),
        dcc.Input(
            id={"type": "input", "id": "videoQuality"},
            type="number",
            value=3.0,
        ),
    ]

def sizeInputUI():
    global videoSizesDict
    return [
        html.Div("Div"),
        dbc.Row(
            [
                dbc.Col(
                    dcc.Input(
                        id={"type": "input", "id": "videoWidth"},
                        type="number",
                        value=1920,
                    ),
                    width=3,
                ),
                dbc.Col(
                    html.Button(
                        "X",
                        id="button_sizeSwitch",
                        n_clicks=0,
                    ),
                    width=1,
                ),
                dbc.Col(
                    dcc.Input(
                        id={"type": "input", "id": "videoHeight"},
                        type="number",
                        value=1080,
                    ),
                    width=3,
                ),
                dbc.Col(
                    dcc.Dropdown(
                        [videoSizes["label"] for videoSizes in videoSizesDict],
                        value=[videoSizes["label"] for videoSizes in videoSizesDict][2],
                        id="dropdown_videoSize",
                    ),
                    width=5,
                ),
            ],
            className="g-0",
        ),
    ]

def upscaleInputUI():
    global upscaleFactor
    return [
        html.Div("Div"),
        dcc.Dropdown(
            upscaleFactor,
            upscaleFactor[0],
            id={"type": "input", "id": "upscaleFactor"},
        ),
    ]

@callback(
    Output('div_processParamUI', 'children'),
    Input('dropdown_processes', 'value'),
)
def update_div_processParamUI(selectedProcess):
    if selectedProcess == "optimize":
        return [
            html.H6("H6"),
            *qualityInputUI(),
        ]
    elif selectedProcess == "resize":
        return [
            html.H6("H6"), # comment this line to remove the bug
            *sizeInputUI(),
            *qualityInputUI(),
        ]
    elif selectedProcess == "upscale":
        return [
            html.H6("H6"), # or this
            *upscaleInputUI(),
            *qualityInputUI(),
        ]

@callback(
    Output('text', 'children'),
    Input('div_processParamUI', 'children'),
    State({'type':'input','id': ALL}, 'value'),
)
def show(_, values):
    return str(values)

if __name__ == '__main__':

    app.run(
        host=ip,
        port=port,
        debug=True,
    )

can we have [ {'id':'id1, 'value':3} ] in this format when State({'type':'input','id': ALL}, 'value') like this ?

Originally posted by @luewh in #2368

antonymilne commented 3 days ago

Related to this but not exactly the same. Can we consider the order of wildcards consistent between two inputs in the same callback or is that just regarded an implementation detail?

The below minimal example seems to work as expected right now since the order of the wildcard State/Input is the same so the id is matched up correctly with each value. This is something I'd really like to rely on but don't know whether it's guaranteed to remain the case.

Given that the above bug seems to have reappeared also, it would be great if it were possible to guarantee some ordering here so that we know whether the order is arbitrary or fixed 🙂

Also related issue but not quite the same: https://github.com/plotly/dash/issues/2834.

from dash import Dash, html, callback, Input, ALL, Output, State, dcc

@callback(Output("summary", "children"), State({"id": ALL}, "id"), Input({"id": ALL}, "value"))
def update_summary(ids, values):
    summary = []
    for id, value in zip(ids, values):
        summary.append(html.P(f"Input {id['id']} has value {value}"))
    return summary

app = Dash()
app.layout = [html.Div(id="summary")] + [dcc.Input(id={"id": i}, placeholder=f"Input {i}") for i in range(5)]
app.run()