Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
luewh opened this issue Nov 6, 2024 · 1 comment
Open
Labels
bug something broken P2 considered for next cycle

Comments

@luewh
Copy link

luewh commented Nov 6, 2024

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

@gvwilson gvwilson changed the title [BUG] Pattern-Matching Callbacks ALL inconsistent order in dash 2.18.1 Pattern-Matching Callbacks ALL have inconsistent order in dash 2.18.1 Nov 11, 2024
@gvwilson gvwilson added bug something broken P2 considered for next cycle labels Nov 11, 2024
@antonymilne
Copy link

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: #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()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken P2 considered for next cycle
Projects
None yet
Development

No branches or pull requests

3 participants