websockets: the README on PyPI is not the README on GitHub
Library for building WebSocket servers and clients in Python
At a glance
- What is it?
- A Python WebSocket library whose build script deletes part of its own README before uploading it, whose Makefile declares a default target that has no rule behind it, and which builds its C accelerator as optional unless an environment variable insists.
- Who is it for?
- websockets is the right choice when you want a WebSocket implementation that treats the protocol specification as a contract rather than a starting point, and you are willing to be explicit about which of its four implementations you are using. Three things to check.
- Can I use it commercially?
- Yes. BSD-3-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository received new commits within the last day.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- python-websockets/websockets5,744 ★ · +23
| Repository | 2026-09-24 | 2026-11-14 |
|---|---|---|
| python-websockets/websockets | 5.7k | 5.7k |
23 Stars in the last 51 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The examples are copy-pasted because GitHub cannot include files
The project page carries a comment explaining one of its own maintenance hazards:
.. copy-pasted because GitHub doesn't support the include directiveThe two code samples on the page, an echo server and a synchronous client, exist in the documentation as well, and the project cannot generate the project page from the documentation the way it would like. So the duplication is deliberate and acknowledged rather than accidental, which is the honest way to handle it.
What it costs is drift. The samples that appear in both places are the ones a beginner copies, and nothing in the build fails if they stop matching. A new API renamed in the tutorial leaves the project page teaching the old spelling until somebody remembers.
The first sample is the asyncio echo server. It takes one argument, loops over incoming messages with an async for, sends each one straight back, serves on a host and a port, and waits forever. The second uses the synchronous client, opens a connection as a context manager, sends one message, prints what comes back. Two short samples, and the page's claim that the whole API is one call in and one call out is visible in the first sample alone.
The package page is missing the part about commercial support
The build script reads the README and edits it before handing it to the package index. The comment explains why:
# PyPI disables the "raw" directive. Remove this section of the README.The removed section is a raw HTML block in the middle of the project page. It is the vendor's subscription offering: a line inviting commercial support and maintenance for the package through a paid subscription, a paragraph explaining that maintainers work with that vendor to deliver it, a link carrying tracking parameters, and a closing note inviting contributors who want to become official support providers to get in touch at a personal site.
So the project page on GitHub shows a commercial support section and the package page on the index does not. Both are the project's own words; one is a filtered copy of the other, filtered by a regular expression in the build script rather than by a template.
The same mechanism strips any raw HTML from the long description. It is a small thing, and it is the sort of detail that only surfaces when you read the packaging code, but it means the two descriptions of the project are not the same document.
The Makefile declares a default target that does not exist
The declaration line lists eight names:
.PHONY: default style types tests coverage maxi_cov build cleanEight rules follow, and the two lists do not line up. There is no rule for `default`, so running it exits successfully and reports that there is nothing to be done, which is the quietest way for a script to believe it invoked a build. And `spell`, which runs a spell checker over four directories and writes the corrections back, is a rule that the declaration does not mention, making it an ordinary file target that a stray file of that name would satisfy instead of the recipe.
The file also exports three environment variables for everything it runs, including one that turns on asyncio debug mode and one that makes the standard library print warnings by default rather than suppressing them. A third export works around a specific issue in the Python runtime that otherwise causes a test failure in the legacy implementation on free-threaded builds, and the comment above it says to remove the workaround, and a matching flag in the test configuration file, when the legacy implementation is dropped.
That comment is the clearest statement of the project's own compatibility debt: the older implementation is still in the test matrix and is held in place with a pinned workaround.
The C accelerator is optional unless an environment variable objects
The performance claim on the project page is that a C extension accelerates the expensive operations, and that it ships pre-compiled for the three major desktop systems as a wheel for each system and interpreter version. The build script is where the caveat lives.
The extension is declared from a single C source file, and it is marked optional based on one environment variable. If the variable says no, no extension is declared at all. If it says anything else, the extension is optional, which in the build system means a compilation failure does not stop the build. Only setting it to yes makes the extension mandatory.
So the default path is a build that tries, and quietly continues without the accelerator if the attempt fails. That is the right default for a library that wants to install everywhere. It also means the fast path can be missing from a working installation.
The version number is obtained in an unusual way in the same file: a small Python file inside the package directory is read and executed to extract the version, rather than imported or parsed.
Thirteen platform combinations, and PyPy as well
The wheel build configuration is the most exhaustive part of the packaging.
Linux is built for seven architectures, which is an unusual list: the two obvious ones, plus a 32-bit x86 target, 32-bit ARM, a 64-bit PowerPC target, the RISC-V port, and the s390 mainframe architecture. macOS is built for three: ARM, x86, and a universal binary combining the two. Windows is built for three: 64-bit, ARM64, and 32-bit x86.
On top of that, PyPy is explicitly enabled as an interpreter to build for. A project that maintains a C extension and pre-built wheels for a mainframe architecture and a RISC-V port is making a different set of trade-offs from a project that ships one source distribution and lets the platform build it.
The spell checker configuration in the same file carries a detail worth noting: two words are on an ignore list, with a comment saying they are used in the tests. So a linter is configured to tolerate misspellings that exist deliberately in fixtures, which is the correct way to do it and a small sign of how the checks are wired together.
Compliance is enforced by a build gate, not by a promise
The first of the project's four stated principles is correctness, and the claim attached to it is specific: the test suite is heavily tested for compliance with the protocol specification, and continuous integration fails if branch coverage drops below one hundred percent.
That is checkable, and the build file is where you check it. The coverage target runs the test suite under coverage, then generates a report with a floor:
coverage report --show-missing --fail-under=100A hundred percent of branches, with missing lines printed, is the gate. There is a second coverage target that runs a script of its own first and then applies exactly the same floor, which suggests the plain one is the quick path and the other is the exhaustive one.
The type checking is on the same footing: the types target runs the checker in strict mode over the package source. So correctness in this project is three things wired into make targets, a compliance claim, a coverage floor, and a strict type pass, rather than a paragraph of prose.
The one place the documentation softens is memory, which the performance section describes as optimized and configurable rather than measured.
Four implementations, and a short list of reasons to pick something else
The default implementation is built on the standard library's asynchronous framework and offers a coroutine-based interface. Alongside it the project ships implementations on top of the threading library and on top of an alternative async library, plus a Sans-I/O layer for projects that want to drive the protocol themselves.
Then there is a section titled as reasons not to use it, and it is unusually direct. The first is callbacks: if you want a callback-based interface, this library will not give you one, because it was built to provide the best coroutine-based interface it could. Pick something else.
The second is HTTP. Support for HTTP is described as minimal, just enough for a health check. If you want HTTP and WebSocket in one server, the page points you at servers that build on top of this library rather than at this library, naming two of them.
Both protocol documents the project implements are cited by number in that section: the WebSocket protocol itself and the compression extensions for it. So the scope is narrow on purpose, and the page says so before you install rather than after.
Editorial conclusion
websockets is the right choice when you want a WebSocket implementation that treats the protocol specification as a contract rather than a starting point, and you are willing to be explicit about which of its four implementations you are using. Three things to check. Decide whether the C accelerator is mandatory for your deployment, because the build makes it optional by default and a wheel can be produced without it, in which case the performance story quietly changes. Make sure you run the coverage target before you trust the claim about compliance testing, since that claim is enforced by a build gate rather than by documentation. And if you need an HTTP server in the same process, this is the wrong library by its own admission.
Frequently asked questions
how to use websockets in python
The page shows an echo server that serves on a host and port with the asyncio implementation, looping over messages and sending each one back, and a synchronous client that opens a connection as a context manager, sends one message and prints the reply. The stated core of the API is one receive call and one send call.
how to install websockets in python
The project page shows no install command. The licence and version badges link the package index page for the package named `websockets`, whose metadata requires Python 3.11 or newer and lists 3.11 through 3.15, with wheels built for PyPy and for Linux, macOS and Windows across thirteen platform and architecture combinations.
What are WebSockets used for?
The project's own scope is a WebSocket server and client library implementing the WebSocket protocol and its compression extensions, across four implementations. It states that if you also need HTTP in the same server, its own HTTP support is minimal and you should use a server built on top of it instead.
Does websockets support callback-based APIs?
No. The page says that if you prefer callbacks over coroutines you should pick another library, because this one was created to provide the best coroutine-based interface for managing WebSocket connections in Python.
What is the latest version of websockets?
Release 17.4 was published on 2026-10-30, following 17.3 on 2026-10-19 and 17.2 on 2026-10-03; the most recent push to the default branch was on 2026-10-31. The package is BSD licensed and the repository is not archived.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/python-websockets-websockets)Star history chart for your README
[](https://hysenlabs.com/projects/python-websockets-websockets)Was this useful?