plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.19k stars 2.04k forks source link

Callback validation for multiple outputs is wrong when one of those outputs is a Dash component with a non-serializable data type #1299

Open abunuwas opened 4 years ago

abunuwas commented 4 years ago

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

The bug

In a callback with multiple outputs, when one of the returned values is a Dash component which has been defined with a non-serializable type, Dash raises the wrong error - it fails to inspect the returned values and catch the actual element which is raising the error.

Expected behavior

I'd expect the validation logic to be able to inspect each of the returned values and catch the actual data type which is causing trouble. I'd expect the error message to tell me exactly which of the returned outputs is causing trouble and what is the data type which is causing the trouble.

Code snippers & screenshots

First, example of a simple app that produces this error:

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

external_stylesheets = [dbc.themes.BOOTSTRAP]

app = dash.Dash('asdf', external_stylesheets=external_stylesheets)

bad_dropdown = html.Label([
    html.Label('error'),
    dcc.Dropdown(options={'a' 'b', 'c'}, placeholder='Select a letter')
])

app.layout = dbc.Container([
    dbc.Col([
        dbc.Row(),
        dbc.Row([html.Button('Error!', id='button')]),
        dbc.Row()
    ]),
    dbc.Col([
        dbc.Row(),
        dbc.Row(id='dropdown-container'),
        dbc.Row(id='something-else')
    ])
])

@app.callback(
    output=[
        Output(component_id='dropdown-container', component_property='children'),
        Output(component_id='something-else', component_property='children'),
    ],
    inputs=[
        Input(component_id='button', component_property='n_clicks')
    ],
    prevent_initial_call=True
)
def go(clicks):
    return bad_dropdown, ''

app.run_server(debug=True)

When you click on the button, the callback tries to return two outputs, one of them is a dropdown in which the options are defined as a set, which is a non-serializable data type and causes Dash to fail. However, the error that I get is:

image

It says the problem is a tuple, which is wrong (the tuple is the array of returned values). Also, see what happens if we change the callback to return a list of values:

def go(clicks):
    return [bad_dropdown, '']

The result is this:

image

At this point something weird is happening in the validation and it's getting confusing. However, further down the trace in the above message, the real error pops up:

Traceback (most recent call last):
  File "/Users/j/.local/share/virtualenvs/sbpaa-rf-app-4422qoGV/lib/python3.7/site-packages/dash/dash.py", line 999, in add_context
    response, cls=plotly.utils.PlotlyJSONEncoder
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/__init__.py", line 238, in dumps
    **kw).encode(obj)
  File "/Users/j/.local/share/virtualenvs/sbpaa-rf-app-4422qoGV/lib/python3.7/site-packages/_plotly_utils/utils.py", line 45, in encode
    encoded_o = super(PlotlyJSONEncoder, self).encode(o)
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 199, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 257, in iterencode
    return _iterencode(o, 0)
  File "/Users/j/.local/share/virtualenvs/sbpaa-rf-app-4422qoGV/lib/python3.7/site-packages/_plotly_utils/utils.py", line 115, in default
    return _json.JSONEncoder.default(self, obj)
  File "/usr/local/Cellar/python/3.7.5/Frameworks/Python.framework/Versions/3.7/lib/python3.7/json/encoder.py", line 179, in default
    raise TypeError(f'Object of type {o.__class__.__name__} '
TypeError: Object of type set is not JSON serializable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/j/.local/share/virtualenvs/sbpaa-rf-app-4422qoGV/lib/python3.7/site-packages/flask/app.py", line 1950, in full_dispatch_request
    rv = self.dispatch_request()
  File "/Users/j/.local/share/virtualenvs/sbpaa-rf-app-4422qoGV/lib/python3.7/site-packages/flask/app.py", line 1936, in dispatch_request
    return self.view_functions[rule.endpoint](**req.view_args)
  File "/Users/j/.local/share/virtualenvs/sbpaa-rf-app-4422qoGV/lib/python3.7/site-packages/dash/dash.py", line 1032, in dispatch
    response.set_data(func(*args, outputs_list=outputs_list))
  File "/Users/j/.local/share/virtualenvs/sbpaa-rf-app-4422qoGV/lib/python3.7/site-packages/dash/dash.py", line 1002, in add_context
    _validate.fail_callback_output(output_value, output)
  File "/Users/j/.local/share/virtualenvs/sbpaa-rf-app-4422qoGV/lib/python3.7/site-packages/dash/_validate.py", line 264, in fail_callback_output
    property=output.component_property, id=output.component_id
AttributeError: 'list' object has no attribute 'component_property'

The following line in the code actually contains the correct error: https://github.com/plotly/dash/blob/526af0c6c50a99bb29277244818abc9f7959c894/dash/dash.py#L1001

(if you change that to except TypeError as e: and inspect e, then you see the real source of the problem. )

The problem is the next call to fail_callback_output doesn't get it right.

bormanjo commented 3 years ago

While this particular ticket hasn't been active since the OP's initial post, I can confirm that I'm seeing the same behavior. In my case the error (producing an uninformative error message exactly like OPs) was (accidentally) passing a set instead of a dictionary to a kwarg in dash_table.DataTable().

My environment is Windows 10 with the following Dash versions: image

Please let me know if this has been resolved in a more recent Dash version, otherwise a fix to provide more informative error messages would be greatly appreciated.