@@ -356,25 +356,40 @@ def register_callback(
356
356
def initialize_context (args , kwargs , inputs_state_indices ):
357
357
"""Initialize context and validate output specifications."""
358
358
app = kwargs .pop ("app" , None )
359
- output_spec = kwargs .pop ("outputs_list" , [] )
359
+ output_spec = kwargs .pop ("outputs_list" )
360
360
callback_ctx = kwargs .pop (
361
361
"callback_context" , AttributeDict ({"updated_props" : {}})
362
362
)
363
363
context_value .set (callback_ctx )
364
- has_output = False
365
- if len (output_spec ) > 0 :
366
- has_output = True
364
+ original_packages = set (ComponentRegistry .registry )
367
365
368
366
if has_output :
369
367
_validate .validate_output_spec (insert_output , output_spec , Output )
370
368
371
369
func_args , func_kwargs = _validate .validate_and_group_input_args (
372
370
args , inputs_state_indices
373
371
)
374
- return output_spec , callback_ctx , func_args , func_kwargs , app
372
+ return (
373
+ output_spec ,
374
+ callback_ctx ,
375
+ func_args ,
376
+ func_kwargs ,
377
+ app ,
378
+ original_packages ,
379
+ False ,
380
+ )
375
381
376
382
def handle_long_callback (
377
- kwargs , long , long_key , response , error_handler , func , func_args , func_kwargs
383
+ kwargs ,
384
+ long ,
385
+ long_key ,
386
+ callback_ctx ,
387
+ response ,
388
+ error_handler ,
389
+ func ,
390
+ func_args ,
391
+ func_kwargs ,
392
+ has_update = False ,
378
393
):
379
394
"""Set up the long callback and manage jobs."""
380
395
callback_manager = long .get (
@@ -437,7 +452,7 @@ def handle_long_callback(
437
452
data ["progressDefault" ] = {
438
453
str (o ): x for o , x in zip (progress_outputs , progress_default )
439
454
}
440
- return to_json (data ), True
455
+ return to_json (data ), True , has_update
441
456
if progress_outputs :
442
457
# Get the progress before the result as it would be erased after the results.
443
458
progress = callback_manager .get_progress (cache_key )
@@ -463,6 +478,12 @@ def handle_long_callback(
463
478
464
479
if output_value is None :
465
480
output_value = NoUpdate ()
481
+ # set_props from the error handler uses the original ctx
482
+ # instead of manager.get_updated_props since it runs in the
483
+ # request process.
484
+ has_update = (
485
+ _set_side_update (callback_ctx , response ) or output_value is not None
486
+ )
466
487
else :
467
488
raise exc
468
489
@@ -477,24 +498,45 @@ def handle_long_callback(
477
498
updated_props = callback_manager .get_updated_props (cache_key )
478
499
if len (updated_props ) > 0 :
479
500
response ["sideUpdate" ] = updated_props
501
+ has_update = True
480
502
481
503
if output_value is callback_manager .UNDEFINED :
482
- return to_json (response ), True
483
- return output_value , False
484
-
485
- def prepare_response (output_value , output_spec , multi , response , callback_ctx , app ):
504
+ return to_json (response ), True , has_update
505
+ return output_value , False , has_update
506
+
507
+ def prepare_response (
508
+ output_value ,
509
+ output_spec ,
510
+ multi ,
511
+ response ,
512
+ callback_ctx ,
513
+ app ,
514
+ original_packages ,
515
+ long ,
516
+ has_update ,
517
+ ):
486
518
"""Prepare the response object based on the callback output."""
487
519
component_ids = collections .defaultdict (dict )
488
- original_packages = set (ComponentRegistry .registry )
489
520
490
- if output_spec :
521
+ if has_output :
491
522
if not multi :
492
523
output_value , output_spec = [output_value ], [output_spec ]
493
524
flat_output_values = output_value
494
525
else :
495
526
if isinstance (output_value , (list , tuple )):
527
+ # For multi-output, allow top-level collection to be
528
+ # list or tuple
496
529
output_value = list (output_value )
497
- flat_output_values = flatten_grouping (output_value , output_spec )
530
+ if NoUpdate .is_no_update (output_value ):
531
+ flat_output_values = [output_value ]
532
+ else :
533
+ # Flatten grouping and validate grouping structure
534
+ flat_output_values = flatten_grouping (output_value , output )
535
+
536
+ if not NoUpdate .is_no_update (output_value ):
537
+ _validate .validate_multi_return (
538
+ output_spec , flat_output_values , callback_id
539
+ )
498
540
499
541
for val , spec in zip (flat_output_values , output_spec ):
500
542
if NoUpdate .is_no_update (val ):
@@ -503,6 +545,7 @@ def prepare_response(output_value, output_spec, multi, response, callback_ctx, a
503
545
zip (val , spec ) if isinstance (spec , list ) else [[val , spec ]]
504
546
):
505
547
if not NoUpdate .is_no_update (vali ):
548
+ has_update = True
506
549
id_str = stringify_id (speci ["id" ])
507
550
prop = clean_property_name (speci ["property" ])
508
551
component_ids [id_str ][prop ] = vali
@@ -513,11 +556,8 @@ def prepare_response(output_value, output_spec, multi, response, callback_ctx, a
513
556
f"No-output callback received return value: { output_value } "
514
557
)
515
558
516
- has_update = (
517
- _set_side_update (callback_ctx , response )
518
- or has_output
519
- or response ["sideUpdate" ]
520
- )
559
+ if not long :
560
+ has_update = _set_side_update (callback_ctx , response ) or has_output
521
561
522
562
if not has_update :
523
563
raise PreventUpdate
@@ -550,18 +590,25 @@ def add_context(*args, **kwargs):
550
590
"""Handles synchronous callbacks with context management."""
551
591
error_handler = on_error or kwargs .pop ("app_on_error" , None )
552
592
553
- output_spec , callback_ctx , func_args , func_kwargs , app = initialize_context (
554
- args , kwargs , inputs_state_indices
555
- )
593
+ (
594
+ output_spec ,
595
+ callback_ctx ,
596
+ func_args ,
597
+ func_kwargs ,
598
+ app ,
599
+ original_packages ,
600
+ has_update ,
601
+ ) = initialize_context (args , kwargs , inputs_state_indices )
556
602
557
603
response : dict = {"multi" : True }
558
604
559
605
try :
560
606
if long is not None :
561
- output_value , skip = handle_long_callback (
607
+ output_value , skip , has_update = handle_long_callback (
562
608
kwargs ,
563
609
long ,
564
610
long_key ,
611
+ callback_ctx ,
565
612
response ,
566
613
error_handler ,
567
614
func ,
@@ -589,6 +636,9 @@ def add_context(*args, **kwargs):
589
636
response ,
590
637
callback_ctx ,
591
638
app ,
639
+ original_packages ,
640
+ long ,
641
+ has_update ,
592
642
)
593
643
try :
594
644
jsonResponse = to_json (response )
@@ -602,18 +652,25 @@ async def async_add_context(*args, **kwargs):
602
652
"""Handles async callbacks with context management."""
603
653
error_handler = on_error or kwargs .pop ("app_on_error" , None )
604
654
605
- output_spec , callback_ctx , func_args , func_kwargs , app = initialize_context (
606
- args , kwargs , inputs_state_indices
607
- )
655
+ (
656
+ output_spec ,
657
+ callback_ctx ,
658
+ func_args ,
659
+ func_kwargs ,
660
+ app ,
661
+ original_packages ,
662
+ has_update ,
663
+ ) = initialize_context (args , kwargs , inputs_state_indices )
608
664
609
665
response : dict = {"multi" : True }
610
666
611
667
try :
612
668
if long is not None :
613
- output_value , skip = handle_long_callback (
669
+ output_value , skip , has_update = handle_long_callback (
614
670
kwargs ,
615
671
long ,
616
672
long_key ,
673
+ callback_ctx ,
617
674
response ,
618
675
error_handler ,
619
676
func ,
@@ -643,6 +700,9 @@ async def async_add_context(*args, **kwargs):
643
700
response ,
644
701
callback_ctx ,
645
702
app ,
703
+ original_packages ,
704
+ long ,
705
+ has_update ,
646
706
)
647
707
try :
648
708
jsonResponse = to_json (response )
0 commit comments