0%

Chapter 6 · practice

The websockets Library, Without asyncio

Install and Pin websockets

Your Chapter 5 echo service works: the hand-built client and the hand-built server agree on every . That agreement proves less than it seems. Both sides come from the same , 618 lines across ws_handshake.py, ws_frames.py and loopback_demo.py, and a mistake made the same way on both sides cancels out, as a 16-bit length written by both would have in Chapter 3. To find such a mistake, something written by other people has to talk to your server.

This chapter brings in the websockets library, written to the same RFC 6455 by other people. First it tests your code; from Lesson 2 on, it takes over the plumbing.

The project tree holds the course’s finished versions of loopback_demo.py, ws_frames.py and ws_handshake.py, so the chapter starts from known files whatever your own Chapter 5 code looked like; nothing in this chapter edits them. You write echo_library.py from Lesson 2 on, and Run starts library_demo.py in the editor lessons.

Which version is here

This lesson has a , and its sandbox can reach one thing besides itself: the course’s package mirror, the same internal Python in Practice installed from. Nothing on the public internet is reachable. Start by asking the sandbox’s own Python which version of the library it carries:

python -c "import websockets; print(websockets.__version__)"

It should print 17.4, the version this course was checked against and the one the site’s sandbox has. That copy is installed for the sandbox’s Python, so every later lesson can import websockets without installing anything.

Pin it

requirements.txt already holds one line, pytest==9.1.1. Open the file in the Project tab, or with nano requirements.txt in the shell as in Python in Practice, and add the library below it, so the file reads:

pytest==9.1.1
websockets==17.4

The == pins the exact version. Libraries change between releases: some this chapter passes do not exist in older versions of this one. On your own computer, pip install -r requirements.txt then gives you the library this chapter uses.

Why write websockets==17.4 rather than websockets?

Install it where you can see it

Now prove the file is enough to rebuild the environment. Make a fresh , install from the file, and what the environment holds:

python -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
echo $?
.venv/bin/python -m pip freeze

pip reads both lines and installs from the course’s mirror, and echo $? prints its exit status, 0 when the install worked. On the site, the status was 0 and pip freeze listed pytest and the packages pytest needs, and the line websockets==17.4. Then ask the new environment which version it got:

.venv/bin/python -c "import websockets; print(websockets.__version__)"

Locally it printed 17.4, the same version the sandbox’s own Python should report.

If the install fails instead, pip prints an error, echo $? prints a status other than 0, and pip freeze shows no line. That is the sandbox’s connection to the mirror, not your file: your pin is still what the check reads, and no later lesson uses this install.

The site never saves .venv. Generated directories are left out when your project is saved, as Python in Practice showed, so the environment you just built is gone in the next lesson. Later lessons use the copy installed for the sandbox’s Python, which is the same version. The pin is for your own machine, and for anyone who installs this project after you.

You open the next lesson. Is your .venv still there?

The library meets your server

Open interop.py and read it; every wait in it is bounded. It starts the Chapter 5 server, loopback_demo.serve_clients, on a thread for one client on 127.0.0.1 0. Then it connects with the library’s client, connect(...) from websockets.sync.client, sends one text and one binary message and reads each echo, sends a and waits at most 2 seconds for the pong, and leaves the with block, which closes the connection. Lesson 3 has you write a client like it. Run it:

python interop.py

A run of these files on the site printed this. PORT stands for the port number the system picked; yours will differ:

interop: the Chapter 5 server is listening on 127.0.0.1:PORT
library client: the handshake succeeded
library client: echo 'hello from the websockets library'
library client: echo b'\x00\x01\x02'
library client: ping answered: True
library client: closed the connection
Chapter 5 server: [{'messages': 2, 'code': 1000}]
interop: server thread still running: False

Read it line by line. The succeeded: the library sent an upgrade request with its own fresh key, check_upgrade answered with a 101 carrying the accept accept_value computed, and the library checked that value the way your check_accept_response does. The two echoes are printed with repr, which writes a value the way Python source would: quotes around a str, a b before bytes. So the library masked a text frame and a binary frame, the Chapter 5 server unmasked and decoded them, and the echoes came back as text and as binary. The ping was answered: the library sent a ping carrying b"interop", server_reply answered with a pong carrying the same bytes, and the wait returned True within its 2 seconds. Leaving the block sent close 1000, and the server’s own report shows it echoed that close: two messages, code 1000. Last, the server thread had finished.

To see your own Chapter 5 code in this run, copy the course’s file aside with cp loopback_demo.py course_loopback_demo.py, paste yours over loopback_demo.py, and run python interop.py again. Nothing checks that run; afterwards put the course’s version back with cp course_loopback_demo.py loopback_demo.py, because later lessons use it.

You run interop.py: the websockets library's client connects to your Chapter 5 server, completes the handshake, gets back a text echo and a binary echo, gets a pong for its ping, and closes with 1000. What does that run show?

Check your work

Press Check my work. It reads requirements.txt and nothing else: exactly one websockets line, websockets==17.4, with the pytest==9.1.1 line still there; comments and blank lines are fine. It does not look at .venv, which is not saved, or at the interop run.

The library has talked to your server and agreed with it. In the next lesson the roles turn around: your Chapter 5 server becomes a library server, a handler of a few lines.

Task

Pin the library in requirements.txt: add the line websockets==17.4 and keep the line pytest==9.1.1.

Then, in the :

  1. python -c "import websockets; print(websockets.__version__)" to see the sandbox’s own version;

  2. python -m venv .venv and .venv/bin/python -m pip install -r requirements.txt to install from the course’s mirror;

  3. .venv/bin/python -m pip freeze and look for websockets==17.4;

  4. python interop.py to watch the library’s client talk to the Chapter 5 server.

Press Check my work when the pin is in the file. The check reads requirements.txt only: it expects exactly one line naming , written websockets==17.4, with no >= or ~= and not only in a comment, and it expects pytest==9.1.1 to still be there. Comments, blank lines and the order of the two lines are up to you. The check does not look at .venv, which the site does not save, or at what interop.py printed.