Repository: plotly/dash

Issue:
multi-outputs: invalid callback return gives bad error message
make some non-JSON-serializable (but still wrapped in a list) return to a multi-output callback:
```py
@app.callback(
    [Output('c', 'children'), Output('d', 'children')],
    [Input('a', 'children'), Input('b', 'children')]
)
def c(a, b):
    return [Output('c', 'children'), Output('d', 'children')]

```

The error handler itself barfs because it's not expecting a multi-output spec:

```
  File "/Users/alex/plotly/dash/dash/dash.py", line 854, in _raise_invalid
    property=output.component_property,
AttributeError: 'list' object has no attribute 'component_property'
```


Discussion:

Resolving patch:
diff --git a/dash/dash.py b/dash/dash.py
--- a/dash/dash.py
+++ b/dash/dash.py
@@ -839,7 +839,7 @@ def _raise_invalid(bad_val, outer_val, bad_type, path, index=None,
                 if getattr(outer_val, 'id', False) else ''
             outer_type = type(outer_val).__name__
             raise exceptions.InvalidCallbackReturnValue('''
-            The callback for property `{property:s}` of component `{id:s}`
+            The callback for `{output:s}`
             returned a {object:s} having type `{type:s}`
             which is not JSON serializable.
 
@@ -851,8 +851,7 @@ def _raise_invalid(bad_val, outer_val, bad_type, path, index=None,
             dash components, strings, dictionaries, numbers, None,
             or lists of those.
             '''.format(
-                property=output.component_property,
-                id=output.component_id,
+                output=repr(output),
                 object='tree with one value' if not toplevel else 'value',
                 type=bad_type,
                 location_header=(


Test patch:
diff --git a/tests/test_integration.py b/tests/test_integration.py
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -18,7 +18,7 @@
 from dash.dependencies import Input, Output, State
 from dash.exceptions import (
     PreventUpdate, DuplicateCallbackOutput, CallbackException,
-    MissingCallbackContextException
+    MissingCallbackContextException, InvalidCallbackReturnValue
 )
 from .IntegrationTests import IntegrationTests
 from .utils import assert_clean_console, invincible, wait_for
@@ -878,6 +878,43 @@ def failure2(children):
             context.exception.args[0]
         )
 
+    def test_callback_return_validation(self):
+        app = dash.Dash(__name__)
+        app.layout = html.Div([
+            html.Div(id='a'),
+            html.Div(id='b'),
+            html.Div(id='c'),
+            html.Div(id='d'),
+            html.Div(id='e'),
+            html.Div(id='f')
+        ])
+
+        @app.callback(Output('b', 'children'), [Input('a', 'children')])
+        def single(a):
+            # anything non-serializable, really
+            return set([1])
+
+        with self.assertRaises(InvalidCallbackReturnValue):
+            single('aaa')
+
+        @app.callback([Output('c', 'children'), Output('d', 'children')],
+                      [Input('a', 'children')])
+        def multi(a):
+            # non-serializable inside a list
+            return [1, set([2])]
+
+        with self.assertRaises(InvalidCallbackReturnValue):
+            multi('aaa')
+
+        @app.callback([Output('e', 'children'), Output('f', 'children')],
+                      [Input('a', 'children')])
+        def multi2(a):
+            # wrong-length list
+            return ['abc']
+
+        with self.assertRaises(InvalidCallbackReturnValue):
+            multi2('aaa')
+
     def test_callback_context(self):
         app = dash.Dash(__name__)