0%

Eindproject: de leeslijstservice en -client · eindproject

Eindproject: De client afmaken

Je server antwoordt nu op een overeenkomende If-None-Match met een 304 zonder body en geeft elk nieuw boek een location. De client gebruikt geen van beide: elke show downloadt het hele boek, en add zegt nooit waar het nieuwe boek staat. Deze les bouwt beide clienthelften.

De cacheregels zijn de regels die je in hoofdstuk 8, les 5, schreef voor cached_get in wire.py, dat zijn items bewaarde in een die de aanroeper meegaf. Die dict bestond zo lang als het programma. Elke opdracht van reading_client.py is een nieuwe run van Python, dus een cache die de tweede show moet helpen, moet de eerste overleven: hij staat in een bestand, reading_cache.json, in de map waarin de client draait.

Eén keer de klok lezen

Om te weten of iets vers is, heb je de huidige tijd nodig. main leest nu de klok, één keer:

def main(argv=None, *, session=None, now=None, cache_path=CACHE_FILE):
    ...
    if now is None:
        now = time.time()

en geeft now door naar binnen, aan run, dat het doorgeeft aan cached_show. Dat is de geïnjecteerde tijdsgrens uit het eindproject van Python in Practice, hoofdstuk 13, les 2. Niets onder main roept time.time() aan, dus een test geeft now=0 en daarna now=59 mee en krijgt bij elke run hetzelfde antwoord. In hoofdstuk 7 tot en met 9 las geen enkele code van jou een klok; een programma met een heeft één plek nodig die dat wel doet, en main is die plek.

cache_path werkt op dezelfde manier: de standaardwaarde is reading_cache.json, en elke test die met het project meekwam, geeft in plaats daarvan een pad binnen tmp_path mee.

Waarom leest main de klok en geeft het now mee, in plaats van dat cached_show zelf time.time() aanroept?

De regels die je al schreef

SERVICE_CONTRACT.md noemt ze voor dit bestand onder “The client” en daarna “The cache”. Het zijn die van hoofdstuk 8:

  • Het bestand bevat een -. De sleutels zijn de gevraagde , en elke is {"etag": ..., "expires": ..., "book": ...}.

  • Een item is vers zolang now kleiner is dan zijn expires. Een vers item wordt getoond zonder verzoek.

  • Een verlopen item wordt opnieuw gevraagd met If-None-Match en zijn etag. Een 304 houdt het boek en zet expires op now plus de eigen max-age van de 304. Een 200 vervangt het item.

  • Alleen een 200 met een etag wordt opgeslagen, met expires op now plus zijn max-age, of 0 als die er niet is. Elk ander antwoord verwijdert het item voor die URL.

Python in Practice leerde je zo’n bestand lezen en schrijven, dus beide komen met het project mee: load_cache(cache_path) geeft de opgeslagen dict terug, of {} als het bestand ontbreekt, niet gelezen kan worden, geen JSON is of geen JSON-object is, en save_cache(cache_path, cache) schrijft de dict terug als -JSON.

De cache gebruikt de URL die de client vroeg als sleutel, dus een show via de oude /v0-basis houdt eigen items bij, ook al verwijst de server hem door.

Een location is nog geen URL

De 201 van je server draagt location: /books/3. Dat is een pad, relatief ten opzichte van de URL die de client vroeg, en een client die het afdrukt zoals het binnenkwam, drukt een half adres af. Hoofdstuk 6, les 2 zette zulke waarden om met urljoin uit urllib.parse, en reading_client.py importeert die al:

urljoin("http://127.0.0.1:8080/books", "/books/3")    # 'http://127.0.0.1:8080/books/3'

Zet het om ten opzichte van response.url, waar requests uitkwam, niet ten opzichte van --base-url: na een verschillen die twee, en het antwoord dat de location droeg, is de juiste basis.

Mijlpalen

Eerst max_age(response). Splits response.headers.get("cache-control", "") op komma’s. Geef voor het element dat, gestript en in kleine letters, begint met max-age=, de ASCII-cijfers daarachter terug als int. Geef 0 terug als dat er niet is. De headers van requests zijn niet hoofdlettergevoelig, dus "cache-control" vindt ook Cache-Control.

Dan cached_show(base_url, book_id, *, cache_path, now, session=None, timeout=TIMEOUT). Bouw de URL met book_url, laad de cache en zoek het item op. Geef een dict terug met precies vier sleutels:

