1010 Output ,
1111)
1212from .exceptions import (
13+ InvalidCallbackReturnValue ,
1314 PreventUpdate ,
1415 WildcardInLongCallback ,
1516 MissingLongCallbackManagerError ,
@@ -226,6 +227,7 @@ def insert_callback(
226227 manager = None ,
227228 running = None ,
228229 dynamic_creator = False ,
230+ no_output = False ,
229231):
230232 if prevent_initial_call is None :
231233 prevent_initial_call = config_prevent_initial_callbacks
@@ -234,7 +236,7 @@ def insert_callback(
234236 output , prevent_initial_call , config_prevent_initial_callbacks
235237 )
236238
237- callback_id = create_callback_id (output , inputs )
239+ callback_id = create_callback_id (output , inputs , no_output )
238240 callback_spec = {
239241 "output" : callback_id ,
240242 "inputs" : [c .to_dict () for c in inputs ],
@@ -248,6 +250,7 @@ def insert_callback(
248250 "interval" : long ["interval" ],
249251 },
250252 "dynamic_creator" : dynamic_creator ,
253+ "no_output" : no_output ,
251254 }
252255 if running :
253256 callback_spec ["running" ] = running
@@ -262,6 +265,7 @@ def insert_callback(
262265 "raw_inputs" : inputs ,
263266 "manager" : manager ,
264267 "allow_dynamic_callbacks" : dynamic_creator ,
268+ "no_output" : no_output ,
265269 }
266270 callback_list .append (callback_spec )
267271
@@ -283,10 +287,12 @@ def register_callback( # pylint: disable=R0914
283287 # Insert callback with scalar (non-multi) Output
284288 insert_output = output
285289 multi = False
290+ has_output = True
286291 else :
287292 # Insert callback as multi Output
288293 insert_output = flatten_grouping (output )
289294 multi = True
295+ has_output = len (output ) > 0
290296
291297 long = _kwargs .get ("long" )
292298 manager = _kwargs .get ("manager" )
@@ -315,6 +321,7 @@ def register_callback( # pylint: disable=R0914
315321 manager = manager ,
316322 dynamic_creator = allow_dynamic_callbacks ,
317323 running = running ,
324+ no_output = not has_output ,
318325 )
319326
320327 # pylint: disable=too-many-locals
@@ -331,9 +338,12 @@ def wrap_func(func):
331338 def add_context (* args , ** kwargs ):
332339 output_spec = kwargs .pop ("outputs_list" )
333340 app_callback_manager = kwargs .pop ("long_callback_manager" , None )
334- callback_ctx = kwargs .pop ("callback_context" , {})
341+ callback_ctx = kwargs .pop (
342+ "callback_context" , AttributeDict ({"updated_props" : {}})
343+ )
335344 callback_manager = long and long .get ("manager" , app_callback_manager )
336- _validate .validate_output_spec (insert_output , output_spec , Output )
345+ if has_output :
346+ _validate .validate_output_spec (insert_output , output_spec , Output )
337347
338348 context_value .set (callback_ctx )
339349
@@ -342,6 +352,7 @@ def add_context(*args, **kwargs):
342352 )
343353
344354 response = {"multi" : True }
355+ has_update = False
345356
346357 if long is not None :
347358 if not callback_manager :
@@ -443,6 +454,10 @@ def add_context(*args, **kwargs):
443454 NoUpdate () if NoUpdate .is_no_update (r ) else r
444455 for r in output_value
445456 ]
457+ updated_props = callback_manager .get_updated_props (cache_key )
458+ if len (updated_props ) > 0 :
459+ response ["sideUpdate" ] = updated_props
460+ has_update = True
446461
447462 if output_value is callback_manager .UNDEFINED :
448463 return to_json (response )
@@ -452,35 +467,49 @@ def add_context(*args, **kwargs):
452467 if NoUpdate .is_no_update (output_value ):
453468 raise PreventUpdate
454469
455- if not multi :
456- output_value , output_spec = [output_value ], [output_spec ]
457- flat_output_values = output_value
458- else :
459- if isinstance (output_value , (list , tuple )):
460- # For multi-output, allow top-level collection to be
461- # list or tuple
462- output_value = list (output_value )
463-
464- # Flatten grouping and validate grouping structure
465- flat_output_values = flatten_grouping (output_value , output )
470+ component_ids = collections .defaultdict (dict )
466471
467- _validate .validate_multi_return (
468- output_spec , flat_output_values , callback_id
469- )
472+ if has_output :
473+ if not multi :
474+ output_value , output_spec = [output_value ], [output_spec ]
475+ flat_output_values = output_value
476+ else :
477+ if isinstance (output_value , (list , tuple )):
478+ # For multi-output, allow top-level collection to be
479+ # list or tuple
480+ output_value = list (output_value )
481+
482+ # Flatten grouping and validate grouping structure
483+ flat_output_values = flatten_grouping (output_value , output )
484+
485+ _validate .validate_multi_return (
486+ output_spec , flat_output_values , callback_id
487+ )
470488
471- component_ids = collections .defaultdict (dict )
472- has_update = False
473- for val , spec in zip (flat_output_values , output_spec ):
474- if isinstance (val , NoUpdate ):
475- continue
476- for vali , speci in (
477- zip (val , spec ) if isinstance (spec , list ) else [[val , spec ]]
478- ):
479- if not isinstance (vali , NoUpdate ):
480- has_update = True
481- id_str = stringify_id (speci ["id" ])
482- prop = clean_property_name (speci ["property" ])
483- component_ids [id_str ][prop ] = vali
489+ for val , spec in zip (flat_output_values , output_spec ):
490+ if isinstance (val , NoUpdate ):
491+ continue
492+ for vali , speci in (
493+ zip (val , spec ) if isinstance (spec , list ) else [[val , spec ]]
494+ ):
495+ if not isinstance (vali , NoUpdate ):
496+ has_update = True
497+ id_str = stringify_id (speci ["id" ])
498+ prop = clean_property_name (speci ["property" ])
499+ component_ids [id_str ][prop ] = vali
500+ else :
501+ if output_value is not None :
502+ raise InvalidCallbackReturnValue (
503+ f"No-output callback received return value: { output_value } "
504+ )
505+ output_value = []
506+ flat_output_values = []
507+
508+ if not long :
509+ side_update = dict (callback_ctx .updated_props )
510+ if len (side_update ) > 0 :
511+ has_update = True
512+ response ["sideUpdate" ] = side_update
484513
485514 if not has_update :
486515 raise PreventUpdate
0 commit comments