0%

Slotproject: een hulpmiddel voor oefenrapporten · oefening

Rapporteer de pagina en test wat je bouwde

Je hulpmiddel kan nu een pagina op drie verschillende plekken vinden. Wat het nog niet kan, is er iets over zeggen. Deze les zet een gevalideerde pagina om in de tekst die een mens daadwerkelijk leest.

Het werk valt in tweeën. Die helften apart houden is het grootste deel van de les.

Tellen is niet afdrukken

summarize_page(page_document) beantwoordt vragen over de gegevens. Hoeveel items zijn er? Wat is de som van hun waarden? Hoe ziet dat eruit per categorie?

De functie geeft gegevens terug, geen tekst:

{
    "item_count": 3,
    "value_total": 60,
    "categories": [
        {"category": "color", "item_count": 2, "value_total": 30},
        {"category": "shape", "item_count": 1, "value_total": 30},
    ],
}

render_report(...) krijgt een pagina en maakt de uiteindelijke string. Die rekent zelf niets uit, maar vraagt het aan summarize_page en maakt het antwoord op.

Waarom zou je ze scheiden? Omdat ze om verschillende redenen veranderen. Je wilt de formulering van het rapport veel vaker aanpassen dan de betekenis van “totaal”. Een functie die beide doet, maakt van elke tekstwijziging een kans om de berekening stuk te maken. Door de scheiding kun je het tellen ook testen met gewone dictionaryvergelijkingen, in plaats van getallen uit een alinea te halen.

Sorteer de categorieën altijd

Categorieën komen uit een dictionary die je tijdens het doorlopen van de items opbouwde. Hun volgorde weerspiegelt dus wanneer ze toevallig verschenen. Twee uitvoeringen met dezelfde gegevens in een andere volgorde leveren rapporten op die alleen verschillen in regelvolgorde.

Dat is een slechte eigenschap voor iets dat mensen vergelijken. Sorteer op categorienaam en het rapport wordt stabiel, zodat je het verschil met dat van gisteren kunt bekijken.

De items zelf behouden de volgorde die de API stuurde, want die volgorde is data. De categorieën zijn je eigen groepering, dus hun volgorde bepaal jij.

Maak de namen ondubbelzinnig

Een categorie is tekst van een dienst. Tekst kan een komma, een aanhalingsteken of een regeleinde bevatten. Elk daarvan kan een nette regel verwarrend maken.

name = json.dumps(category["category"], ensure_ascii=False)

json.dumps op een string geeft die string met aanhalingstekens en escapes terug: "color" blijft leesbaar en een naam met een regeleinde verschijnt als één ondubbelzinnig token in plaats van je rapport ongemerkt over twee regels te verdelen. ensure_ascii=False houdt echte niet-ASCII-tekens leesbaar in plaats van café in caf\u00e9 te veranderen.

Dit kost één functieaanroep en voorkomt een hele familie verwarrende fouten.

De exacte vorm

Practice API report
source: network
fetched_at: 1754000000
page: 1
page_size: 3
dataset_total: 5
item_count: 3
value_total: 60
categories:
- "color": 2 item(s), 30 value
- "shape": 1 item(s), 30 value

source is een van network, fresh-cache, stale-cache of offline, zodat het rapport vertelt waar zijn gegevens vandaan kwamen. Dat is belangrijk: een lezer hoort te weten of die naar een live antwoord kijkt of naar een antwoord van een uur oud.

fetched_at is het tijdstempel voor elke bron behalve offline, dat er geen heeft en none afdrukt. Een offlinerapport heeft geen ophaaltijd. Die verzinnen zou een leugen zijn in een bestand dat mensen bewaren.

Eén afsluitend regeleinde, zoals altijd.

Houd de functie puur

render_report krijgt waarden en geeft een string terug. De functie opent geen bestanden, leest de klok niet en doet geen verzoeken.

Daardoor kan de CLI van hoofdstuk 13 het hele rapport opbouwen voordat het doelbestand wordt geopend. Daardoor blijft je vorige rapport intact als het opbouwen mislukt. Dezelfde regel uit les 1, “doe het werk dat kan mislukken vóór de stap die iets vernietigt”, volgt nu uit een keuze die je hier maakte.

Nu jij

Twee stukken werk die bij elkaar horen.

Voeg eerst summarize_page(page_document) en render_report(page_document, *, source, fetched_at) toe aan practice_report.py, volgens de vorm hierboven. Beide gebruiken de validatie voor rapportpagina’s en laten de invoer ongewijzigd. Geef source en fetched_at op naam mee wanneer je de renderer aanroept.

De renderer wijst een onbekende bron af met ValueError. Eis voor source="offline" dat fetched_at=None en geef het woord none weer. Eis voor elke andere bron een echt niet-negatief geheel tijdstempel; wijs booleaanse en andere ongeldige waarden af met ValueError. Een lege pagina heeft nul items, een totale waarde van nul en een lege categorielijst. Het rapport eindigt dan direct na categories: en het afsluitende regeleinde.

Schrijf daarna tests/test_practice_report.py, met controles voor het rapport én alles wat het hoofdstuk tot nu toe heeft gebouwd. Het slotproject steunt op deze module, dus die verdient meer dan een symbolisch geslaagde test:

  • cachevalidatie: een goed document doorstaat opslaan en teruglezen; een booleaanse fetched_at wordt afgewezen; een cache voor pagina 2 wordt afgewezen bij een verzoek om pagina 1.

  • actualiteit: een leeftijd gelijk aan max_age is actueel, één seconde meer is verouderd en een tijdstempel uit de toekomst werpt een exceptie op.

  • bronkeuze: een actuele cache doet geen verzoek; een ontbrekende cache haalt één keer op; --refresh valt niet terug; een TypeError uit het ophalen wordt niet ingeslikt.

  • het rapport: categorieën gesorteerd, een lege pagina afgehandeld, offline drukt fetched_at: none af en er is precies één afsluitend regeleinde.

Gebruik het patroon met namaaksessies uit hoofdstuk 11 voor alles wat anders het netwerk zou benaderen. Geef overal now_epoch mee in plaats van de klok te lezen en houd elk bestand dat je tests maken onder tmp_path.

Streef naar tests die een realistische fout zouden opmerken. Vraag bij elke test niet “slaagt deze?”, maar “wat moet er stukgaan om deze te laten falen?” Als je dat niet kunt beantwoorden, is de test versiering.

Waarom geeft summarize_page een dictionary terug in plaats van opgemaakte tekst?

Waarom sorteer je de categorieregels op naam?

Waarom moet render_report het openen van bestanden en het lezen van de klok vermijden?

Opdracht

Implementeer summarize_page en render_report in practice_report.py en test beide daarna in tests/test_practice_report.py: gesorteerde categorieën, een lege pagina, offline met de weergave fetched_at: none, een categorienaam met een komma en precies één afsluitend regeleinde.