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 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 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
nowkleiner is dan zijnexpires. Een vers item wordt getoond zonder verzoek.Een verlopen item wordt opnieuw gevraagd met
If-None-Matchen zijnetag. Een 304 houdt het boek en zetexpiresopnowplus de eigenmax-agevan de 304. Een 200 vervangt het item.Alleen een 200 met een
etagwordt opgeslagen, metexpiresopnowplus zijnmax-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
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 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:
| Situatie | Verzoek | "status" | "book" | "response" | "source" |
|---|---|---|---|---|---|
| een vers item | geen | 200 | het opgeslagen boek | None | "cache" |
| een verlopen item, en een 304 | GET met If-None-Match | 200 | het opgeslagen boek | de 304 | "revalidated" |
| al het andere | GET, met If-None-Match als er iets is opgeslagen | de status van het antwoord | de JSON ervan bij een 200, anders None | het 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
Schrijf
max_ageencached_show.Stuur
showinrunviacached_show.Druk na
addedaten de absolute URL af.Voer
python -m pytest -quit 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 add.
max_age(response): het getal namax-age=in decache-controlvan het antwoord, als int, of 0 als dat er niet is.cached_show(base_url, book_id, *, cache_path, now, session=None, timeout=TIMEOUT), volgens “The cache” in het contract: een vers item (nowkleiner dan zijnexpires) wordt teruggegeven zonder verzoek; een verlopen item wordt gevraagd metIf-None-Matchen zijnetag; een 304 houdt het boek en zetexpiresopnowplus de eigenmax-agevan de 304; een 200 met eenetagwordt onder de gevraagde URL opgeslagen als{"etag", "expires", "book"}; elk ander antwoord verwijdert het item van die URL. Gebruikload_cacheensave_cache, de sessie die je krijgt, entimeout. Geef{"status", "book", "response", "source"}terug zoals de tabel in de les laat zien.In
rungaatshowviacached_showmetnowencache_path; een foutstatus druktdescribe_erroraf op standard error en geeft 2 terug.adddruktadded ID: TITLE by AUTHOR at URLaf, waarbij URLurljoin(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 max_age op vijf cache-control-load_cache op een ontbrekend bestand, een map, kapotte JSON, een 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