.. image:: https://raw.githubusercontent.com/tarasko/picows/master/docs/source/_static/banner.png :align: center Introduction ============ .. image:: https://badge.fury.io/py/picows.svg :target: https://pypi.org/project/picows :alt: Latest PyPI package version .. image:: https://img.shields.io/pypi/dm/picows :target: https://pypistats.org/packages/picows :alt: Downloads count .. image:: https://readthedocs.org/projects/picows/badge/?version=latest :target: https://picows.readthedocs.io/en/latest/ :alt: Latest Read The Docs **picows** is a high-performance Python library designed for building asyncio WebSocket clients and servers. Implemented in Cython, it offers exceptional speed and efficiency, surpassing other popular Python WebSocket libraries. .. image:: https://raw.githubusercontent.com/tarasko/websocket-benchmark/master/results/benchmark-Linux-256.png :target: https://github.com/tarasko/websocket-benchmark/blob/master :align: center The above chart shows the performance of echo clients communicating with a server through a loopback interface using popular Python libraries. `boost.beast client `_ is also included for reference. You can find benchmark sources and more results `here `_. Installation ============ picows requires Python 3.9 or greater and is available on PyPI. Use pip to install it:: $ pip install picows Getting started =============== picows provides two APIs: * A reimplementation of the popular `websockets `_ library's asyncio interface. * A low-level core API. It is more efficient than the high-level websockets API (lower latency, higher throughput, zero-copy), but omits a few high-level features that aren't always required. websockets API -------------- This is a drop-in replacement; you only need to change imports to transition from websockets to picows. Certain features from websockets library are not supported yet. Check out :doc:`websockets` for the full list. Client ~~~~~~ .. code-block:: python # Import picows.websockets instead of websockets from picows.websockets.asyncio.client import connect import asyncio async def hello(): async with connect("ws://localhost:8765") as websocket: await websocket.send("Hello world!") message = await websocket.recv() print(message) if __name__ == "__main__": asyncio.run(hello()) Server ~~~~~~ .. code-block:: python # Import picows.websockets instead of websockets from picows.websockets.asyncio.server import serve import asyncio async def echo(websocket): async for message in websocket: await websocket.send(message) async def main(): async with serve(echo, "localhost", 8765) as server: await server.serve_forever() if __name__ == "__main__": asyncio.run(main()) Core API -------- The Core API achieves superior performance by offering an efficient, non-async data path, similar to the `transport/protocol design from asyncio `_. The user handler receives WebSocket frame objects instead of complete messages. Since a message can span multiple frames, it is up to the user to decide the most effective strategy for concatenating them. Each frame object includes additional low-level details about the current parser state, which may help to further optimize the behavior of the user's application. The Core API doesn't offer high-level features like permessage-deflate extension support or an async iterator interface for reading. These features are often not required in real-world applications, significantly slow down the data path, and make a true zero-copy interface impossible. Client ~~~~~~ .. code-block:: python import asyncio from picows import ws_connect, WSFrame, WSTransport, WSListener, WSMsgType, WSCloseCode class ClientListener(WSListener): def on_ws_connected(self, transport: WSTransport): transport.send(WSMsgType.TEXT, b"Hello world") def on_ws_frame(self, transport: WSTransport, frame: WSFrame): print(f"Echo reply: {frame.get_payload_as_ascii_text()}") transport.send_close(WSCloseCode.OK) transport.disconnect() async def main(): transport, client = await ws_connect(ClientListener, "ws://127.0.0.1:9001") await transport.wait_disconnected() if __name__ == "__main__": asyncio.run(main()) Server ~~~~~~ .. code-block:: python import asyncio from picows import ws_create_server, WSFrame, WSTransport, WSListener, WSMsgType, WSUpgradeRequest class ServerClientListener(WSListener): def on_ws_connected(self, transport: WSTransport): print("New client connected") def on_ws_frame(self, transport: WSTransport, frame: WSFrame): if frame.msg_type == WSMsgType.CLOSE: transport.send_close(frame.get_close_code(), frame.get_close_message()) transport.disconnect() else: transport.send(frame.msg_type, frame.get_payload_as_memoryview()) async def main(): def listener_factory(r: WSUpgradeRequest): # Routing can be implemented here by analyzing request content return ServerClientListener() server: asyncio.Server = await ws_create_server(listener_factory, "127.0.0.1", 9001) for s in server.sockets: print(f"Server started on {s.getsockname()}") await server.serve_forever() if __name__ == "__main__": asyncio.run(main())