← Python/Eindproject: Urenrapport
Les 6 van 8
MODULE 14 · 06◷ 35 min

Een rapport eerst opbouwen en daarna publiceren

Na deze les kun je
  • Een gewone uitvoernaam en het gekozen formaat controleren.
  • Rapportgegevens getrouw naar JSON of CSV vertalen.
  • Een oud rapport bij I/O-fouten behouden met gerichte opruiming.

Een berekend rapport kan nog mislukken bij het schrijven. Als je het bestaande rapport meteen overschrijft, kan een fout halverwege een onbruikbaar bestand achterlaten. Deze module maakt daarom eerst de volledige tekst en publiceert die pas na een geslaagde tijdelijke schrijfopdracht.

Een bestemming is een afspraak

De gebruiker kiest hier één gewone bestandsnaam binnen een bestaande, vertrouwde uitvoermap. Een vrij pad is geen onderdeel van die keuze. Controleer de toegestane ASCII-tekens, de lengte en de kleine extensie voordat je uitvoer voorbereidt.

Een naam met twee interne punten kan gewoon geldig zijn. De grens gaat over één leafnaam zonder padseparatoren, niet over het verbieden van ieder puntenpaar. De uitvoermap zelf los je op naar haar bestaande absolute pad.

Gegevens eerst naar een volledige tekst

De schrijver rekent geen teamtotalen opnieuw uit. JSON bewaart alle meegegeven velden, inclusief eventuele extra metadata, met vaste sleutelvolgorde en inspringing. UTF8 kan daarbij tekst zoals een plaatsnaam met een accent rechtstreeks bewaren.

Gegevens eerst volledig naar tekst vertalen
import json

report = {'team': 'balie', 'minutes': 15}
text = json.dumps(report, sort_keys=True, indent=2) + '\n'
print(text, end='')
print(report['minutes'])
Verwachte uitvoer
{
  "minutes": 15,
  "team": "balie"
}
15

Het voorbeeld maakt eerst één complete tekst en laat de rapportgegevens gelijk. Een fout bij deze omzetting hoeft nog geen uitvoerbestand te raken. Dat is een nuttige scheiding tussen gegevens voorbereiden en publiceren.

CSV heeft een ander doel: alleen de teamtabel met de vaste kop team,sessions,minutes. Periodemetadata hoort niet bij die tabelvorm. Bewaar de gegeven teamvolgorde en gebruik expliciete LF-regels, zodat de bestandsvorm voorspelbaar blijft.

Een eigen tijdelijke entry bijhouden

Met NamedTemporaryFile maak je een eigen tijdelijke bestandsnaam in dezelfde map als het doel. Schrijf de volledige tekst en sluit het bestand. Pas daarna vraag je os.replace om de doel-entry te vervangen.

Vervangen na het sluiten van de tijdelijke bron
from pathlib import Path
from tempfile import TemporaryDirectory
import os

with TemporaryDirectory() as folder:
    root = Path(folder)
    target = root / 'rapport.txt'
    target.write_text('oud', encoding='utf-8')
    staged = root / 'eigen-tijdelijk.txt'
    with staged.open('w', encoding='utf-8') as stream:
        stream.write('nieuw')
    print(stream.closed)
    os.replace(staged, target)
    print(target.read_text(encoding='utf-8'))
    print(staged.exists())
Verwachte uitvoer
True
nieuw
False

De uitvoer maakt de volgorde zichtbaar: de tijdelijke bron is gesloten voordat vervangen gebeurt. Na succes heeft de bestemming de nieuwe inhoud en is het tijdelijke pad verdwenen. In de opdracht maakt NamedTemporaryFile de unieke tijdelijke naam voor je.

Een tekstwrite meldt hoeveel tekens zijn geschreven. Controleer die retourwaarde tegen len(text), ook wanneer geen exception is opgeworpen. Een korte schrijfopdracht is nog geen volledige nieuwe publicatie.

Opruimen hoort bij één uitvoering

