0%

Abschlussprojekt: Ein Werkzeug für Übungsberichte · Übung

Eine verlässliche CLI-Grenze schaffen

Alles funktioniert. Noch kann es niemand bequem verwenden, denn bislang musst du das Modul importieren und Funktionen von Hand aufrufen.

Diese Lektion gibt dem Werkzeug eine Kommandozeilenschnittstelle. Das hast du in Kapitel 8 schon gemacht; die Mechanik wird dir vertraut vorkommen. Neu ist, dass dieses Programm jetzt zwei Dateien schreibt. Ihre Schreibreihenfolge ist eine Entscheidung mit Folgen.

Die Schnittstelle

python practice_report.py REPORT_PATH [--cache PATH] [--page N] [--page-size N]
                          [--max-age SECONDS] [--refresh | --offline-source PATH]

Der Berichtspfad ist positional, denn bei einem Werkzeug, das einen Bericht schreibt, solltest du nicht raten müssen, wo er gelandet ist. Alles andere hat einen Standardwert: Cache unter practice-cache.json, Seite 1, Seitengröße 3, Höchstalter 86400 Sekunden.

Der Parser setzt drei Regeln durch, bevor irgendetwas anderes passiert:

--refresh und --offline-source schließen sich gegenseitig aus. Das eine sagt „Hole mir aktuelle Daten“, das andere „Berühre das Netzwerk nicht“. Gemeinsam sind sie ein Widerspruch. Argparse kann einen Widerspruch klarer zurückweisen, als dein Code ihn auflösen kann.

Abkürzungen sind ausgeschaltet. allow_abbrev=False, wie in Kapitel 8. Ohne diese Einstellung bedeutet --ref stillschweigend --refresh, und ein Tippfehler wird zu einer undokumentierten Funktion.

Pfadkollisionen führen vor jeder Ein-/Ausgabe zum Abbruch. Wenn Berichtspfad und Cache-Pfad dieselbe Datei bezeichnen, halte an. Beide in dasselbe Ziel zu schreiben erzeugt eine Datei, die keines von beiden ist. Das nach dem ersten Schreibvorgang zu bemerken ist zu spät.

Erzeuge den Text vor dem Öffnen

Um diese Reihenfolgeregel geht es in dieser Lektion eigentlich.

1. choose the page          (may fail: network, cache, validation)
2. render the whole report  (may fail: bad data, defect in your code)
3. write the cache          (only on the network path)
4. write the report
5. print one line to stdout

Schritt 1 und 2 können scheitern, ohne eines der Ziele zu berühren. Schritt 3 und 4 können den bisherigen Inhalt ersetzen, und jeder Schreibvorgang kann weiterhin scheitern. Deshalb müssen alle Validierungen und die gesamte Texterzeugung im Speicher fertig sein, bevor das erste Ziel geöffnet wird.

Wenn du umgekehrt zuerst die Berichtsdatei öffnest und den Text nach und nach hineinschreibst, hinterlässt ein Fehler auf halbem Weg einen abgeschnittenen Bericht ohne vorherige Version. Die Person verliert eine gute Datei und bekommt eine kaputte, aus einem Lauf, der ohnehin nie erfolgreich enden konnte.

Das ist das dritte Mal, dass diese Idee auftaucht: beim Berichtsschreiber in Kapitel 5, beim Cache-Schreiber in Lektion 1 und jetzt beim gesamten Werkzeug. Jedes Mal dieselbe Regel, in größerem Maßstab.

Zuerst der Cache, dann der Bericht

Schreibe auf dem Netzwerkpfad den Cache vor dem Bericht.

Die Überlegung ist, welchen Fehlerfall du lieber hättest. Wenn das Schreiben des Caches gelingt und das Schreiben des Berichts scheitert, hast du aktuelle Cache-Daten und keinen neuen Bericht. Der nächste Lauf ist dann schnell und erzeugt den Bericht. Wenn der Bericht gelingt und der Cache scheitert, hast du einen Bericht ohne Cache, und der nächste Lauf ruft unnötig erneut ab.

Keines davon ist eine Katastrophe. Das erste ist besser. Ordne die Schritte entsprechend.

