Skip to content

Commit 94b4bdf

Browse files
committed
Backward compatibility
Allow to use named parameters in callback Raise exceptions if outputs, inputs or states are not ordered
1 parent 775bf09 commit 94b4bdf

File tree

1 file changed

+31
-33
lines changed

1 file changed

+31
-33
lines changed

dash/dash.py

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,19 @@ class _NoUpdate(object):
101101
ns["{function_name}"] = {clientside_function};
102102
"""
103103

104-
105-
def _handle_callback_args(args, kwargs):
104+
def extract_callback_args(args, kwargs, name, type_):
105+
"""Extract arguments for callback from a name and type"""
106+
print(args, kwargs)
107+
parameters = kwargs.get(name, [])
108+
if not parameters:
109+
while args and isinstance(args[0], type_):
110+
parameters.append(args.pop(0))
111+
return parameters
112+
113+
def _handle_callback_args(*args, **kwargs):
106114
"""Split args into outputs, inputs and states"""
107-
prevent_initial_call = None
108-
for k, v in kwargs.items():
109-
if k == "prevent_initial_call":
110-
prevent_initial_call = v
111-
else:
112-
raise TypeError(
113-
"callback got an unexpected keyword argument '{}'".format(k)
114-
)
115+
prevent_initial_call = kwargs.get('prevent_initial_call', None)
116+
# flatten args
115117
args = [
116118
arg
117119
# for backward compatibility, one arg can be a list
@@ -121,14 +123,17 @@ def _handle_callback_args(args, kwargs):
121123
arg_or_list if isinstance(arg_or_list, (list, tuple)) else [arg_or_list]
122124
)
123125
]
126+
outputs = extract_callback_args(args, kwargs, 'output', Output)
127+
inputs = extract_callback_args(args, kwargs, 'inputs', Input)
128+
states = extract_callback_args(args, kwargs, 'state', State)
129+
130+
if args:
131+
raise TypeError(
132+
"callback must received first all Outputs, then all Inputs, then all States")
124133
return [
125-
# split according to type Output, Input, State
126-
[arg for arg in args if isinstance(arg, class_)]
127-
for class_ in [Output, Input, State]
128-
] + [
129-
# keep list of args in order, for matching order
130-
# in the callback's parameters
131-
[arg for arg in args if not isinstance(arg, Output)],
134+
outputs,
135+
inputs,
136+
states,
132137
prevent_initial_call,
133138
]
134139

@@ -857,7 +862,7 @@ def dependencies(self):
857862
return flask.jsonify(self._callback_list)
858863

859864
def _insert_callback(
860-
self, output, inputs, state, callback_args, prevent_initial_call
865+
self, output, inputs, state, prevent_initial_call
861866
):
862867
if prevent_initial_call is None:
863868
prevent_initial_call = self.config.prevent_initial_callbacks
@@ -868,14 +873,12 @@ def _insert_callback(
868873
"output": callback_id,
869874
"inputs": [c.to_dict() for c in inputs],
870875
"state": [c.to_dict() for c in state],
871-
"args": [c.to_dict() for c in callback_args],
872876
"clientside_function": None,
873877
"prevent_initial_call": prevent_initial_call,
874878
}
875879
self.callback_map[callback_id] = {
876880
"inputs": callback_spec["inputs"],
877881
"state": callback_spec["state"],
878-
"args": callback_spec["args"],
879882
}
880883
self._callback_list.append(callback_spec)
881884

@@ -996,18 +999,21 @@ def callback(self, *args, **kwargs):
996999
not to fire when its outputs are first added to the page. Defaults to
9971000
`False` unless `prevent_initial_callbacks=True` at the app level.
9981001
"""
1002+
kwargs['prevent_initial_call'] = kwargs.get(
1003+
'prevent_initial_call', None)
1004+
output = kwargs.get('output', args[0])
9991005
# for backward compatibility, store whether first argument is a
10001006
# list of only 1 Output
1001-
specified_output_list = isinstance(args[0], (list, tuple)) and len(args[0]) == 1
1007+
specified_output_list = (
1008+
isinstance(output, (list, tuple)) and len(output) == 1)
10021009
(
10031010
output,
10041011
inputs,
10051012
state,
1006-
callback_args,
10071013
prevent_initial_call,
1008-
) = _handle_callback_args(args, kwargs)
1014+
) = _handle_callback_args(*args, **kwargs)
10091015
callback_id = self._insert_callback(
1010-
output, inputs, state, callback_args, prevent_initial_call
1016+
output, inputs, state, prevent_initial_call
10111017
)
10121018

10131019
def wrap_func(func):
@@ -1078,15 +1084,7 @@ def dispatch(self):
10781084

10791085
response = flask.g.dash_response = flask.Response(mimetype="application/json")
10801086

1081-
# frontend sends inputs and state in separate variables
1082-
# we need to reorder them for the callback
1083-
args_inputs = [
1084-
value
1085-
for arg in self.callback_map[output]["args"]
1086-
for value in (inputs + state)
1087-
if arg["id"] == value["id"] and arg["property"] == value["property"]
1088-
]
1089-
args = inputs_to_vals(args_inputs)
1087+
args = inputs_to_vals(inputs + state)
10901088

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

0 commit comments

Comments
 (0)