SituatieVerzoek"status""book""response""source"
een vers itemgeen200het opgeslagen boekNone"cache"
een verlopen item, en een 304GET met If-None-Match200het opgeslagen boekde 304"revalidated"
al het andereGET, met If-None-Match als er iets is opgeslagende status van het antwoordde JSON ervan bij een 200, anders Nonehet antwoord"network"

Stuur het verzoek via de sessie zoals call dat doet, client = requests if session is None else session, met timeout=timeout, en sla het bestand op telkens als je een item verandert. Ongeveer twintig regels.

Dan run. De show-tak roept cached_show(args.base_url, args.book_id, cache_path=cache_path, now=now, session=session) aan in plaats van call. Een status van 400 of hoger drukt describe_error(result["response"]) af op standard error en geeft 2 terug; anders drukt hij book_line(result["book"]) af en geeft 0 terug.

Als laatste de add-regel. Na added de boekregel, dan at en urljoin(response.url, response.headers["location"]).

python -m pytest -q

De tests die met het project meekwamen, zouden nog moeten slagen.

De exitstatussen gelden nog steeds, en de shell bewaart de laatste in $?. Een show zonder boek-id stopt vóór elk verzoek, dus daarvoor is geen server nodig:

python reading_client.py show --base-url http://books.test
echo $?

argparse drukt een gebruiksblok af op standard error dat eindigt met reading_client.py: error: show needs a book id, en echo $? drukt 2 af. Een 404 eindigt ook met 2; geen antwoord eindigt met 1.

Je opdracht

  1. Schrijf max_age en cached_show.

  2. Stuur show in run via cached_show.

  3. Druk na added at en de absolute URL af.

  4. Voer python -m pytest -q uit en klik daarna op Check my work.

Een mislukte controle noemt de opdracht, de now ervan en de regel. Vraag Monty om de melding te vergelijken met “The cache” in SERVICE_CONTRACT.md, zonder de oplossing te schrijven.

De client onthoudt nu wat hij heeft opgehaald en zegt waar een nieuw boek staat. Doorverwijzingen worden nog stil gevolgd; dat verandert de volgende les, voordat beide helften samen draaien.

Opdracht

Maak twee van de clientrijen van SERVICE_CONTRACT.md af: de cache die show bijhoudt, en de absolute na add.

  1. max_age(response): het getal na max-age= in de cache-control van het antwoord, als int, of 0 als dat er niet is.

  2. cached_show(base_url, book_id, *, cache_path, now, session=None, timeout=TIMEOUT), volgens “The cache” in het contract: een vers item (now kleiner dan zijn expires) wordt teruggegeven zonder verzoek; een verlopen item wordt gevraagd met If-None-Match en zijn etag; een 304 houdt het boek en zet expires op now plus de eigen max-age van de 304; een 200 met een etag wordt onder de gevraagde URL opgeslagen als {"etag", "expires", "book"}; elk ander antwoord verwijdert het item van die URL. Gebruik load_cache en save_cache, de sessie die je krijgt, en timeout. Geef {"status", "book", "response", "source"} terug zoals de tabel in de les laat zien.

  3. In run gaat show via cached_show met now en cache_path; een foutstatus drukt describe_error af op standard error en geeft 2 terug.

  4. add drukt added ID: TITLE by AUTHOR at URL af, waarbij URL urljoin(response.url, response.headers["location"]) is.

Houd elke andere clientrij zoals die is: een timeout op elk verzoek, authorization: Bearer TOKEN alleen met --token, exitstatussen 0, 1 en 2 met één regel op standard error. Voer python -m pytest -q uit in de .

Klik daarna op Check my work. Die voert reading_client.main uit met een sessie die elk verzoek vastlegt en antwoordt volgens een script, met een cachebestand in een map die hij aanmaakt, en controleert: de timeout bij elke opdracht en dat call en cached_show een timeout die ze krijgen doorgeven; exitstatussen en streams voor antwoorden met 404, 409, 500, 503 en 401, een connect-timeout, een read-timeout, een geweigerde verbinding en drie gebruiksfouten; de , URL, het token en de -body van elk schrijfverzoek; max_age op vijf cache-control-; load_cache op een ontbrekend bestand, een map, kapotte JSON, een en bytes die geen zijn; show op de tijden 1000 tot 1099 met max-age-waarden van 45 en 7, via een eerste ophaalactie, een vers item, een 304, een 200 die vervangt, een 404 en een 200 zonder etag; en de absolute URL na add voor vier basis-URL’s en location-waarden. Terwijl je client draait, kan reading_client.requests niets versturen, dus een verzoek dat de sessie overslaat, mislukt met een melding. Niets opent een , en niets uit je terminal wordt beoordeeld.