24
24
25
25
from .fingerprint import build_fingerprint , check_fingerprint
26
26
from .resources import Scripts , Css
27
+ from .dependencies import Input , Output , State
27
28
from .development .base_component import ComponentRegistry
28
29
from .exceptions import PreventUpdate , InvalidResourceError
29
30
from .version import __version__
@@ -824,7 +825,7 @@ def interpolate_index(self, **kwargs):
824
825
def dependencies (self ):
825
826
return flask .jsonify (self ._callback_list )
826
827
827
- def _insert_callback (self , output , inputs , state , prevent_initial_call ):
828
+ def _insert_callback (self , output , inputs , state , callback_args , prevent_initial_call ):
828
829
if prevent_initial_call is None :
829
830
prevent_initial_call = self .config .prevent_initial_callbacks
830
831
@@ -834,20 +835,20 @@ def _insert_callback(self, output, inputs, state, prevent_initial_call):
834
835
"output" : callback_id ,
835
836
"inputs" : [c .to_dict () for c in inputs ],
836
837
"state" : [c .to_dict () for c in state ],
838
+ "args" : [c .to_dict () for c in callback_args ],
837
839
"clientside_function" : None ,
838
840
"prevent_initial_call" : prevent_initial_call ,
839
841
}
840
842
self .callback_map [callback_id ] = {
841
843
"inputs" : callback_spec ["inputs" ],
842
844
"state" : callback_spec ["state" ],
845
+ "args" : callback_spec ["args" ],
843
846
}
844
847
self ._callback_list .append (callback_spec )
845
848
846
849
return callback_id
847
850
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 ):
851
852
"""Create a callback that updates the output by calling a clientside
852
853
(JavaScript) function instead of a Python function.
853
854
@@ -912,10 +913,8 @@ def clientside_callback(
912
913
not to fire when its outputs are first added to the page. Defaults to
913
914
`False` unless `prevent_initial_callbacks=True` at the app level.
914
915
"""
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 )
919
918
920
919
# If JS source is explicitly given, create a namespace and function
921
920
# name, then inject the code.
@@ -946,7 +945,38 @@ def clientside_callback(
946
945
"function_name" : function_name ,
947
946
}
948
947
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 ):
950
980
"""
951
981
Normally used as a decorator, `@app.callback` provides a server-side
952
982
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):
958
988
not to fire when its outputs are first added to the page. Defaults to
959
989
`False` unless `prevent_initial_callbacks=True` at the app level.
960
990
"""
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 )
965
993
966
994
def wrap_func (func ):
967
995
@wraps (func )
@@ -976,8 +1004,11 @@ def add_context(*args, **kwargs):
976
1004
977
1005
# wrap single outputs so we can treat them all the same
978
1006
# 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
981
1012
982
1013
_validate .validate_multi_return (output_spec , output_value , callback_id )
983
1014
@@ -1031,7 +1062,15 @@ def dispatch(self):
1031
1062
1032
1063
response = flask .g .dash_response = flask .Response (mimetype = "application/json" )
1033
1064
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 )
1035
1074
1036
1075
func = self .callback_map [output ]["callback" ]
1037
1076
response .set_data (func (* args , outputs_list = outputs_list ))
0 commit comments