Skip to content

Commit 96df44e

Browse files
committed
fixing for multi outputs
1 parent 4f14a1a commit 96df44e

File tree

1 file changed

+87
-27
lines changed

1 file changed

+87
-27
lines changed

dash/_callback.py

Lines changed: 87 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -356,25 +356,40 @@ def register_callback(
356356
def initialize_context(args, kwargs, inputs_state_indices):
357357
"""Initialize context and validate output specifications."""
358358
app = kwargs.pop("app", None)
359-
output_spec = kwargs.pop("outputs_list", [])
359+
output_spec = kwargs.pop("outputs_list")
360360
callback_ctx = kwargs.pop(
361361
"callback_context", AttributeDict({"updated_props": {}})
362362
)
363363
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)
367365

368366
if has_output:
369367
_validate.validate_output_spec(insert_output, output_spec, Output)
370368

371369
func_args, func_kwargs = _validate.validate_and_group_input_args(
372370
args, inputs_state_indices
373371
)
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+
)
375381

376382
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,
378393
):
379394
"""Set up the long callback and manage jobs."""
380395
callback_manager = long.get(
@@ -437,7 +452,7 @@ def handle_long_callback(
437452
data["progressDefault"] = {
438453
str(o): x for o, x in zip(progress_outputs, progress_default)
439454
}
440-
return to_json(data), True
455+
return to_json(data), True, has_update
441456
if progress_outputs:
442457
# Get the progress before the result as it would be erased after the results.
443458
progress = callback_manager.get_progress(cache_key)
@@ -463,6 +478,12 @@ def handle_long_callback(
463478

464479
if output_value is None:
465480
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+
)
466487
else:
467488
raise exc
468489

@@ -477,24 +498,45 @@ def handle_long_callback(
477498
updated_props = callback_manager.get_updated_props(cache_key)
478499
if len(updated_props) > 0:
479500
response["sideUpdate"] = updated_props
501+
has_update = True
480502

481503
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+
):
486518
"""Prepare the response object based on the callback output."""
487519
component_ids = collections.defaultdict(dict)
488-
original_packages = set(ComponentRegistry.registry)
489520

490-
if output_spec:
521+
if has_output:
491522
if not multi:
492523
output_value, output_spec = [output_value], [output_spec]
493524
flat_output_values = output_value
494525
else:
495526
if isinstance(output_value, (list, tuple)):
527+
# For multi-output, allow top-level collection to be
528+
# list or tuple
496529
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+
)
498540

499541
for val, spec in zip(flat_output_values, output_spec):
500542
if NoUpdate.is_no_update(val):
@@ -503,6 +545,7 @@ def prepare_response(output_value, output_spec, multi, response, callback_ctx, a
503545
zip(val, spec) if isinstance(spec, list) else [[val, spec]]
504546
):
505547
if not NoUpdate.is_no_update(vali):
548+
has_update = True
506549
id_str = stringify_id(speci["id"])
507550
prop = clean_property_name(speci["property"])
508551
component_ids[id_str][prop] = vali
@@ -513,11 +556,8 @@ def prepare_response(output_value, output_spec, multi, response, callback_ctx, a
513556
f"No-output callback received return value: {output_value}"
514557
)
515558

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
521561

522562
if not has_update:
523563
raise PreventUpdate
@@ -550,18 +590,25 @@ def add_context(*args, **kwargs):
550590
"""Handles synchronous callbacks with context management."""
551591
error_handler = on_error or kwargs.pop("app_on_error", None)
552592

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)
556602

557603
response: dict = {"multi": True}
558604

559605
try:
560606
if long is not None:
561-
output_value, skip = handle_long_callback(
607+
output_value, skip, has_update = handle_long_callback(
562608
kwargs,
563609
long,
564610
long_key,
611+
callback_ctx,
565612
response,
566613
error_handler,
567614
func,
@@ -589,6 +636,9 @@ def add_context(*args, **kwargs):
589636
response,
590637
callback_ctx,
591638
app,
639+
original_packages,
640+
long,
641+
has_update,
592642
)
593643
try:
594644
jsonResponse = to_json(response)
@@ -602,18 +652,25 @@ async def async_add_context(*args, **kwargs):
602652
"""Handles async callbacks with context management."""
603653
error_handler = on_error or kwargs.pop("app_on_error", None)
604654

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)
608664

609665
response: dict = {"multi": True}
610666

611667
try:
612668
if long is not None:
613-
output_value, skip = handle_long_callback(
669+
output_value, skip, has_update = handle_long_callback(
614670
kwargs,
615671
long,
616672
long_key,
673+
callback_ctx,
617674
response,
618675
error_handler,
619676
func,
@@ -643,6 +700,9 @@ async def async_add_context(*args, **kwargs):
643700
response,
644701
callback_ctx,
645702
app,
703+
original_packages,
704+
long,
705+
has_update,
646706
)
647707
try:
648708
jsonResponse = to_json(response)

0 commit comments

Comments
 (0)