1- import collections
1+ from collections . abc import MutableSequence
22import re
33from textwrap import dedent
44
55from ._grouping import grouping_len , map_grouping
66from .development .base_component import Component
77from . import exceptions
8- from ._utils import patch_collections_abc , stringify_id
8+ from ._utils import patch_collections_abc , stringify_id , to_json
99
1010
1111def validate_callback (outputs , inputs , state , extra_args , types ):
@@ -198,7 +198,8 @@ def validate_multi_return(outputs_list, output_value, callback_id):
198198
199199
200200def fail_callback_output (output_value , output ):
201- valid = (str , dict , int , float , type (None ), Component )
201+ valid_children = (str , int , float , type (None ), Component )
202+ valid_props = (str , int , float , type (None ), tuple , MutableSequence )
202203
203204 def _raise_invalid (bad_val , outer_val , path , index = None , toplevel = False ):
204205 bad_type = type (bad_val ).__name__
@@ -247,43 +248,74 @@ def _raise_invalid(bad_val, outer_val, path, index=None, toplevel=False):
247248 )
248249 )
249250
250- def _value_is_valid (val ):
251- return isinstance (val , valid )
251+ def _valid_child (val ):
252+ return isinstance (val , valid_children )
253+
254+ def _valid_prop (val ):
255+ return isinstance (val , valid_props )
256+
257+ def _can_serialize (val ):
258+ if not (_valid_child (val ) or _valid_prop (val )):
259+ return False
260+ try :
261+ to_json (val )
262+ except TypeError :
263+ return False
264+ return True
252265
253266 def _validate_value (val , index = None ):
254267 # val is a Component
255268 if isinstance (val , Component ):
269+ unserializable_items = []
256270 # pylint: disable=protected-access
257271 for p , j in val ._traverse_with_paths ():
258272 # check each component value in the tree
259- if not _value_is_valid (j ):
273+ if not _valid_child (j ):
260274 _raise_invalid (bad_val = j , outer_val = val , path = p , index = index )
261275
276+ if not _can_serialize (j ):
277+ # collect unserializable items separately, so we can report
278+ # only the deepest level, not all the parent components that
279+ # are just unserializable because of their children.
280+ unserializable_items = [
281+ i for i in unserializable_items if not p .startswith (i [0 ])
282+ ]
283+ if unserializable_items :
284+ # we already have something unserializable in a different
285+ # branch - time to stop and fail
286+ break
287+ if all (not i [0 ].startswith (p ) for i in unserializable_items ):
288+ unserializable_items .append ((p , j ))
289+
262290 # Children that are not of type Component or
263291 # list/tuple not returned by traverse
264292 child = getattr (j , "children" , None )
265- if not isinstance (child , (tuple , collections . MutableSequence )):
266- if child and not _value_is_valid (child ):
293+ if not isinstance (child , (tuple , MutableSequence )):
294+ if child and not _can_serialize (child ):
267295 _raise_invalid (
268296 bad_val = child ,
269297 outer_val = val ,
270298 path = p + "\n " + "[*] " + type (child ).__name__ ,
271299 index = index ,
272300 )
301+ if unserializable_items :
302+ p , j = unserializable_items [0 ]
303+ # just report the first one, even if there are multiple,
304+ # as that's how all the other errors work
305+ _raise_invalid (bad_val = j , outer_val = val , path = p , index = index )
273306
274307 # Also check the child of val, as it will not be returned
275308 child = getattr (val , "children" , None )
276- if not isinstance (child , (tuple , collections . MutableSequence )):
277- if child and not _value_is_valid ( child ):
309+ if not isinstance (child , (tuple , MutableSequence )):
310+ if child and not _can_serialize ( val ):
278311 _raise_invalid (
279312 bad_val = child ,
280313 outer_val = val ,
281314 path = type (child ).__name__ ,
282315 index = index ,
283316 )
284317
285- # val is not a Component, but is at the top level of tree
286- elif not _value_is_valid (val ):
318+ if not _can_serialize (val ):
287319 _raise_invalid (
288320 bad_val = val ,
289321 outer_val = type (val ).__name__ ,
@@ -301,13 +333,13 @@ def _validate_value(val, index=None):
301333 # if we got this far, raise a generic JSON error
302334 raise exceptions .InvalidCallbackReturnValue (
303335 """
304- The callback for property `{property:s}` of component `{id:s }`
336+ The callback for output `{output }`
305337 returned a value which is not JSON serializable.
306338
307339 In general, Dash properties can only be dash components, strings,
308340 dictionaries, numbers, None, or lists of those.
309341 """ .format (
310- property = output . component_property , id = output . component_id
342+ output = repr ( output )
311343 )
312344 )
313345
0 commit comments