0%

Kapitel 11 · Übung

Abschlussprojekt: Ein Verleihprogramm mit dauerhafter Speicherung

Einen Befehl wählen, bevor die Datenbank geöffnet wird

Das Verleihprogramm braucht sieben Operationen, aber ein falsch geschriebener Befehl darf nicht versehentlich eine Datenbank erstellen oder öffnen. Deshalb ist die Argumentauswahl die erste Grenze in lending.py. main(argv) muss die Anfrage vollständig verstehen, bevor irgendeine Datenbankfunktion läuft.

Dieses Projekt kannst du sehen:

PfadAufgabe
lending.pyDie Datei, die du bearbeitest. Ihre Funktion main(argv) ist der CLI-Einstieg und die Funktion, die Run aufruft.
catalog_db.pyÖffnet konfigurierte Verbindungen und stellt die Verleihbefehle und Abfragen bereit.
catalog_setup.pyErstellt, prüft und migriert das Katalogschema.
catalog_import.pyLiest, validiert und importiert Gegenstands-CSV-Daten.
data/items.csvDrei Beispielgegenstände, die spätere Befehle verwenden.
tests/test_catalog.pyDie vorhandene Datenbanktestsuite mit acht Tests.
demo.pyDer sichtbare Einstieg für Run. Er ruft für diesen ersten Schritt main(["--help"]) auf.

Die drei Dateien catalog_*.py sind bereits vollständig. Beschränke deine Änderungen auf lending.py und rufe ihre benannten Funktionen auf.

Wenn du später Reset project verwendest, kehre zuerst zu der Seite zurück, an der du arbeitest. Reset stellt die vollständige Ausgangsdatei lending.py dieser Seite wieder her.

Öffne lending.py. Vervollständige die beiden Argumentkonverter und build_parser(). Die Grammatik steht fest:

init DATABASE
import-items DATABASE CSV
register-member DATABASE ID MEMBER_CODE NAME EMAIL [--loan-limit NUMBER]
checkout DATABASE LOAN_ID ITEM_ID MEMBER_ID CHECKED_OUT_ON
return DATABASE LOAN_ID RETURNED_ON
available DATABASE
activity DATABASE

Ein Unterbefehl wählt einen Parser für eine Operation. Der übergeordnete Parser liest den Befehlsnamen; der Parser dieses Befehls liest nur dessen eigene Argumente. Beginne mit dem kleinsten Fall:

commands = parser.add_subparsers(dest="command", required=True)
init_parser = commands.add_parser("init")
init_parser.add_argument("database")

required=True macht einen fehlenden Befehl zum Fehler. dest="command" speichert den gewählten Namen in args.command. Das Parsen von ["init", "catalog data.db"] erzeugt args.command == "init" und args.database == "catalog data.db"; das Parsen öffnet diesen Pfad nicht.

Wiederhole commands.add_parser(...) für jeden anderen Befehl und füge dann dessen Argumente zum zurückgegebenen Parser hinzu. Die Registrierung braucht zum Beispiel dieses ID-Argument:

register_parser = commands.add_parser("register-member")
register_parser.add_argument("member_id", metavar="ID", type=positive_integer)

Der erste String benennt das Python-Attribut args.member_id. metavar="ID" beschriftet es nur in der Hilfe. Verwende für die Attribute kleingeschriebene Varianten der anderen Argumentnamen aus der Grammatik, etwa loan_id und checked_out_on. Jeder Befehl braucht sein eigenes Argument database. Das optionale --loan-limit wird zu args.loan_limit; gib ihm type=positive_number und default=3.

Ein Konverter erhält ein Argument als Text. Er gibt den umgewandelten Wert zurück oder löst argparse.ArgumentTypeError mit einer nützlichen Meldung aus. Fange beim Zahlenkonverter eine fehlgeschlagene Umwandlung mit float(value) ab und lehne Werte ab, für die math.isfinite(number) falsch oder number <= 0 wahr ist. math.isfinite lehnt Unendlichkeit und nan ab, die keine brauchbaren Ausleihlimits ergeben. Der Ganzzahlkonverter kann value.isascii() und value.isdigit() verlangen, bevor er umwandelt und prüft, dass das Ergebnis positiv ist.

Verwende verpflichtende Unterbefehle. Parse jede ID als positive ganze Zahl zur Basis zehn. Parse ein ausdrückliches --loan-limit als positiven endlichen float; sein Standardwert ist die ganze Zahl 3. Werte wie 0, -2, nan und inf sind Parserfehler. Die Textfelder bleiben vorerst Strings. Ihre Befehlsfunktionen entfernen umgebenden Leerraum, wenn weitere Befehle hinzukommen. Dort kann ein leerer Wert eine präzise Anwendungsmeldung erhalten.

main(argv=None) muss die übergebene Liste parsen. Wenn argv gleich None ist, verwendet es sys.argv[1:]. Fange argparse-SystemExit nicht ab: Hilfe muss mit Status 0 enden, während ein fehlender Befehl, unbekannter Befehl, fehlendes Argument oder fehlerhafter typisierter Wert mit Status 2 endet. Diese Ergebnisse treten vor der Befehlsauswahl zur Ausführung auf und können deshalb nicht zu einem Anwendungsstatus von 1 werden.

Klicke auf Run. demo.py fängt den Hilfeaufruf auf, meldet seinen Status und zählt datenbankähnliche Dateien in einem leeren temporären Verzeichnis:

== choose a command ==
Help status: 0
Commands: init, import-items, register-member, checkout, return, available, activity
Database files created: 0

Noch existiert keine Befehlsfunktion, also hört Run dort auf. Eine gründliche Parserprüfung probiert außerdem die vollständige Grammatik mit unbekannten Werten aus, einschließlich eines Pfads mit Leerzeichen und Unicode. Sie prüft nur das Parsen und fordert nie einen unfertigen Befehl auf, die Datenbank zu berühren.

Aufgabe

Vervollständige positive_integer, positive_number und build_parser in lending.py für die exakte Grammatik von init, import-items, register-member, checkout, return, available und activity.

Mache den Unterbefehl verpflichtend. Verwende die Konverter für jede ID und für --loan-limit, dessen Standardwert die ganze Zahl 3 ist. Halte main(argv=None) beim Import ohne Nebenwirkungen, lass argparse-Hilfe und Parserfehler ihr ursprüngliches SystemExit auslösen und öffne beim Parsen keine Datenbank.