Een nieuwe bestandsversie voorbereiden en veilig publiceren
Na deze les kun je
Voorbereiden en publiceren van één bestand scheiden
Tijdelijke bestanden opruimen zonder fouten te verbergen
Je bewaart een instellingenbestand en het schrijven stopt halverwege. Bij direct openen in modus w is de oude inhoud dan al verdwenen. We scheiden daarom het voorbereiden van een nieuwe versie van het moment waarop die versie de oude vervangt.
Twee stappen met een duidelijke grens
Stagen betekent hier: schrijf eerst een afzonderlijk tijdelijk bestand en sluit het. Het doelbestand blijft ondertussen staan. Pas daarna publiceer je de gereedgemaakte versie met os.replace.
Een geslaagde vervanging binnen hetzelfde bestandssysteem is een atomaire naamswijziging: die stap biedt de oude of nieuwe versie via het doelpad. Maak het tijdelijke bestand daarom in de map van het doel, niet ergens op een ander bestandssysteem. De ouders van het doel bestaan in deze oefening al.
Eerst gereedmaken, dan vervangen
publiceer.py
import os
from pathlib import Path
from tempfile import NamedTemporaryFile
target = Path("bericht.txt")
target.write_text("oud", encoding="utf-8")
with NamedTemporaryFile(mode="w", encoding="utf-8", dir=".", delete=False) as stream:
pending = Path(stream.name)
stream.write("nieuw")
print(target.read_text(encoding="utf-8"), stream.closed)
os.replace(pending, target)
print(target.read_text(encoding="utf-8"), pending.exists())
Verwachte uitvoer
oud True
nieuw False
NamedTemporaryFile kiest een tijdelijke naam en opent een echte stream. Met mode="w" en UTF8 kun je tekst schrijven; dir kiest de doelmap. delete=False laat het bestand na het sluiten bestaan, zodat een volgende stap het nog kan vervangen.
stream.name geeft de gekozen naam. Bewaar die als een Path en sluit de context voordat je publiceert. De eerste uitvoerregel laat zien dat het doel nog oud bevat terwijl de nieuwe stream al dicht is.
Opruimen hoort bij de afspraak
Na geslaagde vervanging bestaat het tijdelijke pad niet meer: zijn inhoud staat nu onder de doelnaam. Bij een fout vóór het vervangen kan het tijdelijke bestand nog wel bestaan. unlink(missing_ok=True) verwijdert dat pad en accepteert dat het al verdwenen is.
Een mislukte publicatie laat het oude bestand staan
geweigerd.py
from pathlib import Path
from tempfile import NamedTemporaryFile
target = Path("bericht.txt")
target.write_text("oud", encoding="utf-8")
with NamedTemporaryFile(mode="w", encoding="utf-8", dir=".", delete=False) as stream:
pending = Path(stream.name)
stream.write("nieuw")
try:
try:
raise OSError("publicatie geweigerd")
finally:
pending.unlink(missing_ok=True)
except OSError as problem:
print(str(problem))
print(target.read_text(encoding="utf-8"), pending.exists())
Verwachte uitvoer
publicatie geweigerd
oud False
Dit voorbeeld bootst een weigering vlak vóór publicatie na; de oude inhoud blijft intact. finally voert de opruimstap uit bij zowel succes als een fout. De fout zelf blijft zichtbaar, zodat een aanroeper een mislukte opslag niet als voltooid behandelt.
Ook het voorbereiden kan mislukken nadat de tijdelijke naam is aangemaakt. Bewaar daarom dat pad zodra het bekend is. Ruim het bij een schrijffout op en gebruik een kale raise om precies de oorspronkelijke uitzondering door te geven.
Middelen doorgeven maakt fouten toetsbaar
De functies krijgen de bestandsmaker en vervanger als optionele parameters. Normaal zijn dat NamedTemporaryFile en os.replace. De controle kan daardoor echte bestanden openen, een gedeeltelijke schrijffout opwekken of publicatie weigeren, zonder een volle schijf nodig te hebben.
Veelgemaakte fout
Het doel alvast met w openen voordat de tijdelijke versie klaar is, vernietigt de bescherming. Een andere fout is de uitzondering wegvangen en gewoon None teruggeven: dan lijkt opslaan geslaagd. Bescherm het doel én de terugmelding over wat er werkelijk gebeurde.
Ook opruimen kan zelf mislukken, bijvoorbeeld door een PermissionError bij unlink. In deze beginneropdracht lukt het verwijderen van het eigen tijdelijke bestand; onder die aanname blijft de oorspronkelijke schrijf- of publicatiefout zichtbaar. Als verwijderen óók faalt, mag je niet zeggen dat het restant weg is en kan die tweede fout de ontvangen uitzondering bepalen.
AAN DE SLAG 0/4
1
Een gesloten tijdelijke versie
Maak stage_text(target, text, *, make_temp=NamedTemporaryFile): maak met die factory één tijdelijk UTF8-tekstbestand in target.parent, delete=False. Schrijf text, sluit de stream en geef het tijdelijke Path terug. Laat het doel ongewijzigd. In deze opdrachten lukt het opruimen van het eigen tijdelijke bestand.
2
Een gereed bestand publiceren
Maak publish_text(target, text, *, stage=stage_text, replace=os.replace): vraag met stage de tijdelijke versie op en geef die en het doel aan replace. Sluit vóór publiceren; ruim het eigen tijdelijke pad op, met behoud van andere bestanden en submappen. Geef bij succes None. In deze opdrachten lukt het opruimen van het eigen tijdelijke bestand.
3
Publicatiefout behouden
Zorg dat publish_text bij de opgewekte fout vóór vervanging het oude doel of ontbrekende doel intact laat, geen tijdelijk bestand achterlaat en exact de oorspronkelijke OSError doorgeeft. In deze opdrachten lukt het opruimen van het eigen tijdelijke bestand.
4
Gedeeltelijke schrijffout opruimen
Zorg dat stage_text ook na een gedeeltelijke echte schrijffout zijn eigen tijdelijke bestand verwijdert, de stream sluit en exact de oorspronkelijke fout doorgeeft. Laat het doel intact. In deze opdrachten lukt het opruimen van het eigen tijdelijke bestand.
Maak eerst stage_text en gebruik die via de meegegeven stage bij publish_text. Ruim alleen het eigen tijdelijke pad op; andere bestanden en submappen horen niet bij deze opslagactie. Vervang nooit het doel tijdens voorbereiden en laat oorspronkelijke fouten door.
Deze aanpak beschermt één bestandsvervanging, geen groep bestanden of twee gelijktijdige schrijvers. Sluiten is bovendien geen bewijs dat gegevens een stroomuitval overleven; daarvoor zijn aanvullende afspraken zoals synchroniseren nodig. De browseroefening werkt in zijn tijdelijke bestandssysteem en slaat hiermee niets op je eigen pc op.
Onthouden
Bereid de nieuwe versie apart in dezelfde map voor en sluit haar.
Publiceer met één vervanging en ruim restanten op.
Een mislukte voorbereiding of publicatie blijft een fout.
Hierna passen we deze I/O-keuzes toe in een complete automatisering.