Skip to content

[WIP] Delay callback validation #644

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

Closed
wants to merge 1 commit into from

Conversation

ned2
Copy link
Contributor

@ned2 ned2 commented Mar 11, 2019

This is a proposed approach for handling delayed callback validation. As discussed in #519, immediate callback validation requires the Dash layout to be already set, imposing rigid constraints on where callbacks can be defined within a Dash app. In particular, this making arrangement of pages within multi-page apps that contain both callbacks and layout fragments impossible.

This change was more extensive than expected, so at the moment, this is perhaps more of way to get the conversation going.

  • this approach involves adding to a list of registered callbacks in Dash._callback_list (distinct from the Dash.callback_map) when each callback is defined. The list is then used to perform validation when the app starts
  • I think it's critical that this callback validation be applied for WSGI apps as well as apps run through run_server, so I did not put this in the enable_dev_tools_folder, where I think there is a risk of users not running it. So instead I've created a _setup() method that should both be run by run_server and also get_wsgi_application, which would become the required way to get the WSGI callable for when using a WSGI server. This is modelled off Django's approach. So this is also a proposed solution to the general problem of keeping the run_server and WSGI app entry points synchronised (which perhaps should go in a different PR though)
  • I had to split the current callback validation tests into two, as the current code assumes that buggy callbacks can be caught through exceptions and continue on to use the already defined Dash app, which doesn't work that way now
  • A slightly unsatisfying property of this solution is that for an app with invalid callbacks, before validation is applied (and an error thrown), the Dash.callback_map will be in a somewhat dirty state, with eg duplicate callbacks clobbering existing ones.

@ned2
Copy link
Contributor Author

ned2 commented Mar 11, 2019

Here is the error message produced by the below invalid Dash app:

The following outputs have been assigned to multiple callbacks.

<Output `target1.children`> in callback functions: 'callback1', 'callback2'
<Output `target2.children`> in callback functions: 'callback3', 'callback4'

An output can only be associated with a single callback function.
Try combining your inputs and callback functions together into one
function.

The following outputs occur multiple times in a single multi-output
callback:

<Output `target2.children`> in callback function: 'callback4'

Multi-output callbacks cannot target the same output more than
once.
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, State, Output

app = dash.Dash()
app.layout = html.Div([
    html.Div(id='target1'),
    html.Div(id='target2'),
    html.Div(id='target3'),
    dcc.Input(id='my-input', type='text', value=''),
    html.Button(id='submit', n_clicks=0, children='Save')
])

@app.callback(Output('target1', 'children'), [Input('submit', 'n_clicks')],
              [State('my-input', 'value')])
def callback1(n_clicks, state):
    return "callback received value: {}".format(state)

@app.callback(Output('target1', 'children'), [Input('submit', 'n_clicks')],
              [State('my-input', 'value')])
def callback2(n_clicks, state):
    return "callback received value: {}".format(state)

@app.callback(Output('target2', 'children'), [Input('submit', 'n_clicks')],
              [State('my-input', 'value')])
def callback3(n_clicks, state):
    return "callback received value: {}".format(state)

@app.callback([Output('target2', 'children'),
               Output('target2', 'children')],
              [Input('submit', 'n_clicks')],
              [State('my-input', 'value')])
def callback4(n_clicks, state):
    return "callback received value: {}".format(state)

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

@ned2 ned2 force-pushed the delay-callback-validation branch from 5424e6e to 78feb3e Compare March 11, 2019 08:06
@ned2
Copy link
Contributor Author

ned2 commented Mar 11, 2019

Keen to get feedback from @T4rk1n, @alexcjohnson, and @chriddyp

@alexcjohnson
Copy link
Collaborator

Thanks for pushing this forward @ned2 - I think I'll comment over at #519, then we can come back here if this ends up being the preferred approach.

@byronz byronz changed the base branch from master to dev May 8, 2019 01:20
@alexcjohnson
Copy link
Collaborator

Closing - #1103 moved callback validation to the front end.

@ned2 ned2 deleted the delay-callback-validation branch December 11, 2022 05:37
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

Successfully merging this pull request may close these issues.

2 participants