{"id":718,"library":"trio-websocket","title":"Trio WebSocket","description":"Trio WebSocket is a Python library that provides a WebSocket implementation for the Trio asynchronous I/O framework. It offers both client and server functionalities, built on the `wsproto` sans-IO state machine, emphasizing safety, correctness, and ergonomics. The current stable version is 0.12.2. While mature and stable, the project is currently in 'life-support maintenance', with contributions welcomed.","status":"maintenance","version":"0.12.2","language":"python","source_language":"en","source_url":"https://github.com/python-trio/trio-websocket","tags":["websocket","trio","async","networking"],"install":[{"cmd":"pip install trio-websocket","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Core asynchronous I/O framework.","package":"trio","optional":false},{"reason":"Sans-IO WebSocket protocol state machine.","package":"wsproto","optional":false},{"reason":"Required for Python < 3.11, as Trio leverages exception groups.","package":"exceptiongroup","optional":false}],"imports":[{"note":"Context manager for opening a client WebSocket connection by URL.","symbol":"open_websocket_url","correct":"from trio_websocket import open_websocket_url"},{"note":"Context manager for opening a client WebSocket connection by host, port, and resource.","symbol":"open_websocket","correct":"from trio_websocket import open_websocket"},{"note":"Function to run a WebSocket server with a given handler.","symbol":"serve_websocket","correct":"from trio_websocket import serve_websocket"},{"note":"Exception raised when a WebSocket connection is closed.","symbol":"ConnectionClosed","correct":"from trio_websocket import ConnectionClosed"},{"note":"Represents an incoming WebSocket request in a server handler.","symbol":"WebSocketRequest","correct":"from trio_websocket import WebSocketRequest"},{"note":"Represents an active WebSocket connection, used for sending/receiving messages.","symbol":"WebSocketConnection","correct":"from trio_websocket import WebSocketConnection"}],"quickstart":{"code":"import trio\nimport logging\nfrom trio_websocket import serve_websocket, open_websocket_url, ConnectionClosed, WebSocketRequest\n\nlogging.basicConfig(level=logging.INFO)\n\nasync def echo_server_handler(request: WebSocketRequest) -> None:\n    \"\"\"Reverses incoming websocket messages and sends them back.\"\"\"\n    logging.info('Server: Handler starting on path \"%s\"', request.path)\n    ws = await request.accept()\n    try:\n        while True:\n            message = await ws.get_message()\n            logging.info('Server: Received message: %s', message)\n            await ws.send_message(message[::-1]) # Echo reversed message\n    except ConnectionClosed:\n        logging.info('Server: Connection closed.')\n    except Exception as e:\n        logging.error('Server: Error in handler: %s', e)\n    finally:\n        logging.info('Server: Handler exiting.')\n\nasync def client_task(port: int) -> None:\n    \"\"\"Connects to the server, sends a message, and waits for a response.\"\"\"\n    uri = f'ws://127.0.0.1:{port}/echo'\n    logging.info('Client: Connecting to %s', uri)\n    try:\n        async with open_websocket_url(uri) as ws:\n            test_message = 'hello trio-websocket'\n            logging.info('Client: Sending: \"%s\"', test_message)\n            await ws.send_message(test_message)\n\n            received_message = await ws.get_message()\n            expected_message = test_message[::-1] # Reversed\n            logging.info('Client: Received: \"%s\"', received_message)\n\n            assert received_message == expected_message\n            logging.info('Client: Message echoed and reversed successfully!')\n    except OSError as ose:\n        logging.error('Client: Connection attempt failed: %s', ose)\n    except Exception as e:\n        logging.error('Client: Error in client task: %s', e)\n    finally:\n        logging.info('Client: Task finished.')\n\nasync def main():\n    server_port = 8000\n    async with trio.open_nursery() as nursery:\n        logging.info(f'Main: Spawning server on 127.0.0.1:{server_port}')\n        nursery.start_soon(serve_websocket, echo_server_handler, '127.0.0.1', server_port, None)\n        \n        # Give the server a moment to start up\n        await trio.sleep(0.1)\n        \n        logging.info('Main: Spawning client task.')\n        nursery.start_soon(client_task, server_port)\n\nif __name__ == '__main__':\n    try:\n        trio.run(main)\n    except KeyboardInterrupt:\n        logging.info('Program interrupted by user.')\n","lang":"python","description":"This quickstart demonstrates a minimal `trio-websocket` client and server. The server, `echo_server_handler`, binds to `127.0.0.1:8000` and reverses any text messages it receives before sending them back. The client, `client_task`, connects to this server, sends a test message, and asserts that it receives the reversed message back. Both are run concurrently within a Trio nursery."},"warnings":[{"fix":"Be aware of the project's maintenance status. For critical applications, consider contributing or assessing alternatives if more active development is required. The `trio` framework itself is actively maintained.","message":"The `trio-websocket` project is currently in 'life-support maintenance'. While stable, active development and new features may be slow, and future major changes might occur with limited dedicated support.","severity":"gotcha","affected_versions":"All versions from 0.10.x onwards."},{"fix":"Ensure that the data type sent by the client (`str` or `bytes`) matches the expected encoding/decoding on the server side. Explicitly encode/decode if necessary, especially when interoperating with non-Python WebSocket implementations.","message":"When sending data, be mindful of the type (string or bytes). If a server is configured with `encoding=\"text\"` but receives raw bytes, it might process them unexpectedly (e.g., as `None` or an empty string), even though `send_message` theoretically accepts both.","severity":"gotcha","affected_versions":"All versions."},{"fix":"Handle `ConnectionClosed` specifically when expecting a client or server connection to gracefully shut down or when an attempt to send data might occur after closure. This allows for more precise error handling related to WebSocket lifecycle events.","message":"Unlike some other async libraries where `BrokenStreamError` might be raised on a closed connection, `trio-websocket` raises `ConnectionClosed` when attempting to write to a connection that has already been closed.","severity":"gotcha","affected_versions":"All versions."},{"fix":"Understand that high-level `trio-websocket` functions have built-in timeouts. If finer-grained control is needed, these can often be disabled or adjusted, and `trio.fail_after` can be wrapped around `trio-websocket` operations for explicit, composable timeout management.","message":"Trio generally encourages caller-enforced timeouts using cancel scopes (`trio.fail_after`). However, `trio-websocket`'s high-level APIs (like `open_websocket_url` and server setup) incorporate internal timeouts for safety and ergonomics. This can be a point of confusion for users accustomed to Trio's 'timeout for humans' philosophy.","severity":"gotcha","affected_versions":"All versions."}],"env_vars":null,"last_verified":"2026-05-12T18:15:19.302Z","next_check":"2026-06-26T00:00:00.000Z","problems":[{"fix":"Ensure the WebSocket server is running and accessible on the target host and port before the client attempts to connect.","cause":"The WebSocket client attempted to connect to a server that was not running or was not listening on the specified host and port.","error":"ConnectionRefusedError: [Errno 111] Connection refused"},{"fix":"Verify the WebSocket server's endpoint URL is correct and that the server implementation properly handles the WebSocket upgrade request.","cause":"The server responded with an HTTP status code other than 101 (Switching Protocols), indicating that it did not complete the WebSocket handshake successfully.","error":"trio_websocket.HandshakeError: expected status 101, got 404"},{"fix":"Prefix the call to the asynchronous method with `await`. For example, `await ws.send_message(message)`.","cause":"An asynchronous method of the `WebSocketConnection` object, such as `send_message` or `get_message`, was called without the `await` keyword.","error":"TypeError: object <coroutine object WebSocketConnection.send_message at 0x...> is not awaitable"},{"fix":"Wrap your application's entry point in an `async def main():` function and then call `trio.run(main())` to execute it within the Trio event loop.","cause":"A Trio-specific function, such as `trio_websocket.connect_websocket` or `serve_websocket`, was called directly without being inside an `async def main():` function invoked by `trio.run(main)`.","error":"RuntimeError: must be run in a Trio task"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":0,"quickstart_tag":"stale","pypi_latest":"0.12.2","install_checks":{"last_tested":"2026-05-12","tag":"verified","tag_description":"installs cleanly on critical runtimes, fast import, recently tested","results":[{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.44,"mem_mb":10.8,"disk_size":"24.4M"},{"runtime":"python:3.10-alpine","python_version":"3.10","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.47,"mem_mb":10.7,"disk_size":"24.4M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2.2,"import_time_s":0.31,"mem_mb":10.8,"disk_size":"25M"},{"runtime":"python:3.10-slim","python_version":"3.10","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.35,"mem_mb":10.7,"disk_size":"25M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.55,"mem_mb":11.3,"disk_size":"27.0M"},{"runtime":"python:3.11-alpine","python_version":"3.11","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.62,"mem_mb":11.4,"disk_size":"27.0M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2.5,"import_time_s":0.49,"mem_mb":11.3,"disk_size":"28M"},{"runtime":"python:3.11-slim","python_version":"3.11","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.48,"mem_mb":11.4,"disk_size":"28M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.51,"mem_mb":11.2,"disk_size":"18.6M"},{"runtime":"python:3.12-alpine","python_version":"3.12","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.49,"mem_mb":11.3,"disk_size":"18.6M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2.3,"import_time_s":0.48,"mem_mb":11.2,"disk_size":"19M"},{"runtime":"python:3.12-slim","python_version":"3.12","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.52,"mem_mb":11.2,"disk_size":"19M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.46,"mem_mb":11.6,"disk_size":"18.3M"},{"runtime":"python:3.13-alpine","python_version":"3.13","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.47,"mem_mb":11.6,"disk_size":"18.2M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2.3,"import_time_s":0.46,"mem_mb":11.6,"disk_size":"19M"},{"runtime":"python:3.13-slim","python_version":"3.13","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.52,"mem_mb":11.6,"disk_size":"19M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":null,"import_time_s":0.42,"mem_mb":10.7,"disk_size":"23.7M"},{"runtime":"python:3.9-alpine","python_version":"3.9","os_libc":"alpine (musl)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.76,"mem_mb":10.8,"disk_size":"23.7M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":" $EXIT -eq 0 ","exit_code":0,"wheel_type":"wheel","failure_reason":null,"install_time_s":2.6,"import_time_s":0.38,"mem_mb":10.7,"disk_size":"24M"},{"runtime":"python:3.9-slim","python_version":"3.9","os_libc":"slim (glibc)","variant":"default","exit_code":0,"wheel_type":null,"failure_reason":null,"install_time_s":null,"import_time_s":0.39,"mem_mb":10.8,"disk_size":"24M"}]},"quickstart_checks":{"last_tested":"2026-04-24","tag":"stale","tag_description":"widespread failures or data too old to trust","results":[{"runtime":"python:3.10-alpine","exit_code":-1},{"runtime":"python:3.10-slim","exit_code":-1},{"runtime":"python:3.11-alpine","exit_code":-1},{"runtime":"python:3.11-slim","exit_code":-1},{"runtime":"python:3.12-alpine","exit_code":-1},{"runtime":"python:3.12-slim","exit_code":-1},{"runtime":"python:3.13-alpine","exit_code":-1},{"runtime":"python:3.13-slim","exit_code":-1},{"runtime":"python:3.9-alpine","exit_code":-1},{"runtime":"python:3.9-slim","exit_code":-1}]}}