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

dash 1.11 suppresses unrelated callbacks on component-load if ReferenceError detected #1200

Closed
michaelbabyn opened this issue Apr 17, 2020 · 5 comments · Fixed by #1212
Closed

Comments

@michaelbabyn
Copy link

Describe your context

dash                               1.10.0     
dash-core-components               1.9.0      
dash-html-components               1.0.3      
dash-renderer                      1.3.0         
dash-table                         4.6.2 

Describe the bug

When components are added to the layout of an app in dash 1.11 (e.g in a multipage app), all the callbacks associated with those new components are triggered in python but they won't update the app on the front end if a ReferenceError was detected (i.e if an output to a callback was added without the corresponding inputs).

Expected behavior

In previous versions of dash, all the well defined callbacks associated with the components added to the dash layout would still update the app

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

app = dash.Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Button("Button", id="input1", n_clicks=0),
    html.Div(id='output1', children='initial value for output1'),
    html.Div(id='partial-output-initial-layout'),
    html.Div(id='content'),
])


def render_layout():
    return html.Div([
        dcc.Input(id='input2'),
        html.Div(id='output2', children='initial value for output2'),
        html.Div(id='input3'),
        html.Div(id='output3', children='initial value for output3'),
        html.Div(id='partial-output-secondary-layout'),
    ])


@app.callback(dash.dependencies.Output('content', 'children'),
              [dash.dependencies.Input('url', 'pathname')])
def display_content(url):
    if url:
        return render_layout()


@app.callback(
    Output('output1', 'children'),
    [Input('input1', 'n_clicks')])
def button_callback(n_clicks):
    print('callback with input1')
    return 'callback with input1'


@app.callback(
    Output('output2', 'children'),
    [Input('input2', 'value')]
)
def dcc_input_callback(value):
    print('callback with input2')
    return 'callback with input2'



@app.callback(
    Output('output3', 'children'),
    [Input('input3', 'children')]
)
def html_div_callback(value):
    print('callback with input3')
    return 'callback with input3'


@app.callback(
    Output('partial-output-secondary-layout', 'children'),
    [Input('non-existant-input', 'value')]
)
def non_existant_input_callback_secondary_layout(value):
    return 'this will fail'


@app.callback(
    Output('partial-output-initial-layout', 'children'),
    [Input('non-existant-input', 'value')]
)
def non_existant_input_callback_primary_layout(value):
    return 'this will fail'

if __name__ == '__main__':
    app.run_server(debug=True, port=8091)
@alexcjohnson
Copy link
Collaborator

Here's what I thought made most sense as the rule to enforce:

If:

  • None of the inputs for a certain callback are on the page
  • The inputs are NOT entirely multi-item patterns (with ALL or ALLSMALLER wildcards)

Then:

  • We will not raise an error
  • We will not try to fire this callback
  • If any of the outputs of this callback are themselves inputs to other callbacks, those other callbacks will fire immediately.

What I notice now is that this isn't precisely what happens in dash 1.10, or at least it has more nuance: as to that final point, the "other callbacks" that flow from the callback with missing inputs, they will only fire with at least one other input in addition to the blocked one. To make this concrete:

import dash
import dash_html_components as html
from dash.dependencies import Input, Output, State

app = dash.Dash(__name__, suppress_callback_exceptions=True)

app.layout = html.Div(children=[
    html.Button("Click", id="btn"),
    html.Div("Title", id="title"),
    html.Div(id="content"),
    html.Div("Output1", id="output1"),
    html.Div("No out2 call yet", id="output2")
])

@app.callback(Output("content", "children"), [Input("btn", "n_clicks")])
def content(n):
    return [
        html.Div("A", id="a"),
        html.Div("B", id="b")
    ] if n else "No content yet"

@app.callback(
    Output("output1", "children"),
    [Input("a", "children"), Input("b", "children")],
    [State("title", "children")]
)
def out1(a, b, title):
    return str(a) + str(b) + str(title)

@app.callback(
    Output("output2", "children"),
    [Input("output1", "children")]
)
def out2(out1):
    return out1 + " - final"

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

Like that, the output2 callback will not fire on load, only after a button click. But if you change it to:

@app.callback(
    Output("output2", "children"),
    [Input("output1", "children"), Input("title", "children")]
)
def out2(out1, title):
    return out1 + " - final: " + title

Then it will fire on load.


My first thought was that this didn't make a whole lot of sense - if we're treating this situation like "its inputs don't exist, therefore out1 doesn't really exist yet either", then it should behave as it does when out1 is commented out entirely - and in that case out2 of course fires.

However, you might also think of it like "its inputs don't exist, therefore out1 is implied PreventUpdate" - and using that rationale, the behavior of v1.10 is exactly right: an initial callback will not fire if its inputs are themselves all outputs that were prevented.

Unless anyone wants to argue that this is a bug and we should really fire out2 in both cases, I'm inclined to go with that reasoning - AFAICT it's fully backward-compatible and it does have a coherent rationale.

cc @chriddyp @Marc-Andre-Rivet

@Marc-Andre-Rivet
Copy link
Contributor

Marc-Andre-Rivet commented Apr 23, 2020

Can you refresh my memory as to the behavior for callbacks with some ALL|ALLSMALLER and with missing inputs? Would this callback always behave like the partial inputs case?

What would be the behavior for a multi-output callback on output1 and output2?

@Marc-Andre-Rivet
Copy link
Contributor

Looks like we would potentially want to test/lock behavior for the following scenarios:

  • all/some inputs missing
  • all/some input missing with at least one MATCH pattern
  • all/some input missing with at least one ALL|ALLSMALLER pattern
  • all/some missing with only MATCH
  • all/some missing with only ALL|ALLSMALLER

Other modifiers:

  • output prop chaining
  • output presence
  • multiple outputs

@alexcjohnson
Copy link
Collaborator

Thanks, that's a really useful collection of test cases!

If all the inputs have ALL or ALLSMALLER then we need to fire this callback even with no matching elements. That's to handle cases like allowing the Todo app to show an aggregation message "no items in the list." But if any inputs are non-multi (MATCH or string ID) then we're back in the same case - either all of these are present and we fire the callback; all are missing and we don't fire it; or some are present and it's an error.

Multi-output callbacks: if none of the inputs are present, it would be OK to have only some of the outputs present, this would not be an error. This could happen for example if one of the outputs was inside the children section that hasn't arrived yet but the other is always on the page.

@chriddyp
Copy link
Member

AFAICT it's fully backward-compatible and it does have a coherent rationale.

👍

alexcjohnson added a commit that referenced this issue Apr 26, 2020
except when all inputs have multivalued wildcards and all outputs exist
alexcjohnson added a commit that referenced this issue Apr 26, 2020
except when all inputs have multivalued wildcards and all outputs exist
@alexcjohnson alexcjohnson mentioned this issue Apr 26, 2020
6 tasks
alexcjohnson added a commit that referenced this issue Apr 26, 2020
except when all inputs have multivalued wildcards and all outputs exist
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants