Install & Compatibility
Where this runs
tested against v0.12.2 · pip install
no network on importno background threads
Install × environment matrix
Each cell = how many times install + import succeeded across repeated harness runs. Partial = flaky.
glibc = Debian/Ubuntu slim · musl = Alpine Linux
muslpy 3.10–3.95 runs
installs and imports cleanly · install 0.0s · import 0.496s · 24.2MB
glibcpy 3.10–3.95 runs
installs and imports cleanly · install 3.3s · import 0.430s · 25MB
23MB installed
● package 23MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
open_websocket_url
✓ from trio_websocket import open_websocket_url
Context manager for opening a client WebSocket connection by URL.
open_websocket
✓ from trio_websocket import open_websocket
Context manager for opening a client WebSocket connection by host, port, and resource.
serve_websocket
✓ from trio_websocket import serve_websocket
Function to run a WebSocket server with a given handler.
ConnectionClosed
✓ from trio_websocket import ConnectionClosed
Exception raised when a WebSocket connection is closed.
WebSocketRequest
✓ from trio_websocket import WebSocketRequest
Represents an incoming WebSocket request in a server handler.
WebSocketConnection
✓ from trio_websocket import WebSocketConnection
Represents an active WebSocket connection, used for sending/receiving messages.
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.
import trio
import logging
from trio_websocket import serve_websocket, open_websocket_url, ConnectionClosed, WebSocketRequest
logging.basicConfig(level=logging.INFO)
async def echo_server_handler(request: WebSocketRequest) -> None:
"""Reverses incoming websocket messages and sends them back."""
logging.info('Server: Handler starting on path "%s"', request.path)
ws = await request.accept()
try:
while True:
message = await ws.get_message()
logging.info('Server: Received message: %s', message)
await ws.send_message(message[::-1]) # Echo reversed message
except ConnectionClosed:
logging.info('Server: Connection closed.')
except Exception as e:
logging.error('Server: Error in handler: %s', e)
finally:
logging.info('Server: Handler exiting.')
async def client_task(port: int) -> None:
"""Connects to the server, sends a message, and waits for a response."""
uri = f'ws://127.0.0.1:{port}/echo'
logging.info('Client: Connecting to %s', uri)
try:
async with open_websocket_url(uri) as ws:
test_message = 'hello trio-websocket'
logging.info('Client: Sending: "%s"', test_message)
await ws.send_message(test_message)
received_message = await ws.get_message()
expected_message = test_message[::-1] # Reversed
logging.info('Client: Received: "%s"', received_message)
assert received_message == expected_message
logging.info('Client: Message echoed and reversed successfully!')
except OSError as ose:
logging.error('Client: Connection attempt failed: %s', ose)
except Exception as e:
logging.error('Client: Error in client task: %s', e)
finally:
logging.info('Client: Task finished.')
async def main():
server_port = 8000
async with trio.open_nursery() as nursery:
logging.info(f'Main: Spawning server on 127.0.0.1:{server_port}')
nursery.start_soon(serve_websocket, echo_server_handler, '127.0.0.1', server_port, None)
# Give the server a moment to start up
await trio.sleep(0.1)
logging.info('Main: Spawning client task.')
nursery.start_soon(client_task, server_port)
if __name__ == '__main__':
try:
trio.run(main)
except KeyboardInterrupt:
logging.info('Program interrupted by user.')
Debug
Known issues
gotchaThe `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.fixBe 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.
affects: All versions from 0.10.x onwards.
gotchaWhen 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.fixEnsure 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.
affects: All versions.
gotchaUnlike 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.fixHandle `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.
affects: All versions.
gotchaTrio 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.fixUnderstand 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.
affects: All versions.
Errors
Common errors & fixes
ConnectionRefusedError: [Errno 111] Connection refused
The WebSocket client attempted to connect to a server that was not running or was not listening on the specified host and port.
fixEnsure the WebSocket server is running and accessible on the target host and port before the client attempts to connect.
trio_websocket.HandshakeError: expected status 101, got 404
The server responded with an HTTP status code other than 101 (Switching Protocols), indicating that it did not complete the WebSocket handshake successfully.
fixVerify the WebSocket server's endpoint URL is correct and that the server implementation properly handles the WebSocket upgrade request.
TypeError: object <coroutine object WebSocketConnection.send_message at 0x...> is not awaitable
An asynchronous method of the `WebSocketConnection` object, such as `send_message` or `get_message`, was called without the `await` keyword.
fixPrefix the call to the asynchronous method with `await`. For example, `await ws.send_message(message)`.
RuntimeError: must be run in a Trio task
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)`.
fixWrap 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.
Upgrade
Version history
0.12.2latest on PyPI · released Feb 25, 2025
Audit
Dependencies
triorequiredCore asynchronous I/O framework.
wsprotorequiredSans-IO WebSocket protocol state machine.
exceptiongrouprequiredRequired for Python < 3.11, as Trio leverages exception groups.