How to send data from multiple objects but with only one websocket?

Question

Hi all,

I encountered a problem as I want to send data by means of the websocket in two different object

If I try to send data in each object like the following demo codes, I found that each of sending will build new connection, i.e. server get several connection rather than just one connection, which is not my design in my project.

The very simple idea for me to do that is getting the actual websocket object which is embedded in the package and directly conduct sending and receiving, say ws.send("information") or ws.revc("information").
However, I had no idea how to do that.

Can anyone kindly give me some suggestions to that job?

Thank you all!

Demo Codes

import dash
from dash import html, no_update
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc

# Websocket connection 
from dash_extensions import WebSocket
import json as js

# Declare the application
app = dash.Dash("Test")

# Declare objects
## localhost:5555 is just an example for the url to server
object1 = html.Div([dbc.Button("Send Hello", id="b1"),
                    WebSocket(url="ws://localhost:5555", id="ws1")], 
                   )

object2 = html.Div([dbc.Button("Send Hello", id="b2"),
                    WebSocket(url="ws://localhost:5555", id="ws2")], 
                   )

# Send hello function
def send_hello(n_click):
    if n_click > 0:
        return js.dumps("Hello"), 0
    else:
        return no_update, no_update

app.callback([Output("ws1", "send"),
              Output("b1", "n_clicks")],
             Input("b1", "n_clicks")
             )(send_hello)

app.callback([Output("ws2", "send"),
              Output("b2", "n_clicks")],
             Input("b2", "n_clicks")
             )(send_hello)

In your code, you are creating two Websocket components (one with id ws1 and one with id ws2). Hence you get two separate connections. If you one want one, just create one Websocket component.

1 Like

Content

Hi @Emil ,

Thanks for your kind reply

In the very beginning, I tried the following code to pass all the information via one websocket

However, I realized that Output of specific ID can only be used once in Dash.

Hence, I want to get the sole websocket object to send and receive separately and I had no any idea how to do that.

Please correct me if I am wrong

Thanks you!

Szuhsien

Demo Code

import dash
from dash import html, no_update
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc

# Websocket connection 
from dash_extensions import WebSocket
import json as js

# Declare the application
app = dash.Dash("Test")

# localhost:5555 is just an example for the url to server
ws = WebSocket(url="ws://localhost:5555", id="ws")

# Declare objects
object1 = html.Div(dbc.Button("Send Hello", id="b1"))

object2 = html.Div(dbc.Button("Send Hello", id="b2"))

# Send hello function
def send_hello(n_click):
    if n_click > 0:
        return js.dumps("Hello"), 0
    else:
        return no_update, no_update

# This two command will show the error in the dash page
app.callback([Output("ws", "send"),
              Output("b1", "n_clicks")],
             Input("b1", "n_clicks")
             )(send_hello)

app.callback([Output("ws", "send"),
              Output("b2", "n_clicks")],
             Input("b2", "n_clicks")
             )(send_hello)

... skip ...

You can use the MultiplexerTransform to target a component by multiple outputs,

Question

Hi @Emil,

Thanks for your kind suggestions!

Multiple callback of the same Output is really helpful

But, I encounter another problem related to pattern-matching:

My application consists of dynamic adding or deleting upload objects which would send data to specific address via the websocket

Because of dynamically adding or deleting objects, I need to use pattern-matching to do callbacks to all objects

However, as each upload use the same websocket object to send data, I cannot use pattern-matching anymore. (Please see send_data function in the demo codes)

Is there any alternative way to do what I am aimed to do?

Thanks

Szuhsien

Demo Code

from dash_extensions.enrich import Dash, Input, Output, State , MATCH
from dash import dcc, html, no_update, callback_context
import dash_bootstrap_components as dbc
import os, base64, io, datetime

# HTTP Connection
from dash_extensions import WebSocket
import json as js

# Declare websocket object
ws = WebSocket(url="ws://localhost:2345", id="ws")

# Declare the application
app = Dash("Component", 
            title="Component",
            external_stylesheets=[dbc.themes.SPACELAB],
            assets_external_path=".\assets",
            suppress_callback_exceptions=True)

# Declare initial tabs; Each of tab consists of the upload button
tabs_list = [html.Div(
    dcc.Upload(
        html.Div(html.Button(children='Upload File Button{}'.format(1))),
        id={"type": 'upload',
            "index": 1},
        ),    
    id={"type": 'tab_control',
        "index": 1},
    )]

# Let tabs_list become dcc.Tabs object
tabs = dcc.Tabs(children=[dcc.Tab(children=tabs_list[i], 
                                  label='Tab{}'.format(i+1)
                                  ) for i in range(len(tabs_list))
                          ],
                id="rx_tabs")

# Declare add & delete buttons
button_add = dbc.Button("Add One Tab", id="button_add", style={"width":"200px"})
button_del = dbc.Button("Delete All Tabs", id="button_del", style={"width":"200px"})

@app.callback(Output('rx_tabs', 'children'),
              Input('rx_tabs', 'children'),
              Input('button_add', 'n_clicks'),
              Input('button_del', 'n_clicks'),
              )

# Control behavior of add_button and del-button; n_add &n_del are just trigger arguments
def control_number_tabs(tabs, n_add, n_del):
    
    changed_id = callback_context.triggered[0]['prop_id'].split('.')[0]

    if "button_del" == changed_id:
        
        return tabs[0:1]
    
    elif "button_add" == changed_id:
        
        idx = len(tabs) + 1
        
        upload = html.Div(
            dcc.Upload(
                html.Div(html.Button(children='Upload File Button{}'.format(idx))),
                id={"type": 'upload',
                    "index": idx},
                ),    
            id={"type": 'tab_control',
                "index": idx},
            )

        one_tab = dcc.Tab(children=upload, 
                          label="Tab{}".format(idx), 
                          )
        
        tabs.append(one_tab)
        return tabs
    
    else:        
        
        return no_update

'''

# This function is wrong, and I don't know how to finish this concept
# Aims: after uploading, files will be sent to the server
@app.callback(Output("ws", "send"),
              Input({"type":"upload", "index":MATCH}, "contents"),
              State({"type":"upload", "index":MATCH}, "filename")
              )
def send_data(contents, names):

    if contents != None:
        
        content_type, content_string = contents.split(',')

        # From b64 to bytes
        byte_message = base64.b64decode(content_string)
        df = byte_message.decode('utf-8')
        
        f = dict()
        f["fileName"] = names
        f["fileContent"] = df
        
        return js.dumps(f)

    else:
        
        return no_update
'''


main_layout = dbc.Container(
    [
        ws,
        html.Br(),
        dbc.Row([
            dbc.Col(button_add, width={"size":3, "offset":3}),
            dbc.Col(button_del, width=6),
            ]
        ),
        html.Br(),
        tabs,

    ],
    fluid=True
    )


app.layout = main_layout
    
if __name__ == '__main__':
    print(len(tabs.children))
    app.run_server(debug=True, port=9999, dev_tools_hot_reload=False)

As noted in the docs, the MultiplexerTransform does not support the MATCH and ALLSMALLER wildcards. Hence I would suggest using ALL wildcard instead.

Hi, @Emil

Thanks for your kind solutions

My problems are solved :slight_smile:

Sincerely,
Szuhsien