Skip to content

Commit 35f35e6

Browse files
wilhelmhbalexcjohnson
authored andcommitted
Allow callback arguments to be passed outside lists
1 parent e9f5559 commit 35f35e6

File tree

1 file changed

+55
-16
lines changed

1 file changed

+55
-16
lines changed

dash/dash.py

Lines changed: 55 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
from .fingerprint import build_fingerprint, check_fingerprint
2626
from .resources import Scripts, Css
27+
from .dependencies import Input, Output, State
2728
from .development.base_component import ComponentRegistry
2829
from .exceptions import PreventUpdate, InvalidResourceError
2930
from .version import __version__
@@ -824,7 +825,7 @@ def interpolate_index(self, **kwargs):
824825
def dependencies(self):
825826
return flask.jsonify(self._callback_list)
826827

827-
def _insert_callback(self, output, inputs, state, prevent_initial_call):
828+
def _insert_callback(self, output, inputs, state, callback_args, prevent_initial_call):
828829
if prevent_initial_call is None:
829830
prevent_initial_call = self.config.prevent_initial_callbacks
830831

@@ -834,20 +835,20 @@ def _insert_callback(self, output, inputs, state, prevent_initial_call):
834835
"output": callback_id,
835836
"inputs": [c.to_dict() for c in inputs],
836837
"state": [c.to_dict() for c in state],
838+
"args": [c.to_dict() for c in callback_args],
837839
"clientside_function": None,
838840
"prevent_initial_call": prevent_initial_call,
839841
}
840842
self.callback_map[callback_id] = {
841843
"inputs": callback_spec["inputs"],
842844
"state": callback_spec["state"],
845+
"args": callback_spec["args"],
843846
}
844847
self._callback_list.append(callback_spec)
845848

846849
return callback_id
847850

848-
def clientside_callback(
849-
self, clientside_function, output, input_, state=(), prevent_initial_call=None
850-
):
851+
def clientside_callback(self, clientside_function, *args, **kwargs):
851852
"""Create a callback that updates the output by calling a clientside
852853
(JavaScript) function instead of a Python function.
853854
@@ -912,10 +913,8 @@ def clientside_callback(
912913
not to fire when its outputs are first added to the page. Defaults to
913914
`False` unless `prevent_initial_callbacks=True` at the app level.
914915
"""
915-
is_multi_input = isinstance(input_, (list, tuple))
916-
inputs = input_ if is_multi_input else [input_]
917-
918-
self._insert_callback(output, inputs, state, prevent_initial_call)
916+
output, inputs, state, callback_args, prevent_initial_call = self._handle_callback_args(args, kwargs)
917+
self._insert_callback(output, inputs, state, callback_args)
919918

920919
# If JS source is explicitly given, create a namespace and function
921920
# name, then inject the code.
@@ -946,7 +945,38 @@ def clientside_callback(
946945
"function_name": function_name,
947946
}
948947

949-
def callback(self, output, input_, state=(), prevent_initial_call=None):
948+
def _handle_callback_args(self, args, kwargs):
949+
"""Split args into outputs, inputs and states"""
950+
prevent_initial_call = None
951+
for k, v in kwargs.items():
952+
if k == "prevent_initial_call":
953+
prevent_initial_call = v
954+
else:
955+
raise TypeError(
956+
"callback got an unexpected keyword argument '{}'".format(k)
957+
)
958+
args = [
959+
arg
960+
# for backward compatibility, one arg can be a list
961+
for arg_or_list in args
962+
# flatten args that are lists
963+
for arg in (
964+
arg_or_list if isinstance(arg_or_list, (list, tuple))
965+
else [arg_or_list]
966+
)
967+
]
968+
return [
969+
# split according to type Output, Input, State
970+
[arg for arg in args if isinstance(arg, class_)]
971+
for class_ in [Output, Input, State]
972+
] + [
973+
# keep list of args in order, for matching order
974+
# in the callback's parameters
975+
[arg for arg in args if not isinstance(arg, Output)],
976+
prevent_initial_call
977+
]
978+
979+
def callback(self, *args, **kwargs):
950980
"""
951981
Normally used as a decorator, `@app.callback` provides a server-side
952982
callback relating the values of one or more `output` items to one or
@@ -958,10 +988,8 @@ def callback(self, output, input_, state=(), prevent_initial_call=None):
958988
not to fire when its outputs are first added to the page. Defaults to
959989
`False` unless `prevent_initial_callbacks=True` at the app level.
960990
"""
961-
is_multi_input = isinstance(input_, (list, tuple))
962-
inputs = input_ if is_multi_input else [input_]
963-
callback_id = self._insert_callback(output, inputs, state, prevent_initial_call)
964-
multi = isinstance(output, (list, tuple))
991+
output, inputs, state, callback_args, prevent_initial_call = self._handle_callback_args(args, kwargs)
992+
callback_id = self._insert_callback(output, inputs, state, callback_args, prevent_initial_call)
965993

966994
def wrap_func(func):
967995
@wraps(func)
@@ -976,8 +1004,11 @@ def add_context(*args, **kwargs):
9761004

9771005
# wrap single outputs so we can treat them all the same
9781006
# for validation and response creation
979-
if not multi:
980-
output_value, output_spec = [output_value], [output_spec]
1007+
if not isinstance(output_value, (list, tuple)):
1008+
if not isinstance(output_spec, (list, tuple)):
1009+
output_value, output_spec = [output_value], [output_spec]
1010+
else:
1011+
output_value, output_spec = [output_value], output_spec
9811012

9821013
_validate.validate_multi_return(output_spec, output_value, callback_id)
9831014

@@ -1031,7 +1062,15 @@ def dispatch(self):
10311062

10321063
response = flask.g.dash_response = flask.Response(mimetype="application/json")
10331064

1034-
args = inputs_to_vals(inputs) + inputs_to_vals(state)
1065+
# frontend sends inputs and state in separate variables
1066+
# we need to reorder them for the callback
1067+
args_inputs = [
1068+
value
1069+
for arg in self.callback_map[output]["args"]
1070+
for value in (inputs + state)
1071+
if arg['id'] == value['id']
1072+
and arg['property'] == value['property']]
1073+
args = inputs_to_vals(args_inputs)
10351074

10361075
func = self.callback_map[output]["callback"]
10371076
response.set_data(func(*args, outputs_list=outputs_list))

0 commit comments

Comments
 (0)