Die anderen drei Pfade, aktueller Cache, veralteter Cache und Offline, schreiben nur den Bericht. Bei keinem gibt es neue Daten für den Cache.

Eine Ergebniszeile, eine Fehlerzeile

Die Exit-Vorgaben folgen demselben Prinzip wie in Kapitel 8:

ErgebnisstdoutstderrStatus
Erfolgeine Zeile mit dem BerichtspfadWarnungen, falls vorhanden0
Erwarteter Fehlerleereine Zeile1
Falsche Argumenteleerargparse-Aufrufhilfe2
Fehler in deinem Codekeine ZusageTracebackungleich null

Fange nur die erwarteten Fehler ab: Timeout, ConnectionError, HTTPError und JSON-Dekodierfehler von Requests, ungültige Eingabedaten sowie OSError beim Lesen oder Schreiben von Dateien. Eine unabhängige RequestException, einschließlich InvalidURL, InvalidSchema oder MissingSchema, behält ihren Traceback. Diese Programmierfehler bei Anfragen erben auch von ValueError und OSError. Löse deshalb verbleibende RequestException-Werte nach den erwarteten Requests-Handlern und vor den beiden allgemeineren Handlern für eingebaute Ausnahmen erneut aus.

Beachte, dass Warnungen und Fehler beide stderr verwenden, aber unterschiedliche Bedeutungen haben. Eine Warnung begleitet einen erfolgreichen Lauf mit Exit-Status 0. Ein Fehler ersetzt das Ergebnis und führt zu Exit-Status 1.

Jetzt bist du dran

Vervollständige build_parser() und diese Funktion in practice_report.py:

def run_report(
    report_path, cache_path, page, page_size, max_age, *,
    refresh=False, offline_source=None, session=None, now_epoch=None,
):
    ...

Gib bei Erfolg Wrote N items to REPORT_PATH from SOURCE. mit einem Zeilenumbruch aus. Ersetze die Platzhalter durch die ausgewählte Artikelzahl, den angeforderten Pfad und die Quelle. Gib 0 zurück. Gib bei den oben genannten erwarteten Betriebsfehlern 1 zurück; lass unabhängige Anfragefehler und Programmierfehler weiterlaufen.

Verbinde den Parser mit choose_page, erzeuge den Berichtstext, schreibe in der dokumentierten Reihenfolge, gib die übergebenen Warnungen aus und liefere den richtigen Status zurück. Halte main(argv=None) genau wie in Kapitel 8 beim Import frei von Ausführung: parsen, delegieren, zurückgeben.

Erweitere jetzt tests/test_practice_report.py um diese CLI-Grenzen: Widersprüchliche Modi und kollidierende Pfade scheitern vor jeder Ein-/Ausgabe; ein Fehler bei der Texterzeugung erhält den bisherigen Bericht und Cache; der Offline-Modus berührt weder Netzwerk noch Cache; und ein erfolgreicher Befehl gibt die dokumentierte Meldung aus und null zurück. Verwende wie bisher tmp_path, Fake-Sessions und capsys. Das Referenz-Testmodul enthält auch abschließende CLI-Prüfungen, die bestehen können, sobald die Funktionen dieser Lektion implementiert sind.

Versuche anschließend, es kaputtzumachen. Richte --cache und den Bericht auf denselben Pfad. Übergib --page 0. Übergib sowohl --refresh als auch --offline-source. Schreibe Text in die Berichtsdatei und führe einen Befehl aus, der scheitern wird. Bestätige dann, dass dein Text erhalten blieb.

Warum erzeugst du den vollständigen Bericht vor dem Öffnen der Zieldatei?

Warum schreibst du bei einem erfolgreichen Netzwerklauf den Cache vor dem Bericht?

Das Werkzeug greift auf einen veralteten Cache zurück und schreibt einen Bericht. Was sollte es tun?

Aufgabe

Vervollständige die genaue CLI-Matrix, die Vorabprüfung auf Pfadkollisionen, die eng begrenzten Ausnahmemeldungen, die Reihenfolge Cache vor Bericht und das Verhalten, das Kontrollinhalte bei Fehlern vor Schreibbeginn erhält.