Bewaar het exacte tijdelijke pad zolang jouw uitvoering daarvoor verantwoordelijk is. Een finally kan dat eigen pad opruimen wanneer schrijven, sluiten of vervangen faalt. Bij een geslaagde vervanging is die tijdelijke entry al opgegaan in het doel.

Een glob met alle tijdelijke namen wissen is te ruim. Een buurfile kan van een eerdere of andere uitvoering zijn, ook met dezelfde prefix. De oude rapportinhoud en die buurfiles moeten bij een fout behouden blijven.

Veelgemaakte fout: de laatste pad-entry oplossen

Een bestaand doel kan een symlink zijn. Als je die laatste entry met resolve volgt, kun je bij een ander bestand uitkomen. Hier wil je juist de gekozen entry vervangen, zodat de inhoud van het symlinkdoel gelijk blijft.

Deze afspraak gebruikt een vertrouwde uitvoermap zonder gelijktijdige padmanipulatie. Het patroon claimt ook geen crashduurzaamheid met fsync. De les bewijst de volgorde en foutpaden van één publicatie onder die concrete aannames.

De regie blijft klein

publish verbindt de stappen: bestemming controleren, volledig renderen en daarna tijdelijk schrijven en vervangen. Zij herhaalt een mislukte bewerking niet en maakt van een fout geen succespad. De CLI kan zo later alleen na echte publicatie een succesmelding geven.

AAN DE SLAG 0/4
1

Een gewone bestemming kiezen

Maak safe_destination(root, name, kind). Alleen json/csv, anders InputError("kies json of csv"). name is één ASCII-bestandsnaam van1–80tekens:eerste letter/cijfer, daarna letters/cijfers/punt/underscore/koppelteken, anders InputError("gebruik alleen een gewone bestandsnaam"). De kleine extensie moet passen, anders InputError("de extensie past niet bij het gekozen formaat"). root moet bestaand/vertrouwd zijn; geen map geeft InputError("de uitvoermap is geen map"). Geef het absolute root-pad plus leafnaam terug en resolve de laatste entry niet.

2

Het hele rapport naar tekst vertalen

Maak render_report(report, kind) zonder bronmutatie. JSON neemt alle meegegeven velden en extra metadata mee, met ensure_ascii=False, sort_keys=True, indent=2 en één afsluitende LF. CSV bevat team,sessions,minutes en de teamregels in hun gegeven volgorde, met LF-regels. Onbekend formaat geeft InputError("kies json of csv").

3

Eerst schrijven en sluiten, dan vervangen

Maak write_atomic(destination, text, *, replace, temporary_file) met de bestaande defaults. Maak een eigen tijdelijk tekstbestand in destination.parent (UTF8, newline="", delete=False), schrijf alle tekst, sluit en roep dan replace(staged, destination) aan. Geef destination terug. publish kiest eerst de bestemming en rendert alle tekst, en geeft die door aan write_atomic. Een bestaande doel-symlink wordt als entry vervangen.

4

Iedere fout bewaart de vorige publicatie

Een korte write geeft OSError("rapport niet volledig geschreven") vóór vervangen. Andere create/write/close/replace-fouten gaan als hetzelfde object door. Ruim alleen het eigen tijdelijke pad op, ook bij fouten; bewaar oud rapport en buurfiles. Naam- en serialisatiefouten komen vóór tijdelijke uitvoer. De opruimaanname is dat unlink zelf slaagt.

Samenvatting

  • Controleer de gewone naam en bouw alle uitvoertekst vóór bestandswijziging op.
  • Schrijf en sluit een eigen tijdelijke bron vóór de vervanging van de doel-entry.
  • Bewaar oud rapport en buurfiles bij fouten; ruim alleen je eigen tijdelijke pad op.
hours/reports.py
UitvoerPython
›
›_

Hier komt je resultaat.

Voer je code uit en ontdek
wat je hebt gemaakt.

0/4 stappen
EVEN MEEKIJKEN

Bekijk hoe het kan.

Je mag de oplossing bekijken. Deze poging telt dan niet mee als afgeronde les. Met Reset kun je daarna opnieuw zelf oefenen.