Slotproject: een hulpmiddel voor oefenrapporten · oefening
Kies netwerk, cache of offline gegevens
Je hebt nu drie mogelijke bronnen voor een pagina: het netwerk, de cache op schijf en een geschreven bestand dat geen van beide gebruikt. Deze les bepaalt welke bron een uitvoering gebruikt.
Die beslissing vormt de kern van het hulpmiddel. Hier gaat een programma als dit meestal mis. Niet omdat een afzonderlijke regel moeilijk is, maar omdat regels één voor één worden ontdekt en aangeplakt, totdat niemand zonder uitvoeren kan zeggen wat het programma doet.
Schrijf dus eerst de tabel.
De tabel
| Situatie | Ophalen? | Terugvallen? | Bron |
|---|---|---|---|
--offline-source meegegeven | nee | nooit | offline |
| Cache geldig en actueel | nee | niet nodig | fresh-cache |
| Cache ontbreekt | één keer | nee | network |
| Cache ongeldig | één keer | nee, en waarschuwen | network |
| Cache geldig maar verouderd | één keer | ja, bij verwachte fout | network of stale-cache |
--refresh meegegeven | één keer | nooit | network |
Zes rijen. Elke uitvoering valt in precies één ervan. Lees de tabel twee keer, want alles hieronder werkt alleen die rijen verder uit.
Offline betekent offline
--offline-source leest de geschreven pagina en stopt. Die controleert de cache niet, leest de klok niet en doet geen verzoek. Als het bestand ontbreekt of niet overeenkomt met de gevraagde pagina, is dat een fout, geen reden om toch op te halen.
Dit moet de allereerste tak zijn. Alles wat ervoor wordt uitgevoerd, inclusief de klok uitlezen, schendt de belofte dat de offlinemodus niets anders aanraakt.
Ontbrekend en ongeldig zijn verschillende fouten
Beide leiden tot ophalen, maar het zijn niet dezelfde gebeurtenissen en ze horen er niet hetzelfde uit te zien.
Een ontbrekende cache is volkomen normaal. Het is de eerste uitvoering, of iemand heeft opgeruimd. Zeg niets en haal op.
Een ongeldige cache verdient wel een melding. Het bestand was er maar kon niet worden gebruikt. Iemand die dat nooit hoort, zal zich afvragen waarom het hulpmiddel traag is. Geef één waarschuwing en haal daarna op:
Warning: ignored invalid cache practice-cache.json.
Eén regel op stderr en daarna doorgaan. Dit is een waarschuwing in plaats van een fout, omdat het programma zich heeft hersteld.
Let op welke excepties bij welk geval horen. FileNotFoundError betekent ontbrekend. json.JSONDecodeError en ValueError betekenen ongeldig. Elke andere OSError, bijvoorbeeld een rechtenprobleem, is geen van beide: je programma kon een bestand niet lezen dat het moest lezen. Doen alsof dat een lege cache is, zou een echt probleem verbergen.
Verouderd is een terugvaloptie, geen voorkeur
Een verouderde cache is oud maar heeft een goede structuur. Het plan is actuele gegevens op te halen. De waarde van de oude kopie is dat die er is als ophalen mislukt.
Probeer dus op te halen. Als dat slaagt, gebruik je de nieuwe pagina. Als het mislukt op een manier waarop een netwerk naar verwachting kan falen, val je terug op de oude kopie en waarschuw je dat je dat deed:
Warning: refresh failed; using stale cache practice-cache.json.
“Verwacht” is wezenlijk in die zin. Het betekent precies de fouten uit hoofdstuk 11: requests.Timeout, requests.ConnectionError, requests.HTTPError, een JSON-decodeerfout of een ValueError bij het valideren van het antwoord.
Het betekent niet een TypeError door een fout in je eigen code. Als je ophaalfunctie een fout bevat en je die hier opvangt, levert het programma stilletjes oude gegevens en meldt succes. De fout gaat vermomd mee naar productie. Laat het crashen.
Vernieuwen betekent vernieuwen
--refresh zegt: ik wil actuele gegevens en weet dat ik die misschien niet krijg. De optie haalt één keer op en valt nooit terug, ook niet als er een prima verouderde cache klaarstaat.
Gegevens uit de cache geven aan iemand die expliciet om actuele gegevens vroeg, zou het verwarrendste zijn wat dit hulpmiddel kan doen.
Nooit meer dan één verzoek
Elke rij in de tabel doet nul of één GET. Nooit twee.
Geen lus voor opnieuw proberen, geen lus voor paginering, geen “probeer opnieuw met een kleinere paginagrootte”. Als een verzoek mislukt, meldt dit programma dat of valt het terug. Beleid voor opnieuw proberen is een echte ontwerpkeuze die wachttijden tussen pogingen, een maximum en een reden nodig heeft. Niets daarvan hoort midden in een functie die de gegevensbron kiest.
Nu jij
Implementeer deze interface in practice_report.py:
import time
DEFAULT_TIMEOUT = api_catalog.DEFAULT_TIMEOUT
def choose_page(
page, page_size, cache_path, max_age, *,
refresh=False, offline_source=None, timeout=DEFAULT_TIMEOUT,
session=None, now_epoch=None,
):
...
Geef een tuple met vier onderdelen terug: (page_document, source, fetched_at, warnings). source is precies "offline", "fresh-cache", "network" of "stale-cache". Gebruik None als offlinetijdstempel, het opgeslagen tijdstempel van de cache voor beide cachebronnen en de ene waarde now_epoch voor een geslaagde netwerkophaalactie. warnings is een lijst met de hierboven getoonde waarschuwingsstrings, zonder afsluitende regeleinden. Geef die lijst terug; de CLI drukt haar later af.
offline_source noemt een UTF-8-JSON-bestand met een losse pagina, geen cache-omhulsel. Ontleed het, valideer het met _validate_report_page en eis dat pagina en paginagrootte overeenkomen met de gevraagde gehele getallen. Een hulpfunctie zoals read_offline_page(path, *, page, page_size) kan die tak kort houden.
Eis voor andere modi echte gehele getallen voor pagina (1 tot en met 10), paginagrootte (1 tot en met 3) en niet-negatieve max_age en now_epoch. Haal op via api_catalog.fetch_items_page(page, page_size, timeout=timeout, session=session) en pas _validate_report_page op het resultaat toe. Laat andere programmeerfouten in verzoeken verdergaan, net als TypeError.
Deze functie kiest gegevens; ze schrijft geen cache of rapport. Laat die schrijfbewerkingen over aan de CLI-les.
Handel de takken af in de volgorde van de tabel. Lees int(time.time()) één keer, alleen wanneer er geen now_epoch is meegegeven en pas nadat de offlinetak is uitgesloten.
Test choose_page rechtstreeks met een namaaksessie en een expliciete now_epoch. Het startpunt voor de commandoregel wordt pas in les 5 afgemaakt, dus practice_report.py --refresh uitvoeren is nog geen test van deze functie.
Een cachebestand bestaat, maar bevat verkeerd gevormde JSON. Wat hoort er te gebeuren?
Waarom weigert --refresh terug te vallen op een verouderde cache?
Waarom mag een TypeError binnen het ophalen geen terugval op de verouderde cache veroorzaken?
Opdracht
Implementeer de exacte beslistabel voor netwerk/cache/offline met één meegegeven tijd, maximaal één begrensde ophaalactie en gerichte excepties voor terugval op een verouderde cache.