Install & Compatibility
Where this runs
tested against v1.3.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.118s · 18.2MB
glibcpy 3.10–3.95 runs
installs and imports cleanly · install 1.7s · import 0.104s · 19MB
16MB installed
● package 16MB
Code
Verified usage
Verified import paths — ran on the pinned version, not inferred.
WSConnection
✓ from wsproto import WSConnection
✗ from wsproto.connection import WSConnection
Moved in version 0.13.0 for better discoverability and to be closer to other top-level classes. The old import path is now deprecated and removed in newer versions.
ConnectionType
✓ from wsproto import ConnectionType
✗ from wsproto.connection import ConnectionType
Similar to WSConnection, ConnectionType was moved to the top-level package for easier access.
Request
✓ from wsproto.events import Request
AcceptConnection
✓ from wsproto.events import AcceptConnection
Message
✓ from wsproto.events import Message
TextMessage
✓ from wsproto.events import TextMessage
CloseConnection
✓ from wsproto.events import CloseConnection
wsproto is a 'sans-I/O' library, meaning it handles the protocol state machine but not network communication. The quickstart demonstrates a simulated client-server interaction by manually passing bytes between two `WSConnection` instances. In a real application, `client_ws.send()`'s output would be written to a network socket, and `client_ws.receive_data()` would consume bytes read from that socket. Events are processed by iterating `ws.events()`.
from wsproto import WSConnection, ConnectionType
from wsproto.events import Request, AcceptConnection, TextMessage, CloseConnection
import socket
import os
# Example: Simple WebSocket client interaction (sans-I/O)
# This code demonstrates the wsproto logic; actual network I/O is omitted for brevity.
# In a real application, 'send_bytes_to_network' and 'receive_bytes_from_network'
# would interact with a socket or other I/O primitive.
def simulate_client_server_interaction():
client_ws = WSConnection(ConnectionType.CLIENT)
server_ws = WSConnection(ConnectionType.SERVER)
# Client initiates handshake
client_handshake_request = client_ws.send(Request(host="example.com", target="/"))
print(f"Client sends handshake request: {client_handshake_request!r}")
# Server receives handshake request
server_ws.receive_data(client_handshake_request)
for event in server_ws.events():
if isinstance(event, Request):
print(f"Server receives client Request: {event}")
server_handshake_response = server_ws.send(AcceptConnection())
print(f"Server sends handshake response: {server_handshake_response!r}")
break
# Client receives handshake response
client_ws.receive_data(server_handshake_response)
for event in client_ws.events():
if isinstance(event, AcceptConnection):
print(f"Client receives Server Acceptance: {event}")
break
# Client sends a message
client_message_bytes = client_ws.send(TextMessage(data="Hello from client!"))
print(f"Client sends message: {client_message_bytes!r}")
# Server receives the message
server_ws.receive_data(client_message_bytes)
for event in server_ws.events():
if isinstance(event, TextMessage):
print(f"Server receives message: {event.data!r}")
if event.message_finished:
# Server echoes back
server_response_bytes = server_ws.send(TextMessage(data=f"Echo: {event.data.decode()}"))
print(f"Server sends echo: {server_response_bytes!r}")
break
# Client receives server's echo
client_ws.receive_data(server_response_bytes)
for event in client_ws.events():
if isinstance(event, TextMessage):
print(f"Client receives echo: {event.data.decode()!r}")
break
# Client initiates close
client_close_bytes = client_ws.send(CloseConnection(code=1000, reason="Done"))
print(f"Client sends close frame: {client_close_bytes!r}")
# Server receives close
server_ws.receive_data(client_close_bytes)
for event in server_ws.events():
if isinstance(event, CloseConnection):
print(f"Server receives CloseConnection: {event}")
server_close_response = server_ws.send(event.response())
print(f"Server sends close response: {server_close_response!r}")
break
# Client receives server's close response
client_ws.receive_data(server_close_response)
for event in client_ws.events():
if isinstance(event, CloseConnection):
print(f"Client receives CloseConnection response: {event}")
break
simulate_client_server_interaction()
Debug
Known issues
gotchawsproto is a 'sans-I/O' library. It manages the WebSocket protocol state but does not perform any network I/O itself. Users must implement their own 'network glue' to send and receive bytes over the actual network (e.g., using `socket` or an async I/O library) and feed them into `wsproto`.fixBe prepared to manage your own network sockets and I/O loops. `wsproto.WSConnection.send()` returns bytes to transmit, and `wsproto.WSConnection.receive_data(data)` takes bytes received from the network.
affects: All versions
gotchaWhen the underlying network connection drops unexpectedly (e.g., `socket.recv()` returns zero bytes), you must call `ws.receive_data(None)` to inform `wsproto` of the connection closure and update its internal state. Failing to do so can leave the connection in an inconsistent state.fixUpon detecting a connection drop (e.g., `recv()` returning `b''`), call `ws.receive_data(None)` immediately before tearing down the connection.
affects: All versions
gotchaFor correct protocol behavior, both client and server connections *must* respond to certain control frames. Specifically, a received `Ping` event requires sending a `Pong` event (use `event.response()`), and a received `CloseConnection` event requires sending a `CloseConnection` event back (also `event.response()`).fixAlways iterate `ws.events()` after receiving data and ensure you handle `Ping` and `CloseConnection` events by sending their respective responses using `ws.send(event.response())`.
affects: All versions
gotchaWebSocket data messages (TextMessage, BinaryMessage) can be fragmented across multiple `Message` events. The `data` field of these events represents only a chunk of the message. Applications need to buffer and reassemble these chunks until `event.message_finished` is `True` to get the complete logical message.fixMaintain a buffer for incoming message data and append `event.data` until `event.message_finished` indicates the full message has arrived. Then, process the complete buffered message.
affects: All versions
breakingThe primary `WSConnection` class was moved from `wsproto.connection.WSConnection` to `wsproto.WSConnection` in version 0.13.0. Additionally, the method to feed data into the connection was renamed from `receive_bytes` to `receive_data` in the same version.fixUpdate imports from `from wsproto.connection import WSConnection` to `from wsproto import WSConnection`. Replace calls to `ws.receive_bytes(data)` with `ws.receive_data(data)`.
affects: Prior to 0.13.0 to 0.13.0+
gotchaWhen processing `wsproto` events, `event.data` for `TextMessage` events is a Unicode string (`str`), while for `BinaryMessage` events it is bytes (`bytes`). Attempting to call `.decode()` on `event.data` from a `TextMessage` will result in an `AttributeError` because strings do not have a `.decode()` method.fixCheck the type of the event (e.g., `isinstance(event, wsproto.events.TextMessage)`) before processing its data. If `event` is a `TextMessage`, `event.data` is already a string and should be used directly. If `event` is a `BinaryMessage`, `event.data` is bytes and can be decoded if a string representation is needed.
affects: All versions
gotchaWhen processing `Message` events (like `TextMessage` or `BinaryMessage`), be aware of the type of `event.data`. For `TextMessage` events, `event.data` is a `str`. For `BinaryMessage` events, `event.data` is `bytes`. Attempting to `decode()` a `str` or `encode()` bytes unnecessarily will result in an `AttributeError` or `TypeError`.fixFor `TextMessage` events, `event.data` is already a string; use it directly or `encode()` it to bytes if the target API expects bytes. For `BinaryMessage` events, `event.data` is bytes; use it directly or `decode()` it to a string if the target API expects a string (specifying encoding).
affects: All versions
Errors
Common errors & fixes
ModuleNotFoundError: No module named 'wsproto'
The 'wsproto' library is not installed in your current Python environment.
AttributeError: 'Connection' object has no attribute 'send_message'
Developers often expect a high-level 'send_message' method, but 'wsproto' provides a low-level 'send' method for sending individual WebSocket frames.
fixUse the `connection.send()` method with raw bytes and an appropriate WebSocket opcode (e.g., `Opcode.TEXT`, `Opcode.BINARY`).
wsproto.utilities.InvalidStateError
An operation was attempted on the `wsproto.Connection` object when it was not in the correct state to perform that action (e.g., calling `accept()` on an already accepted connection).
fixImplement proper state handling by checking `connection.state` before attempting operations, ensuring they align with the connection's current lifecycle stage.
wsproto.utilities.ProtocolError
The `wsproto` connection received bytes that do not conform to the WebSocket protocol specification (e.g., malformed frame header, invalid opcode, or reserved bits set incorrectly).
fixVerify that the data being sent to `connection.receive_data()` is valid WebSocket protocol data, debugging the sender or network for corruption or non-compliance.
Upgrade
Version history
1.3.2latest on PyPI · released Nov 20, 2025
Audit
Dependencies
h11optionalOften used for HTTP/1.1 handshake by projects integrating wsproto, though not a direct dependency of wsproto itself.
hyper-h2optionalRecommended for HTTP/2 WebSocket support, requires external HTTP/2 parser.