Glue42 Enterprise is now io.Connect Desktop! The new documentation site for all interop.io products is located at docs.interop.io.

Data Sharing Between Apps

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="glue42", children=[
    # Registering an Interop method that returns a result.
    dash_glue42.MethodRegister(id="g42-register-sum", definition={"name": "Sum"}, returns=True),

    # Registering an Interop method that doesn't return a result.
    dash_glue42.MethodRegister(id="g42-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="g42-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 Glue42 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. The invocationId 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("g42-register-sum", "result"),
    Input("g42-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("g42-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="glue42", children=[
    # A component which will invoke the "Sum" Interop method.
    dash_glue42.MethodInvoke(id="g42-invoke-sum"),

    # A component which will invoke the "SendMessage" Interop method.
    dash_glue42.MethodInvoke(id="g42-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("g42-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("g42-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="glue42", children=[
    # A component which will invoke the "Sum" Interop method.
    dash_glue42.MethodInvoke(id="g42-invoke-sum"),

    # A component which will invoke the "SendMessage" Interop method.
    dash_glue42.MethodInvoke(id="g42-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("g42-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("g42-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("g42-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("g42-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"
    }