Skip to content

Commit da65b56

Browse files
committed
More informative JSON not serializable errors with paths.
1 parent f309cc2 commit da65b56

File tree

3 files changed

+79
-7
lines changed

3 files changed

+79
-7
lines changed

dash/dash.py

+57
Original file line numberDiff line numberDiff line change
@@ -475,6 +475,62 @@ def _validate_callback(self, output, inputs, state, events):
475475
output.component_id,
476476
output.component_property).replace(' ', ''))
477477

478+
def _validate_callback_output(self, output_value, output):
479+
valid = [str, dict, int, float, type(None), Component, dict]
480+
481+
def _raise_invalid(bad_val, outer_type, bad_type, path, index=None):
482+
raise exceptions.ReturnValueNotJSONSerializable('''
483+
The callback for property `{:s}` of component `{:s}`
484+
returned a tree with one value having type `{:s}`
485+
which is not JSON serializable.
486+
487+
The value in question is located at
488+
489+
`{:s}`
490+
491+
and has string representation
492+
493+
`{}`.
494+
495+
In general, Dash properties can only be
496+
dash components, strings, dictionaries, numbers, None,
497+
or lists of those.
498+
'''.format(
499+
output.component_property,
500+
output.component_id,
501+
bad_type,
502+
(
503+
"outer list index {:d} ({:s}) -> "
504+
.format(index, outer_type)
505+
if index is not None
506+
else (outer_type + " -> ")
507+
) + path,
508+
bad_val).replace(' ', ''))
509+
510+
def _value_is_valid(val):
511+
return (
512+
# pylint: disable=unused-variable
513+
any([isinstance(val, x) for x in valid]) or
514+
type(val).__name__ == 'unicode'
515+
)
516+
517+
def _validate_value(val, index=None):
518+
if isinstance(val, Component):
519+
for p, j in val.traverse_with_paths():
520+
if not _value_is_valid(j):
521+
_raise_invalid(j, type(val).__name__, type(j).__name__,
522+
p, index)
523+
else:
524+
if not _value_is_valid(val):
525+
_raise_invalid(val, type(val).__name__, type(val).__name__,
526+
'', index)
527+
528+
if isinstance(output_value, list):
529+
for i, val in enumerate(output_value):
530+
_validate_value(val, index=i)
531+
elif isinstance(output_value, Component):
532+
_validate_value(output_value)
533+
478534
# TODO - Update nomenclature.
479535
# "Parents" and "Children" should refer to the DOM tree
480536
# and not the dependency tree.
@@ -513,6 +569,7 @@ def wrap_func(func):
513569
def add_context(*args, **kwargs):
514570

515571
output_value = func(*args, **kwargs)
572+
self._validate_callback_output(output_value, output)
516573
response = {
517574
'response': {
518575
'props': {

dash/development/base_component.py

+18-7
Original file line numberDiff line numberDiff line change
@@ -151,22 +151,33 @@ def __delitem__(self, id): # pylint: disable=redefined-builtin
151151

152152
def traverse(self):
153153
"""Yield each item in the tree."""
154+
for t in self.traverse_with_paths():
155+
yield t[1]
156+
157+
def traverse_with_paths(self):
158+
"""Yield each item with its path in the tree."""
154159
children = getattr(self, 'children', None)
160+
children_type = type(children).__name__
155161

156162
# children is just a component
157163
if isinstance(children, Component):
158-
yield children
159-
for t in children.traverse():
160-
yield t
164+
yield children_type, children
165+
for p, t in children.traverse_with_paths():
166+
yield " -> ".join([children_type, p]), t
161167

162168
# children is a list of components
163169
elif isinstance(children, collections.MutableSequence):
164-
for i in children: # pylint: disable=not-an-iterable
165-
yield i
170+
for idx, i in enumerate(children):
171+
list_path = "{:s} index {:d} (type {:s})".format(
172+
children_type,
173+
idx,
174+
type(i).__name__
175+
)
176+
yield list_path, i
166177

167178
if isinstance(i, Component):
168-
for t in i.traverse():
169-
yield t
179+
for p, t in i.traverse_with_paths():
180+
yield " -> ".join([list_path, p]), t
170181

171182
def __iter__(self):
172183
"""Yield IDs in the tree of children."""

dash/exceptions.py

+4
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,7 @@ class CantHaveMultipleOutputs(CallbackException):
4444

4545
class PreventUpdate(CallbackException):
4646
pass
47+
48+
49+
class ReturnValueNotJSONSerializable(CallbackException):
50+
pass

0 commit comments

Comments
 (0)