Data Sharing
Method Registration
See the Dash Interop example on GitHub.
To register an Interop method and make it available to other apps, instantiate the MethodRegister
component. Pass an ID for the component, a method definition using the definition
property, and specify whether the method returns a result or not. The method definition can be either the method name as a string or an object with a name
property holding the method name, method signature and other method properties. The JavaScript equivalent of this object is the MethodDefinition object. It is mandatory to specify whether the method returns a result to the calling app.
The following example demonstrates how to register two Interop methods, one of which returns a result and the other is void:
import dash
import dash_glue42
app = dash.Dash(__name__)
app.layout = dash_glue42.Glue42(id="io-connect", children=[
# Registering an Interop method that returns a result.
dash_glue42.MethodRegister(id="io-connect-register-sum", definition={"name": "Sum"}, returns=True),
# Registering an Interop method that doesn't return a result.
dash_glue42.MethodRegister(id="io-connect-register-send-message", definition="SendMessage", returns=False)
])
The MethodRegister
component has an error
property which is set in case the method registration fails. The value of this property is assigned by the framework and must not be altered by client code. You can use it to check whether the Interop method has been registered successfully.
To set the amount of time the component should wait for a reply from the Dash backend, use the methodResponseTimeoutMs
property:
# Specifying time to wait for a reply from the method handler. The default timeout is 30000 ms.
dash_glue42.MethodRegister(id="io-connect-register-sum", definition={"name": "Sum"}, returns=True, methodResponseTimeoutMs=20000)
⚠️ Note that it's very important to set this property accordingly for your specific use cases, as io.Connect Dash executes invocations in a sequence. If a method registered via
MethodRegister
is invoked three times, the invocations will be passed to the Dash callbacks one by one.
Define a handler callback for every registered Interop method. The handler will be triggered each time the method is invoked. For Input
of the callback pass the ID of the respective MethodRegister
component and its invoke
property. The value of this property is assigned by the framework and must not be altered by client code. The invoke
property is an object with invocationId
, args
and caller
properties. Use it to retrieve the invocation ID (if necessary), the arguments for the invocation, and information about the caller. The JavaScript equivalent of the caller
object is the Instance
object. If the method returns a result to the caller, for Output
of the callback pass the ID of the respective MethodRegister
component and its result
property, otherwise pass the ID and a property of the component you want to update directly.
⚠️ Note that when returning a result, it's mandatory to return an
invocationId
property holding the assigned by the framework invocation ID, otherwise the caller won't receive the result. TheinvocationId
property is used to create a correlation between the method invocation and the respective invocation result.
The example below demonstrates how to define handlers for the previously registered Interop methods "Sum" and "SendMessage". The handler for the "Sum" method validates the input arguments and returns either their sum or an error in the result
property of the respective MethodRegister
component. The "SendMessage" method handler directly updates another component with the message received as an argument of the invocation.
# Helper to validate the input arguments for the "Sum" method.
def is_number(s):
try:
float(s)
return True
except ValueError:
return False
# Handler for the "Sum" Interop method.
@app.callback(
Output("io-connect-register-sum", "result"),
Input("io-connect-register-sum", "invoke")
)
def sum_invocation_handler(invoke):
if invoke is None:
raise PreventUpdate
invocationId = invoke.get("invocationId")
args = invoke.get("args", {})
a = args.get("a")
b = args.get("b")
are_numbers = is_number(a) and is_number(b)
if are_numbers:
total = float(a) + float(b)
# When a method isn't void, you must always return the assigned `invocationId`,
# otherwise the caller won't receive the result.
return {
"invocationId": invocationId,
"invocationResult": {
"sum": total
}
}
else:
return {
"invocationId": invocationId,
"error": {
"message": "The arguments must be numbers!"
}
}
# Handler for the "SendMessage" Interop method.
@app.callback(
Output("message", "children"),
Input("io-connect-register-send-message", "invoke")
)
def send_message_invocation_handler(invoke):
if invoke is not None:
args = invoke.get("args", {})
message = args.get("message", "")
return message
Method Invocation
To invoke an Interop method registered by another app, instantiate the MethodInvoke
component and pass an ID for it:
import dash_glue42
app.layout = dash_glue42.Glue42(id="io-connect", children=[
# A component which will invoke the "Sum" Interop method.
dash_glue42.MethodInvoke(id="io-connect-invoke-sum"),
# A component which will invoke the "SendMessage" Interop method.
dash_glue42.MethodInvoke(id="io-connect-invoke-send-message")
])
Define a callback that will trigger invocations of the Interop method. For Output
of the callback pass the ID of the respective MethodInvoke
component and its invoke
property. The callback must return an object with a required definition
property which hold the name or the definition object of the method to invoke. To pass the invocation arguments, use the argumentObj
property of the returned object. You can also specify invocation options by using the options
property. The JavaScript equivalent of the options
object is the InvokeOptions
object.
The following example demonstrates how to define a callback for triggering the "Sum" Interop method when the user clicks a button. The arguments for the method are taken from UI inputs:
# Callback that will trigger "Sum" invocation.
@app.callback(
Output("io-connect-invoke-sum", "invoke"),
Input("sum-numbers-btn", "n_clicks"),
State("number-a", "value"),
State("number-b", "value"),
prevent_initial_call=True
)
def sum_numbers(_, a, b):
return {
"definition": {
"name": "Sum"
},
"argumentObj": {
"a": a,
"b": b
},
"options": {
"methodResponseTimeoutMs": 60000,
"waitTimeoutMs": 60000
}
}
If the Interop method returns a result to the caller, define another callback for handling the result. For Input
of the callback pass the ID of the respective MethodInvoke
component and its result
property. The value of this property is assigned by the framework and must not be altered by client code. Consume the result as per your app logic.
The following example demonstrates how to extract the returned result and the invocation error (if any):
# Callback that will handle the result returned by "Sum".
@app.callback(
Output("sum-numbers-result", "children"),
Input("io-connect-invoke-sum", "result")
)
def sum_numbers_result_handler(result):
if result is None:
raise PreventUpdate
error = result.get("error")
hasError = error is not None
if hasError:
return error.get("message", '')
else:
invocationResult = result.get("invocationResult", {})
sumValue = invocationResult.get("returned", {}).get("sum")
return "Sum is {}".format(sumValue)
Complete example of Interop method invocation:
import dash
from dash.exceptions import PreventUpdate
from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_core_components as dcc
import dash_glue42
from run import server
app = dash.Dash(__name__, server=server, routes_pathname_prefix="/app-a/")
app.layout = dash_glue42.Glue42(id="io-connect", children=[
# A component which will invoke the "Sum" Interop method.
dash_glue42.MethodInvoke(id="io-connect-invoke-sum"),
# A component which will invoke the "SendMessage" Interop method.
dash_glue42.MethodInvoke(id="io-connect-invoke-send-message"),
html.Div([
dcc.Input(id="number-a", type="text",
autoComplete="off", value=37),
dcc.Input(id="number-b", type="text",
autoComplete="off", value=5),
html.Button(id="sum-numbers-btn", children="Sum"),
]),
html.P(id="sum-numbers-result"),
html.Hr(),
html.Div(
[
html.Label("Message: "),
dcc.Input(id="message", type="text", autoComplete="off",
value="Send your daily report!"),
html.Button(id="send-message", children="Send")
]
)
])
# Callback that will trigger "Sum" invocation.
@app.callback(
Output("io-connect-invoke-sum", "invoke"),
Input("sum-numbers-btn", "n_clicks"),
State("number-a", "value"),
State("number-b", "value"),
prevent_initial_call=True
)
def sum_numbers(_, a, b):
return {
"definition": {
"name": "Sum"
},
"argumentObj": {
"a": a,
"b": b
}
}
# Callback that will handle the result returned by "Sum".
@app.callback(
Output("sum-numbers-result", "children"),
Input("io-connect-invoke-sum", "result")
)
def sum_numbers_result_handler(result):
if result is None:
raise PreventUpdate
error = result.get("error")
hasError = error is not None
if hasError:
return error.get("message", '')
else:
invocationResult = result.get("invocationResult", {})
sumValue = invocationResult.get("returned", {}).get("sum")
return "Sum is {}".format(sumValue)
# Callback that will trigger "SendMessage" invocation.
@app.callback(
Output("io-connect-invoke-send-message", "invoke"),
Input("send-message", "n_clicks"),
State("message", "value"),
prevent_initial_call=True
)
def send_message(_, message):
return {
"definition": {
"name": "SendMessage"
},
"argumentObj": {
"message": message
}
}
Targeting
When invoking an Interop method, you can target all Interop servers, the best Interop server or all except the current one. The "best" Interop server is the app instance which has registered the Interop method first. Use the target
property of the invoke
object to specify the desired target.
The target
property accepts the following values:
Value | Description |
---|---|
"all" |
Targets all Interop servers that have registered the method. |
"best" |
Default. Targets only the Interop server that has registered the method first. |
"skipMine" |
Targets all Interop servers except the current one. |
The example below demonstrates how to target all Interop servers that have registered the "Sum" method:
@app.callback(
Output("io-connect-invoke-sum", "invoke"),
Input("sum-numbers-btn", "n_clicks"),
State("number-a", "value"),
State("number-b", "value"),
prevent_initial_call=True
)
def sum_numbers(_, a, b):
return {
"definition": {
"name": "Sum"
},
"argumentObj": {
"a": a,
"b": b
},
"target": "all"
}