Announcing Lomond, a WebSocket Client Library for Python
I'd like to announce the first official release of Lomond, a new WebSocket client library for Python. The development was sponsored by Dataplicity.
Lomond is not the first websocket client for Python, so why go to the effort of building another one? For our purposes, we needed a stand alone client that didn't need a framework to run. So that excludes the websocket client support in Tornado, aiohttp etc. The two libraries that were suitable for our product, websocket-client and ws4py, both had show-stopper bugs with ssl support; websocket-client would sometimes refuse to processes packets until additional data was received, and ws4py could lose entire packets. I'm sure both libraries could be fixed, but neither project appears to be actively maintained.
Interestingly, both of those projects had a flaw in essentially the same area: processing data from SSL sockets. PSA – using select with SSL Socket objects may appear to work, but there can be unprocessed data in an internal buffer. It's hardly surprising that the authors of these two packages got that wrong; the problems it caused occurred only in production, and disappeared when we tried to reproduce it locally. It was a classic Heisenbug.
Fixing the SSL issue was the initial motivation, but Lomond was also an opportunity to make a websocket client library that was easier to use. Lomond takes an event based approach rather than callbacks, which—to my mind—is easier to reason about.
Here's an example of an echo client in Lomond, which connects to a server, and sends back any text messages it receives:
from lomond import WebSocket
websocket = WebSocket('wss://echo.example.org')
for event in websocket:
if event.name == 'text':
websocket.send_text(event.text)
These events are generated in an orderly manner, and no threads are used (even internally).
Another motivation was that we needed to build a client that would maintain a persistent reliable connection to a websocket server. In other words: It had to recover gracefully if connectivity is flakey. The libraries we used before Lomond did not make that easy. At one point we had a second thread monitoring the WebSocket library thread in order to reconnect if the socket dies. It was impossible to reason about fully, and not an approach I can see myself ever attempting again.
The linear event based approach taken by Lomond makes persistent connections rather straightforward. Here's how the above code could be adapted to make a persistent connection:
from lomond import WebSocket
from lomond.persist import persist
websocket = WebSocket('wss://echo.example.org')
for event in persist(websocket):
if event.name == 'text':
websocket.send_text(event.text)
This one function replaced a lot of overly complex threaded code, which was deeply satisfying to remove.
Why "Lomond"?
Anything vaguely websockety was taken in PyPi, so I named it after a beautiful Scottish Loch.
Design
The internals of Lomond separate logic from IO, with a coroutine based stream parser. This design opens up the possibility of integrating Lomond with async frameworks such as asyncio and curio. It is my hope that Lomond could become a reference implementation of sorts for Websocket clients.
Roadmap
Lomond is a young project, but it has near 100% test coverage and passes the Autobahn test suite. It is production ready, but I do expect there to be more features added before a 1.0.0
release. Top of my list is per-message compression.
More Information
For more information on Lomond, see the documentation, or the official repository.
It's a bit disappointing to see that Lomond is not based on wsproto. Did you not know about this or did you feel like it was too slow or the code quality was bad?
Hi Alex. I was aware of wsproto. I can't comment on the code quality, but wsproto doesn't work in a way that I would want it to.
I see. What way would you have liked it to work then? I am a big fan of convergence and while I'm not deeply involved with the project, I would've liked to see it become the go-to base implementation of websockets.
The coroutine based stream parser was one part of Lomond that I particularly wanted to develop, and Py2 compatibility was essential. I'm sure both projects will ultimately benefit. wsproto might benefit from this for example.
Can you add the option of creating a server?
I would like to add an option to integrate the server side with tornado / asyncio etc.
Hi, is there any example of multiple websockets with Lomond and Asyncio? Thanks!
Not that I know of. For Lomond, you can run a websocket in threads. For asyncio, take a look at aiohttp, which has websocket support.