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

allow_duplicate on outputs doesn't work #475

Closed
zyassine opened this issue Sep 26, 2023 · 7 comments
Closed

allow_duplicate on outputs doesn't work #475

zyassine opened this issue Sep 26, 2023 · 7 comments

Comments

@zyassine
Copy link

I have a dash app where I update the Output in many Dash Callbacks. In order to do it, I have to use allow_duplicate=True in the Output argument, but it doesn't work on a django dash app.

This is a basic Dash app to showcase the use of allow_duplicate:

from dash import Dash, Input, Output, html, dcc

app = Dash(__name__)

app.layout = html.Div([
    dcc.Input(id='input_text_1', type="text", value=''),
    dcc.Input(id='input_text_2', type="text", value=''),
    html.Div(id="output")
])


@app.callback(
    Output(component_id='output', component_property='children', allow_duplicate=True),
    Input(component_id='input_text_1', component_property='value'),
    prevent_initial_call=True,
)
def update_1(value):
    return f"Input: {value}"

@app.callback(
    Output(component_id='output', component_property='children', allow_duplicate=True),
    Input(component_id='input_text_2', component_property='value'),
    prevent_initial_call=True,
)
def update_2(value):
    return f"Input: {value}"

app.run(debug=False)

If I use app = DjangoDash(__name__), then the output doesn't get updated

@delsim
Copy link
Contributor

delsim commented Oct 8, 2023

This is essentially the statement that django-plotly-dash does not support duplicate callbacks.

Are you in a position to make your callback structure less ill-defined and consolidate the calculations so that only one callback is used for an output value? If the multiple callbacks are non-trivial to combine you could insert a dummy hidden output for each callback (add in also a timestamp or similar if you need to work out which one to use) and then add a third callback to determine which is to be used.

Given that multiple callbacks implicitly say 'use whatever arrived the most recently, dont care what that is' then at worst this introduces the overhead of a third callback (which one could make run locally in the browser if an issue) but also permits the question of which value to be used to be directly addressed.

@zachsiegel-capsida
Copy link

I would also like to use allow_duplicate in a django-plotly-dash app!

For what it's worth, the race condition described by @delsim does not necessarily arise if prevent_initial_call=True is used in all (or even all but one) of the callbacks involved and some pathological patterns are avoided. The Plotly team created this argument/feature in Dash 2.9 because it can absolutely work.

@delsim any chance this argument will be supported in django-plotly-dash? Any comment on why it doesn't work already (to the extent to which DPD is a wrapper to Dash, I don't see exactly where it should fail).

Thank you so much for any response on this!

@zachsiegel-capsida
Copy link

I would also like to use allow_duplicate in a django-plotly-dash app!

For what it's worth, the race condition described by @delsim does not necessarily arise if prevent_initial_call=True is used in all (or even all but one) of the callbacks involved and some pathological patterns are avoided. The Plotly team created this argument/feature in Dash 2.9 because it can absolutely work.

@delsim any chance this argument will be supported in django-plotly-dash? Any comment on why it doesn't work already (to the extent to which DPD is a wrapper to Dash, I don't see exactly where it should fail).

Thank you so much for any response on this!

Ah @delsim sorry for my confusion, I found that allow_duplicate does already work. prevent_initial_call has to be used on all callbacks that use this type of Output, as documented by Plotly.

@zyassine perhaps that answers your question?

@zachsiegel-capsida
Copy link

zachsiegel-capsida commented Oct 30, 2023

Whoops - @delsim I notice allow_duplicate works only in the sense that the Dash app starts, but the Output functionality doesn't actually send anything to the component properties with the allow_duplicate argument (even if they in fact only have one Output)!

@zyassine perhaps this is what you referred to originally.

@ryan-bloom
Copy link

@delsim I am noticing the same thing that @zyassine and @zachsiegel-capsida are seeing and would love the allow_duplicate functionality to work for my django-plotly-dash app. As @zachsiegel-capsida said, you can add the allow_duplicate argument to a callback and the application starts up as expected but the expected update is never seen when the callback runs. (e.g. if the callback fires on a button click and changes the children of another element, the button click doesn't raise any error, but the output element's children component is not changed at all)

Any more updates on this functionality? Personally would be using it because I have a bunch of input elements that correspond to local session storage items and would want a change to any of the inputs to update the session storage that it corresponds to. Therefore, I want multiple callbacks pointing to update the same session storage object.

@delsim
Copy link
Contributor

delsim commented Dec 20, 2023

@zachsiegel-capsida I'm struggling to see how prevent_initial_callbacks can stop a race condition. If I have two callbacks that both update some target value, and some interaction in the UI causes first one and then shortly after the second to be called, there is no way of controlling which one will return first.

Whilst this issue might not manifest on a single-threaded synchronous server such as the test ones in Django, Flask etc., as these will only process the callbacks in order, it will nevertheless become quite apparent as soon as one moves to a production environment and uses multiple threads or processes or an async server; there is no guarantee of the order in which the callbacks will return their value to the server. There is a note to this effect in the dash documentation.

@ryan-bloom what stops you having each callback update a separate value and then combine all of these values into the session object with an additional callback? Or, if the purpose of the session object is to maintain some server-side composite record, make updating it a side-effect of a callback rather than its return value.

@zachsiegel-capsida
Copy link

@zachsiegel-capsida I'm struggling to see how prevent_initial_callbacks can stop a race condition. If I have two callbacks that both update some target value, and some interaction in the UI causes first one and then shortly after the second to be called, there is no way of controlling which one will return first.

Whilst this issue might not manifest on a single-threaded synchronous server such as the test ones in Django, Flask etc., as these will only process the callbacks in order, it will nevertheless become quite apparent as soon as one moves to a production environment and uses multiple threads or processes or an async server; there is no guarantee of the order in which the callbacks will return their value to the server. There is a note to this effect in the dash documentation.

@delsim I totally see your point! Thanks so much for this response. I do stand by my comment below, though:

For what it's worth, the race condition described by @delsim does not necessarily arise if prevent_initial_call=True is used in all (or even all but one) of the callbacks involved and some pathological patterns are avoided. The Plotly team created this argument/feature in Dash 2.9 because it can absolutely work.

You're taking issue with my claim that only "pathological patterns" cause race conditions and I see your point, but it is possible to avoid them to an acceptable level in practice and that's why Dash supports allow_duplicate! One way is disabling interactive UI elements and showing spinners via clientside callbacks to discourage or prevent multiple triggers. Furthermore, the previously-required Dash pattern of setting up dummy hidden output elements that all write to the real output element via a single callback ALSO suffers from race conditions for the same reason.

Enforcing this pattern in django-plotly-dash when Dash no longer enforces it is probably not what most people expect from this (excellent!) package. I think most people want/expect a wrapper that lets them use Dash from Django, and the more the django-plotly-dash interface deviates from Dash the more difficult that will be (and the more documentation dpd requires to clarify those differences).

As always thank you for maintaining this package and for responding thoughtfully to GitHub issues!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants