plotly / plotly/dash

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

Open
#1,299 1 comment 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug P3
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 dash below
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:

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.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.