0%

Hoofdstuk 11 · oefening

Eindproject: een uitleenprogramma dat gegevens bewaart

Kies een opdracht voordat je de database opent

Het uitleenprogramma heeft zeven bewerkingen nodig, maar een verkeerd getypte opdracht mag niet per ongeluk een database aanmaken of openen. Daarom is het kiezen van de argumenten de eerste stap in lending.py. main(argv) moet het verzoek volledig begrijpen voordat een databasefunctie wordt uitgevoerd.

Dit is het project dat je ziet:

PadRol
lending.pyHet bestand dat je bewerkt. De functie main(argv) is het CLI-startpunt en de functie die Run aanroept.
catalog_db.pyOpent geconfigureerde verbindingen en biedt de uitleenopdrachten en query’s.
catalog_setup.pyMaakt het catalogusschema aan, controleert het en migreert het.
catalog_import.pyLeest, valideert en importeert CSV-gegevens van voorwerpen.
data/items.csvDrie voorbeeldvoorwerpen die latere opdrachten gebruiken.
tests/test_catalog.pyDe bestaande databasesuite met acht tests.
demo.pyHet zichtbare startpunt voor Run. Voor deze eerste stap roept het main(["--help"]) aan.

De drie bestanden catalog_*.py zijn al compleet. Beperk je wijzigingen tot lending.py en roep hun benoemde functies aan.

Als je later Reset project gebruikt, ga dan eerst terug naar de pagina waaraan je werkt. Reset herstelt de volledige startversie van lending.py voor die pagina.

Open lending.py. Maak de twee argumentconverters en build_parser() af. De syntaxis ligt vast:

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

Een subopdracht kiest een parser voor één bewerking. De hoofdparser leest de opdrachtnaam; de parser van die opdracht leest alleen zijn eigen argumenten. Begin met het kleinste geval:

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

required=True maakt een ontbrekende opdracht tot een fout. dest="command" slaat de gekozen naam op in args.command. Het parsen van ["init", "catalog data.db"] levert args.command == "init" en args.database == "catalog data.db" op; parsen opent dat pad niet.

Herhaal commands.add_parser(...) voor elke andere opdracht en voeg daarna de argumenten van die opdracht toe aan de teruggegeven parser. Registratie heeft bijvoorbeeld dit ID-argument nodig:

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

De eerste string benoemt het Python-attribuut, args.member_id. metavar="ID" geeft het alleen een label in de helptekst. Gebruik voor de attributen kleine letters van de andere argumentnamen uit de syntaxis, zoals loan_id en checked_out_on. Elke opdracht heeft een eigen argument database nodig. De optionele --loan-limit wordt args.loan_limit; geef die type=positive_number en default=3.

Een converter ontvangt één argument als tekst. Die geeft de omgezette waarde terug of werpt argparse.ArgumentTypeError op met een bruikbare melding. Vang in de numerieke converter een mislukte omzetting met float(value) op en weiger waarden waarvoor math.isfinite(number) onwaar is of number <= 0. math.isfinite weigert oneindigheid en nan, die geen bruikbare uitleenlimieten zijn. De converter voor gehele getallen kan value.isascii() en value.isdigit() vereisen voordat die omzet en controleert dat het resultaat positief is.

Maak subopdrachten verplicht. Parse elk ID als een positief geheel getal in het tientallig stelsel. Parse een expliciete --loan-limit als een positieve eindige float; de standaardwaarde is het gehele getal 3. Waarden zoals 0, -2, nan en inf zijn parserfouten. De tekstvelden blijven voorlopig strings. Hun opdrachtfuncties verwijderen later de omringende witruimte wanneer meer opdrachten worden toegevoegd, zodat een lege waarde een precieze applicatiemelding kan krijgen.

main(argv=None) moet de meegegeven lijst parsen. Als argv gelijk is aan None, gebruikt de functie sys.argv[1:]. Vang de SystemExit van argparse niet op: help moet eindigen met status 0, terwijl een ontbrekende opdracht, onbekende opdracht, ontbrekend argument of ongeldige getypeerde waarde eindigt met status 2. Die uitkomsten ontstaan voordat de opdracht wordt uitgevoerd, dus kunnen ze geen applicatiestatus 1 worden.

Druk op Run. demo.py vangt de helpaanroep op, meldt de status en telt bestanden die op databases lijken in een lege tijdelijke map:

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

Er bestaat nog geen opdrachtfunctie, dus stopt Run daar. Een grondige parsercontrole probeert ook de volledige syntaxis met nieuwe waarden, inclusief een pad met spaties en Unicode. Die controleert alleen het parsen en vraagt een onvoltooide opdracht nooit om de database aan te raken.

Opdracht

Maak positive_integer, positive_number en build_parser in lending.py af voor de exacte syntaxis van init, import-items, register-member, checkout, return, available en activity.

Maak de subopdracht verplicht. Gebruik de converters voor elk ID en voor --loan-limit, waarvan de standaardwaarde het gehele getal 3 is. Houd main(argv=None) zonder bijwerkingen bij importeren, laat help en parserfouten van argparse hun oorspronkelijke SystemExit opwerpen en open geen database tijdens het parsen.