Skip to content
New issue

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

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

Already on GitHub? # to your account

[BUG] dash 1.11 breaks Dynamic Multipage App Validation #1193

Closed
eddy-geek opened this issue Apr 14, 2020 · 5 comments · Fixed by #1201
Closed

[BUG] dash 1.11 breaks Dynamic Multipage App Validation #1193

eddy-geek opened this issue Apr 14, 2020 · 5 comments · Fixed by #1201
Assignees
Labels
bug something broken regression this used to work

Comments

@eddy-geek
Copy link
Contributor

eddy-geek commented Apr 14, 2020

Describe your context
Please provide us your environment so we can easily reproduce the issue.

pip list | grep dash

dash                               1.11.0              
dash-bootstrap-components          0.9.1               
dash-core-components               1.9.1               
dash-dangerously-set-inner-html    0.0.2               
dash-flow-example                  0.0.5               
dash-html-components               1.0.3               
dash-renderer                      1.4.0               
dash-table                         4.6.2               

Describe the bug

Copy example Dynamically Create a Layout for Multi-Page App Validation from https://dash.plotly.com/urls

Click to expand!
import dash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State

import flask

app = dash.Dash(
    __name__,
    external_stylesheets=['https://codepen.io/chriddyp/pen/bWLwgP.css']
)

url_bar_and_content_div = html.Div([
    dcc.Location(id='url', refresh=False),
    html.Div(id='page-content')
])

layout_index = html.Div([
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
])

layout_page_1 = html.Div([
    html.H2('Page 1'),
    dcc.Input(id='input-1-state', type='text', value='Montreal'),
    dcc.Input(id='input-2-state', type='text', value='Canada'),
    html.Button(id='submit-button', n_clicks=0, children='Submit'),
    html.Div(id='output-state'),
    html.Br(),
    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-2"', href='/page-2'),
])

layout_page_2 = html.Div([
    html.H2('Page 2'),
    dcc.Dropdown(
        id='page-2-dropdown',
        options=[{'label': i, 'value': i} for i in ['LA', 'NYC', 'MTL']],
        value='LA'
    ),
    html.Div(id='page-2-display-value'),
    html.Br(),
    dcc.Link('Navigate to "/"', href='/'),
    html.Br(),
    dcc.Link('Navigate to "/page-1"', href='/page-1'),
])


def serve_layout():
    if flask.has_request_context():
        return url_bar_and_content_div
    return html.Div([
        url_bar_and_content_div,
        layout_index,
        layout_page_1,
        layout_page_2,
    ])


app.layout = serve_layout


# Index callbacks
@app.callback(Output('page-content', 'children'),
              [Input('url', 'pathname')])
def display_page(pathname):
    if pathname == "/page-1":
        return layout_page_1
    elif pathname == "/page-2":
        return layout_page_2
    else:
        return layout_index


# Page 1 callbacks
@app.callback(Output('output-state', 'children'),
              [Input('submit-button', 'n_clicks')],
              [State('input-1-state', 'value'),
               State('input-2-state', 'value')])
def update_output(n_clicks, input1, input2):
    return ('The Button has been pressed {} times,'
            'Input 1 is "{}",'
            'and Input 2 is "{}"').format(n_clicks, input1, input2)


# Page 2 callbacks
@app.callback(Output('page-2-display-value', 'children'),
              [Input('page-2-dropdown', 'value')])
def display_value(value):
    print('display_value')
    return 'You have selected "{}"'.format(value)


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

Expected behavior

No errors.

Screenshots

🛑 Errors (4)
⛑️
ID not found in layout
10:53:31 AM
Attempting to connect a callback Input item to component:
  "page-2-dropdown"
but no components with that id exist in the layout.

If you are assigning callbacks to components that are
generated by other callbacks (and therefore not in the
initial layout), you can suppress this exception by setting
`suppress_callback_exceptions=True`.
This ID was used in the callback(s) for Output(s):
  page-2-display-value.children

once per ID:

Click to expand!
⛑️
ID not found in layout
10:53:31 AM
Attempting to connect a callback Input item to component:
  "submit-button"
