0%

Capstone: A Persistent Lending Tool · practice

Initialize the Catalog Explicitly

A missing catalog file should be created only because somebody asked for init. If available contains a typo, or --help was requested, the database path must remain untouched. lending.py now needs its first dispatch branch and its first command .

Run will make this exact call with a path inside a temporary directory:

database_path = temporary_directory / "catalog.db"
main(["init", str(database_path)])

After argparse succeeds, the namespace contains args.command == "init" and args.database == str(database_path). Add setup_error_is_documented(error) exactly as shown, then add init_command(args) and the explicit args.command == "init" branch in main:

def setup_error_is_documented(error):
    message = str(error)
    if message == "Database has tables but no schema version.":
        return True
    if message.startswith("Schema version "):
        if message.endswith(" is not supported."):
            return True
        if message.endswith(" does not match the expected tables and columns."):
            return True
    return False


if args.command == "init":
    return init_command(args)

Keep parsing above the branch. This order is what prevents help and parser errors from reaching database setup.

Inside init_command, pass args.database to prepare_database. That one call already owns the full compatibility matrix: it creates a missing or blank version 2 database, migrates an exact version 1 database, leaves exact version 2 unchanged, and refuses every unsupported or marker-shape disagreement without changing it. Do not copy that logic into lending.py.

Only a accepted by setup_error_is_documented is an expected application refusal here. Catch ValueError, re-raise it when the function returns False, and otherwise pass it to report_error for one stderr line and status 1. This keeps an unrelated ValueError, OperationalError, ProgrammingError, or TypeError visible with its original type. Printing success before prepare_database returns would lie when setup later fails, so print only afterward:

Database ready (schema version 2).

Return 0 after the line is written. The function does not open another connection merely to inspect the version. prepare_database has already verified the result and closed the setup connection it owns.

Press Run. demo.py calls main(["init", str(database_path)]), captures the returned status and both streams, then opens the temporary file only long enough to read its stored marker:

== init ==
Status: 0
stdout: Database ready (schema version 2).
stderr: <empty>
Schema version after reopening: 2

A thorough check repeats the call with missing, exact version 1, exact version 2, and incompatible databases. It also injects an unrelated database failure. Expected compatibility messages become status 1; the unrelated failure must still surface for repair.

Task

Add the exact setup_error_is_documented(error) from the instructions, add or complete init_command(args), and add this branch in main:

if args.command == "init":
    return init_command(args)

Call prepare_database(args.database). On success, print Database ready (schema version 2). and return 0. Report only a accepted by the classifier; re-raise every other one. Do not create or open a database before argparse selects this command.