Skip to content

Commit

Permalink
Validate allow_duplicate with prevent_initial_call.
Browse files Browse the repository at this point in the history
  • Loading branch information
T4rk1n committed Feb 20, 2023
1 parent 6cbad7e commit c8c756c
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 3 deletions.
8 changes: 7 additions & 1 deletion dash/_callback.py
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,19 @@ def insert_callback(
if prevent_initial_call is None:
prevent_initial_call = config_prevent_initial_callbacks

_validate.validate_duplicate_output(
output, prevent_initial_call, config_prevent_initial_callbacks
)

callback_id = create_callback_id(output)
callback_spec = {
"output": callback_id,
"inputs": [c.to_dict() for c in inputs],
"state": [c.to_dict() for c in state],
"clientside_function": None,
"prevent_initial_call": prevent_initial_call,
# prevent_initial_call can be a string "initial_duplicates"
# which should not prevent the initial call.
"prevent_initial_call": prevent_initial_call is True,
"long": long
and {
"interval": long["interval"],
Expand Down
27 changes: 27 additions & 0 deletions dash/_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -521,3 +521,30 @@ def validate_long_callbacks(callback_map):
f"Long callback circular error!\n{circular} is used as input for a long callback"
f" but also used as output from an input that is updated with progress or running argument."
)


def validate_duplicate_output(
output, prevent_initial_call, config_prevent_initial_call
):

if "initial_duplicate" in (prevent_initial_call, config_prevent_initial_call):
return

def _valid(out):
if out.allow_duplicate is True and not (
prevent_initial_call or config_prevent_initial_call
):
raise exceptions.DuplicateCallback(
"allow_duplicate requires prevent_initial_call to be True. The order of the call is not"
" guaranteed to be the same on every page load. "
"To enable duplicate callback with initial call, set prevent_initial_call='initial_duplicate' "
" or globally in the config prevent_initial_callbacks='initial_duplicate'"
)

if isinstance(output, (list, tuple)):
for o in output:
_valid(o)

return

_valid(output)
35 changes: 33 additions & 2 deletions tests/unit/library/test_validate.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

from dash import Output
from dash.html import Div
from dash.exceptions import InvalidCallbackReturnValue
from dash._validate import fail_callback_output
from dash.exceptions import InvalidCallbackReturnValue, DuplicateCallback
from dash._validate import fail_callback_output, validate_duplicate_output


@pytest.mark.parametrize(
Expand Down Expand Up @@ -36,3 +36,34 @@ def test_ddvl001_fail_handler_fails_correctly(val):

with pytest.raises(InvalidCallbackReturnValue):
fail_callback_output(val, outputs)


@pytest.mark.parametrize(
"output, prevent_initial_call, config_prevent_initial_call, expect_error",
[
(Output("a", "a", allow_duplicate=True), True, False, False),
(Output("a", "a", allow_duplicate=True), False, True, False),
(Output("a", "a", allow_duplicate=True), True, True, False),
(Output("a", "a", allow_duplicate=True), False, False, True),
(Output("a", "a", allow_duplicate=True), "initial_duplicates", False, False),
(Output("a", "a", allow_duplicate=True), False, "initial_duplicates", False),
(Output("a", "a"), False, False, False),
],
)
def test_ddv002_allow_duplicate_validation(
output, prevent_initial_call, config_prevent_initial_call, expect_error
):
valid = False
if expect_error:
with pytest.raises(DuplicateCallback) as err:
validate_duplicate_output(
output, prevent_initial_call, config_prevent_initial_call
)
valid = err is not None
else:
validate_duplicate_output(
output, prevent_initial_call, config_prevent_initial_call
)
valid = True

assert valid

0 comments on commit c8c756c

Please sign in to comment.