0%

Capstone: The Reading List Service and Client · capstone

Capstone project: Run Them Together

Each half has been proven alone, and the two have never talked to each other. This lesson runs eleven commands of your client against your server, starting from reading_server.sample_state() with the now values a test would pass, and every step has to match the contract:

StepCommandnowWhat should happenExit
1list0GET /books, two books0
2add, writer’s token0POST /books, 201, the new printed0
3show 30GET /books/3, fresh until 600
4show 330nothing sent0
5show 360If-None-Match, 304, fresh until 1200
6update 3 via /v0, reader’s token60308, then PUT /books/3: 4032
7update 3 via /v0, writer’s token60308, then 200 with the new title0
8show 390nothing sent, the old title0
9show 3120If-None-Match, 200, the new title0
10delete 3 via /v0120308, then 2040
11show 3200If-None-Match, 4042

Every row of “Not built yet” is in there. Steps 6, 7, and 10 cross the old path, step 5 revalidates, step 2 prints a location, and the server’s statuses decide each exit code.

A redirect you can see

In steps 6, 7, and 10 requests follows the 308 for you, as Chapter 10 showed: it sends the same and body to the new URL and returns the last response. A redirect leaves a trace, though. response.history is a of the responses that came before the last one, one per hop, oldest first, and response.url is where requests ended up. For step 7, history holds one 308 whose url is the /v0 URL.

The contract asks the client to say so: one line on standard error per redirect followed, followed STATUS from URL, before any other line. Standard output stays for results, so a script that reads the book list never sees the line. Write report_redirects(response), a two-line over response.history that prints each entry’s status_code and url with file=sys.stderr. Then call it in run for every command that got a response: right after call for list, add, update, and delete, and for show when cached_show returned a response, before the error check in both places.

update 3 through /v0 with the writer's token succeeds. How many followed lines appear, and where?

Watch it run

pl_http.py starts your server on a the system picks and runs the eleven commands with your main, the now values above, and a cache file in a temporary folder. URLs read http://books.test, because it points requests at your server without a name lookup.

python pl_http.py capstone

For each command it prints the requests your server received, then the , the exit status, and what your client printed on each stream. Steps 4 and 5 look like this:

--- requests and replies ---
--- your reading_client.py show 3 --base-url http://books.test  (now 30) ---
exit status: 0
standard output: ['3: Piranesi by Susanna Clarke']
standard error: []
--- requests and replies ---
request 1  GET /books/3  (If-None-Match: "6a584f0c2da517c8750cdd3157f9d6f9cc07bbd19955c92a953fcc8cb494f8a9")  ->  304 Not Modified  (connection 1)

The first block has no request at all: the entry stored at step 3 was fresh. Step 6 crosses the old path with a token that may only read:

request 1  PUT /v0/books/3  ->  308 Permanent Redirect  (connection 1)
request 2  PUT /books/3  ->  403 Forbidden  (connection 1)
--- your reading_client.py update 3 --base-url http://books.test/v0 --token tok-ben-reads-K9pz --title "Piranesi (paperback)" --author "Susanna Clarke"  (now 60) ---
exit status: 2
standard output: []
standard error: ['followed 308 from http://books.test/v0/books/3', '403 this token may read the list but not change it']

Two requests reached your server for one command. The second got a 403, not a 401, so requests sent the token again after the redirect: it keeps authorization when the new URL is on the same host. The standard-error list starts with your followed line and ends with the one error line. Every request says connection 1: requests kept one connection open across all eleven commands, as Chapter 9 taught your server to allow. At the end, pl_http opens two empty connections so that serve() reaches its count of three and returns.

Fresh is not the same as current

Step 8 is the one that looks wrong:

--- your reading_client.py show 3 --base-url http://books.test  (now 90) ---
exit status: 0
standard output: ['3: Piranesi by Susanna Clarke']

At step 7 the title became Piranesi (paperback), and your server knows it. Your client printed the old title anyway, and sent nothing. It is right to. At step 5 the server’s 304 said max-age=60: you may reuse this copy for 60 seconds without asking. That promise ran until 120, and the change at step 7 cannot reach a copy the client never asks about. Step 9, at 120, is when the promise ends: the client sends the old tag, the tag no longer matches, and the 200 brings the new title. max-age trades freshness for requests saved, and the server chooses the trade.

Step 8 prints the old title, though step 7 changed it. Is the client wrong?

What is proven, and how

Check my work does not read that run. It repeats every check of Lessons 1 and 2, then runs eleven commands in memory: a real requests.Session whose only adapter hands each request’s bytes to your server’s handler class in the same process, so requests follows the 308s and reads the 304 as it would on a network. The books, titles, tokens, and base URL are new, MAX_AGE is 45, and every exit status, output line, followed line, and request count must match, with TIMEOUT on every hop.

Your task

  1. Write report_redirects.

  2. Call it in run for every command, before the error check.

  3. Run python -m pytest -q and python pl_http.py capstone.

  4. Press Check my work.

The two halves now agree on every command of the contract. The next lesson writes the tests that would notice if one of them stopped.

Task

Report every redirect your client followed, and run both halves together.

  1. report_redirects(response): for each entry of response.history, in order, print followed STATUS from URL on standard error, with the entry’s status_code and url. Nothing for an empty history.

  2. In run, call it for every command that got a response (for show, when cached_show returned one), before the error check, so the followed lines come before any other standard-error line.

Keep every server and client row from Lessons 1 and 2. Run python -m pytest -q and python pl_http.py capstone in the .

Then press Check my work. It repeats every check of Lessons 1 and 2, then checks report_redirects on a reply that followed a 308 and a 301, and the first standard-error line of list, show, add, update, delete, and a refused update, each through a /v0 base . Last, it runs eleven commands of your client against your server in memory, through a real requests session whose only adapter hands each request’s bytes to your server’s handler class: books, titles, and tokens you have not seen, the base URL http://shelf.test:8123, MAX_AGE set to 45, and now 0, 0, 0, 30, 60, 60, 60, 90, 120, 120, 200. Every exit status, standard-output line, followed line, error line, request count, If-None-Match, and the timeout on every hop must match; then main with no session must make its own with make_session. While your client runs, reading_client.requests cannot send anything. It does not read your terminal or the pl_http.py run.