{"id":596,"library":"wsproto","title":"wsproto: Pure-Python WebSocket Protocol Implementation","description":"wsproto is a pure-Python, sans-I/O implementation of the WebSocket protocol stack (RFC 6455) and its per-message compression extension (RFC 7692). It provides a low-level, state-machine-driven API, allowing developers to embed WebSocket communication into various programming paradigms without dictating network or concurrency models. The current version is 1.3.2, with releases occurring on an irregular but active basis as needed by the `python-hyper` community.","status":"active","version":"1.3.2","language":"python","source_language":"en","source_url":"https://github.com/python-hyper/wsproto/","tags":["websocket","protocol","sans-io","networking","low-level"],"install":[{"cmd":"pip install wsproto","lang":"bash","label":"Install latest version"}],"dependencies":[{"reason":"Often used for HTTP/1.1 handshake by projects integrating wsproto, though not a direct dependency of wsproto itself.","package":"h11","optional":true},{"reason":"Recommended for HTTP/2 WebSocket support, requires external HTTP/2 parser.","package":"hyper-h2","optional":true}],"imports":[{"note":"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.","wrong":"from wsproto.connection import WSConnection","symbol":"WSConnection","correct":"from wsproto import WSConnection"},{"note":"Similar to WSConnection, ConnectionType was moved to the top-level package for easier access.","wrong":"from wsproto.connection import ConnectionType","symbol":"ConnectionType","correct":"from wsproto import ConnectionType"},{"symbol":"Request","correct":"from wsproto.events import Request"},{"symbol":"AcceptConnection","correct":"from wsproto.events import AcceptConnection"},{"symbol":"Message","correct":"from wsproto.events import Message"},{"symbol":"TextMessage","correct":"from wsproto.events import TextMessage"},{"symbol":"CloseConnection","correct":"from wsproto.events import CloseConnection"}],"quickstart":{"code":"from wsproto import WSConnection, ConnectionType\nfrom wsproto.events import Request, AcceptConnection, TextMessage, CloseConnection\nimport socket\nimport os\n\n# Example: Simple WebSocket client interaction (sans-I/O)\n# This code demonstrates the wsproto logic; actual network I/O is omitted for brevity.\n# In a real application, 'send_bytes_to_network' and 'receive_bytes_from_network'\n# would interact with a socket or other I/O primitive.\n\ndef simulate_client_server_interaction():\n    client_ws = WSConnection(ConnectionType.CLIENT)\n    server_ws = WSConnection(ConnectionType.SERVER)\n\n    # Client initiates handshake\n    client_handshake_request = client_ws.send(Request(host=\"example.com\", target=\"/\"))\n    print(f\"Client sends handshake request: {client_handshake_request!r}\")\n\n    # Server receives handshake request\n    server_ws.receive_data(client_handshake_request)\n    for event in server_ws.events():\n        if isinstance(event, Request):\n            print(f\"Server receives client Request: {event}\")\n            server_handshake_response = server_ws.send(AcceptConnection())\n            print(f\"Server sends handshake response: {server_handshake_response!r}\")\n            break\n\n    # Client receives handshake response\n    client_ws.receive_data(server_handshake_response)\n    for event in client_ws.events():\n        if isinstance(event, AcceptConnection):\n            print(f\"Client receives Server Acceptance: {event}\")\n            break\n\n    # Client sends a message\n    client_message_bytes = client_ws.send(TextMessage(data=\"Hello from client!\"))\n    print(f\"Client sends message: {client_message_bytes!r}\")\n\n    # Server receives the message\n    server_ws.receive_data(client_message_bytes)\n    for event in server_ws.events():\n        if isinstance(event, TextMessage):\n            print(f\"Server receives message: {event.data!r}\")\n            if event.message_finished:\n                # Server echoes back\n                server_response_bytes = server_ws.send(TextMessage(data=f\"Echo: {event.data.decode()}\"))\n                print(f\"Server sends echo: {server_response_bytes!r}\")\n                break\n\n    # Client receives server's echo\n    client_ws.receive_data(server_response_bytes)\n    for event in client_ws.events():\n        if isinstance(event, TextMessage):\n            print(f\"Client receives echo: {event.data.decode()!r}\")\n            break\n\n    # Client initiates close\n    client_close_bytes = client_ws.send(CloseConnection(code=1000, reason=\"Done\"))\n    print(f\"Client sends close frame: {client_close_bytes!r}\")\n\n    # Server receives close\n    server_ws.receive_data(client_close_bytes)\n    for event in server_ws.events():\n        if isinstance(event, CloseConnection):\n            print(f\"Server receives CloseConnection: {event}\")\n            server_close_response = server_ws.send(event.response())\n            print(f\"Server sends close response: {server_close_response!r}\")\n            break\n\n    # Client receives server's close response\n    client_ws.receive_data(server_close_response)\n    for event in client_ws.events():\n        if isinstance(event, CloseConnection):\n            print(f\"Client receives CloseConnection response: {event}\")\n            break\n\nsimulate_client_server_interaction()\n","lang":"python","description":"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()`."},"warnings":[{"fix":"Be 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.","message":"wsproto 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`.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Upon detecting a connection drop (e.g., `recv()` returning `b''`), call `ws.receive_data(None)` immediately before tearing down the connection.","message":"When 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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Always iterate `ws.events()` after receiving data and ensure you handle `Ping` and `CloseConnection` events by sending their respective responses using `ws.send(event.response())`.","message":"For 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()`).","severity":"gotcha","affected_versions":"All versions"},{"fix":"Maintain 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.","message":"WebSocket 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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Update imports from `from wsproto.connection import WSConnection` to `from wsproto import WSConnection`. Replace calls to `ws.receive_bytes(data)` with `ws.receive_data(data)`.","message":"The 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.","severity":"breaking","affected_versions":"Prior to 0.13.0 to 0.13.0+"},{"fix":"Check 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.","message":"When 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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"For `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).","message":"When 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`.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-05-12T16:25:22.678Z","next_check":"2026-06-26T00:00:00.000Z","problems":[{"fix":"pip install wsproto","cause":"The 'wsproto' library is not installed in your current Python environment.","error":"ModuleNotFoundError: No module named 'wsproto'"},{"fix":"Use the `connection.send()` method with raw bytes and an appropriate WebSocket opcode (e.g., `Opcode.TEXT`, `Opcode.BINARY`).","cause":"Developers often expect a high-level 'send_message' method, but 'wsproto' provides a low-level 'send' method for sending individual WebSocket frames.","error":"AttributeError: 'Connection' object has no attribute 'send_message'"},{"fix":"Implement proper state handling by checking `connection.state` before attempting operations, ensuring they align with the connection's current lifecycle stage.","cause":"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).","error":"wsproto.utilities.InvalidStateError"},{"fix":"Verify that the data being sent to `connection.receive_data()` is valid WebSocket protocol data, debugging the sender or network for corruption or non-compliance.","cause":"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).","error":"wsproto.utilities.ProtocolError"}],"ecosystem":"pypi","meta_description":null,"install_score":100,"install_tag":"verified","quickstart_score":0,"quickstart_tag":"stale","pypi_latest":"1.3.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.1,"mem_mb":2.3,"disk_size":"18.2M"},{"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.12,"mem_mb":2.3,"disk_size":"18.2M"},{"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":1.5,"import_time_s":0.07,"mem_mb":2.3,"disk_size":"19M"},{"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.07,"mem_mb":2.3,"disk_size":"19M"},{"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.13,"mem_mb":2.6,"disk_size":"20.1M"},{"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.15,"mem_mb":2.6,"disk_size":"20.1M"},{"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":1.7,"import_time_s":0.13,"mem_mb":2.6,"disk_size":"21M"},{"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.12,"mem_mb":2.6,"disk_size":"21M"},{"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.12,"mem_mb":2.6,"disk_size":"12.0M"},{"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.14,"mem_mb":2.6,"disk_size":"12.0M"},{"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":1.5,"import_time_s":0.12,"mem_mb":2.6,"disk_size":"12M"},{"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.13,"mem_mb":2.6,"disk_size":"12M"},{"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.1,"mem_mb":2.7,"disk_size":"11.7M"},{"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.12,"mem_mb":2.7,"disk_size":"11.6M"},{"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":1.5,"import_time_s":0.11,"mem_mb":2.5,"disk_size":"12M"},{"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.11,"mem_mb":2.5,"disk_size":"12M"},{"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.1,"mem_mb":2.2,"disk_size":"17.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.12,"mem_mb":2.2,"disk_size":"17.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":1.8,"import_time_s":0.08,"mem_mb":2.2,"disk_size":"18M"},{"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.1,"mem_mb":2.2,"disk_size":"18M"}]},"quickstart_checks":{"last_tested":"2026-04-23","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}]}}