1
- import collections
1
+ from collections . abc import MutableSequence
2
2
import re
3
3
from textwrap import dedent
4
4
5
5
from ._grouping import grouping_len , map_grouping
6
6
from .development .base_component import Component
7
7
from . import exceptions
8
- from ._utils import patch_collections_abc , stringify_id
8
+ from ._utils import patch_collections_abc , stringify_id , to_json
9
9
10
10
11
11
def validate_callback (outputs , inputs , state , extra_args , types ):
@@ -198,7 +198,8 @@ def validate_multi_return(outputs_list, output_value, callback_id):
198
198
199
199
200
200
def 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 )
202
203
203
204
def _raise_invalid (bad_val , outer_val , path , index = None , toplevel = False ):
204
205
bad_type = type (bad_val ).__name__
@@ -247,43 +248,74 @@ def _raise_invalid(bad_val, outer_val, path, index=None, toplevel=False):
247
248
)
248
249
)
249
250
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
252
265
253
266
def _validate_value (val , index = None ):
254
267
# val is a Component
255
268
if isinstance (val , Component ):
269
+ unserializable_items = []
256
270
# pylint: disable=protected-access
257
271
for p , j in val ._traverse_with_paths ():
258
272
# check each component value in the tree
259
- if not _value_is_valid (j ):
273
+ if not _valid_child (j ):
260
274
_raise_invalid (bad_val = j , outer_val = val , path = p , index = index )
261
275
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
+
262
290
# Children that are not of type Component or
263
291
# list/tuple not returned by traverse
264
292
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 ):
267
295
_raise_invalid (
268
296
bad_val = child ,
269
297
outer_val = val ,
270
298
path = p + "\n " + "[*] " + type (child ).__name__ ,
271
299
index = index ,
272
300
)
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 )
273
306
274
307
# Also check the child of val, as it will not be returned
275
308
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 ):
278
311
_raise_invalid (
279
312
bad_val = child ,
280
313
outer_val = val ,
281
314
path = type (child ).__name__ ,
282
315
index = index ,
283
316
)
284
317
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 ):
287
319
_raise_invalid (
288
320
bad_val = val ,
289
321
outer_val = type (val ).__name__ ,
@@ -301,13 +333,13 @@ def _validate_value(val, index=None):
301
333
# if we got this far, raise a generic JSON error
302
334
raise exceptions .InvalidCallbackReturnValue (
303
335
"""
304
- The callback for property `{property:s}` of component `{id:s }`
336
+ The callback for output `{output }`
305
337
returned a value which is not JSON serializable.
306
338
307
339
In general, Dash properties can only be dash components, strings,
308
340
dictionaries, numbers, None, or lists of those.
309
341
""" .format (
310
- property = output . component_property , id = output . component_id
342
+ output = repr ( output )
311
343
)
312
344
)
313
345
0 commit comments