Callback validation for multiple outputs is wrong when one of those outputs is a Dash component with a non-serializable data type
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 24.4k
- Forks
- 2.3k
- Avg merge
- 2d 7h
- Merged PRs (30d)
- 13
Description
The context
Please provide us your environment so we can easily reproduce the issue.
- replace the result of
pip list | grep dashbelow
dash 1.12.0
dash-bootstrap-components 0.10.0
dash-core-components 1.10.0
dash-html-components 1.0.3
dash-renderer 1.4.1
dash-table 4.7.0
-
if frontend related, tell us your Browser, Version and OS
- OS: macOS Mojave
- Browser: chrome
- Version: 83.0.4103.97 (Official Build) (64-bit)
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:

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:

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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Reproduce the callback-validation failure with the example app, then inspect dash/dash.py around line 1001 and dash/_validate.py at fail_callback_output. The fix is done when a multi-output callback identifies the specific output and non-serializable data type instead of reporting the returned tuple or list incorrectly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100