Communicating with Dash through WebSockets

Hi everyone,

I need to run a dash app in a separate thread inside a main backend application (Qt).
I need to communicate with Dash after running the dash app. Because dash callbacks must be initialized beforehand so i must use websockets to send server->client message from my main backend application.

This works well with a combination of Dash-Extensions and Quart websockets, as long as Quart runs in the main application.
See dash_extensions WebSocket example

Even with quart + dash running in the same main app

But my main backend program also requires also the websocket server to run in it’s separate thread.
Unfortunately Quart websockets cannot run in a thread (never easy…). So I’m trying to do get the same results as quart using standard python websockets.

I think everything is set up correctly, but the Dash-Extensions WebSocket doesn’t receive the message.

What is going wrong here?

import websockets # pip install websockets 
import threading
import asyncio

from dash_extensions import WebSocket
from dash_extensions.enrich import html, dcc, Output, Input, DashProxy

#########################################################################
# DASH STUFF
update_graph = """
function(msg) {
    if(!msg){
        return {};
    }  // no data, just return
    const data = JSON.parse(msg.data);  // read the data
    return data;
};  // plot the data
"""
dash_app = DashProxy(__name__)
dash_app.layout = html.Div([
    WebSocket(id="ws", url=f"ws://127.0.0.1:5000/random_data"),
    html.Div(['POPO'],id="graph")
])
dash_app.clientside_callback(update_graph, Output("graph", "children"), Input("ws", "message"))

#########################################################################
# WEBSOCKET STUFF
async def bogus(websocket, path):
    async for data in websocket:
        print(f">> Received: {data} {path}")

def run_ws_server():
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    ws_server = websockets.serve(bogus, '127.0.0.1', 5000)

    loop.run_until_complete(ws_server)
    loop.run_forever()
    loop.close()

async def send_receive_ws_message(uri, data):
    async with websockets.connect(uri) as websocket:
        await websocket.send(data)
        #reply = await websocket.recv()
        #print(f"The reply is: '{reply}'")


#########################################################################
# SIMULATE MAIN PROGRAM
if __name__ == "__main__": 
    # STARTUP DASH + WS THREAD
    # daemon dash_app thread:
    threading.Thread(target=dash_app.run_server, kwargs={'host':'127.0.0.1','port':8050,'debug':False}, daemon=True).start()
    # daemon ws_server thread:
    threading.Thread(target=run_ws_server, daemon=True).start()

    # SOMEWHERE INSIDE THE MAIN PROGRAM 
    import time, random
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    while True:
        loop.run_until_complete(send_receive_ws_message(f'ws://127.0.0.1:5000/random_data', f'HURAY : {random.random()}'))
        time.sleep(2)
    loop.close()

Or in short:
@Emil : How to run the dash extensions WebSocket Example with standard websockets instead of Quart?

1 Like

It’s some time ago that I worked with web sockets. But as I remember, the response from the server can differ a bit between implementations (e.g. Quart vs. others). I would suggest that you take a look at the responses from the two servers to identify potential differences. If possible, you could then configure your other server to match the Quart response. If that’s not possible, I am open to look at a PR that extends the implementation to cover your use case :blush:

@Emil : Thanks for the quick reply!

I am not very well versed in stuff like a client-server/jscript/react/websockets/etc…
But now I know what to look for.

PS:
The Dash Extensions Component already saved me lots of time/trouble getting around all kinds of Dash limitations. Super helpful and very reliable Library. Thanks for this! The official Dash is slowly catching up.
@Emil : A little off-topic but a question that I’m asking myself for a while now: Do the DashProxy app and enrich components etc always automatically support the latest installed version of the official Dash?

Thanks! Dash-extensions should be compatible with new versions of Dash, unless Plotly makes breaking changes (which they rarely do, if ever). However, there might be a delay in support for new features - an example was the introduction of flexible callback signatures.