Skip to content

Commit 88cdad4

Browse files
committed
Validate allow_duplicate with prevent_initial_call.
1 parent 6cbad7e commit 88cdad4

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-3
lines changed

dash/_callback.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,13 +241,19 @@ def insert_callback(
241241
if prevent_initial_call is None:
242242
prevent_initial_call = config_prevent_initial_callbacks
243243

244+
_validate.validate_duplicate_output(
245+
output, prevent_initial_call, config_prevent_initial_callbacks
246+
)
247+
244248
callback_id = create_callback_id(output)
245249
callback_spec = {
246250
"output": callback_id,
247251
"inputs": [c.to_dict() for c in inputs],
248252
"state": [c.to_dict() for c in state],
249253
"clientside_function": None,
250-
"prevent_initial_call": prevent_initial_call,
254+
# prevent_initial_call can be a string "initial_duplicates"
255+
# which should not prevent the initial call.
256+
"prevent_initial_call": prevent_initial_call is True,
251257
"long": long
252258
and {
253259
"interval": long["interval"],

dash/_validate.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,3 +521,32 @@ def validate_long_callbacks(callback_map):
521521
f"Long callback circular error!\n{circular} is used as input for a long callback"
522522
f" but also used as output from an input that is updated with progress or running argument."
523523
)
524+
525+
526+
def validate_duplicate_output(
527+
output, prevent_initial_call, config_prevent_initial_call
528+
):
529+
530+
if "initial_duplicate" in (prevent_initial_call, config_prevent_initial_call):
531+
return
532+
533+
def _valid(out):
534+
if (
535+
out.allow_duplicate
536+
and not prevent_initial_call
537+
and not config_prevent_initial_call
538+
):
539+
raise exceptions.DuplicateCallback(
540+
"allow_duplicate requires prevent_initial_call to be True. The order of the call is not"
541+
" guaranteed to be the same on every page load. "
542+
"To enable duplicate callback with initial call, set prevent_initial_call='initial_duplicate' "
543+
" or globally in the config prevent_initial_callbacks='initial_duplicate'"
544+
)
545+
546+
if isinstance(output, (list, tuple)):
547+
for o in output:
548+
_valid(o)
549+
550+
return
551+
552+
_valid(output)

tests/unit/library/test_validate.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22

33
from dash import Output
44
from dash.html import Div
5-
from dash.exceptions import InvalidCallbackReturnValue
6-
from dash._validate import fail_callback_output
5+
from dash.exceptions import InvalidCallbackReturnValue, DuplicateCallback
6+
from dash._validate import fail_callback_output, validate_duplicate_output
77

88

99
@pytest.mark.parametrize(
@@ -36,3 +36,29 @@ def test_ddvl001_fail_handler_fails_correctly(val):
3636

3737
with pytest.raises(InvalidCallbackReturnValue):
3838
fail_callback_output(val, outputs)
39+
40+
41+
@pytest.mark.parametrize(
42+
"output, prevent_initial_call, config_prevent_initial_call, expect_error",
43+
[
44+
(Output("a", "a", allow_duplicate=True), True, False, False),
45+
(Output("a", "a", allow_duplicate=True), False, True, False),
46+
(Output("a", "a", allow_duplicate=True), True, True, False),
47+
(Output("a", "a", allow_duplicate=True), False, False, True),
48+
(Output("a", "a", allow_duplicate=True), "initial_duplicate", False, False),
49+
(Output("a", "a", allow_duplicate=True), False, "initial_duplicate", False),
50+
(Output("a", "a"), False, False, False),
51+
],
52+
)
53+
def test_ddv002_allow_duplicate_validation(
54+
output, prevent_initial_call, config_prevent_initial_call, expect_error
55+
):
56+
if expect_error:
57+
with pytest.raises(DuplicateCallback):
58+
validate_duplicate_output(
59+
output, prevent_initial_call, config_prevent_initial_call
60+
)
61+
else:
62+
validate_duplicate_output(
63+
output, prevent_initial_call, config_prevent_initial_call
64+
)

0 commit comments

Comments
 (0)