but no components with that id exist in the layout.

If you are assigning callbacks to components that are
generated by other callbacks (and therefore not in the
initial layout), you can suppress this exception by setting
`suppress_callback_exceptions=True`.
This ID was used in the callback(s) for Output(s):
  output-state.children
⛑️
ID not found in layout
10:53:31 AM
Attempting to connect a callback Output item to component:
  "page-2-display-value"
but no components with that id exist in the layout.

If you are assigning callbacks to components that are
generated by other callbacks (and therefore not in the
initial layout), you can suppress this exception by setting
`suppress_callback_exceptions=True`.
This ID was used in the callback(s) for Output(s):
  page-2-display-value.children
⛑️
ID not found in layout
10:53:31 AM
Attempting to connect a callback Output item to component:
  "output-state"
but no components with that id exist in the layout.

If you are assigning callbacks to components that are
generated by other callbacks (and therefore not in the
initial layout), you can suppress this exception by setting
`suppress_callback_exceptions=True`.
This ID was used in the callback(s) for Output(s):
  output-state.children
@eddy-geek eddy-geek changed the title [BUG] dash 1.11 breaks Dynamic Multipage App Validation broken [BUG] dash 1.11 breaks Dynamic Multipage App Validation Apr 14, 2020
@eddy-geek
Copy link
Contributor Author

eddy-geek commented Apr 14, 2020

note: I think pip upgraded all deps as needed but, this comment about disabling devtools asserts made me doubt, it looks related (?):

@chriddyp @alexcjohnson As we discussed in DM, merging this in as there's no averse affect on feature/behavior. Will create a follow up issue to re-enable devtools asserts once we figure it out.

@alexcjohnson
Copy link
Collaborator

Thanks @eddy-geek - did this example work without errrors prior to v1.11? It looks to me as though it's just missing suppress_callback_exceptions=True, which is essentially always needed for multi-page apps and is included in all the other examples on that page. Anyway that fixes it for me locally - we'll get the docs updated (as well as updating them to use the preferred usage of suppress_callback_exceptions as a constructor kwarg, rather than the older app.config.suppress_callback_exceptions = True)

@eddy-geek
Copy link
Contributor Author

eddy-geek commented Apr 14, 2020

@alexcjohnson yes the example worked fine, and "validation" -- ie not suppressing exceptions -- is actually the whole point! The doc says it better than I could:

For full validation, all components within your callback must therefore appear in the initial layout of your app, and you will see an error if they do not. However, in the case of more complex Dash apps that involve dynamic modification of the layout (such as multi-page apps), not every component appearing in your callbacks will be included in the initial layout.

Since this validation is done before flask has any request context, you can create a layout function that checks flask.has_request_context() and returns a complete layout to the validator if there is no request context and returns the incomplete index layout otherwise.

I'm using the same technique in my app (so I am back on 1.10.0) and I think it is popular (judging by related community threads) because any non-toy app is multi-page, and suppress_callback_exceptions slows down development/debugging a lot. Actually if I understand correctly, wildcards will allow more use-cases to work without suppress_callback_exceptions which is going in the good direction :)

So, while the client-side validation and wildcard look great, I would argue it's important to either maintain support for, or replace, this pattern, not just take the "easy" route of suppress_callback_exceptions.

@alexcjohnson
Copy link
Collaborator

Ah OK! I was unaware of the flask.has_request_context technique, thank you for bringing it to my attention! We'll have to think about how to support that with client-side validation, it doesn't seem simple but I do see a way to do it.

@pmkroeker
Copy link

Thanks @eddy-geek - did this example work without errrors prior to v1.11? It looks to me as though it's just missing suppress_callback_exceptions=True, which is essentially always needed for multi-page apps and is included in all the other examples on that page. Anyway that fixes it for me locally - we'll get the docs updated (as well as updating them to use the preferred usage of suppress_callback_exceptions as a constructor kwarg, rather than the older app.config.suppress_callback_exceptions = True)

https://dash.plotly.com/callback-gotchas

This has not been updated in the docs yet.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
bug something broken regression this used to work
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants