Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/corner-grocer/llms.txt
Use this file to discover all available pages before exploring further.
db_handler.py is the database access layer for Corner Grocer. It isolates every SQL statement and connection detail behind seven named functions so the rest of the application never constructs a query string or handles a raw cursor. This page explains when and why each function is called during a normal application run — for the full parameter-by-parameter API reference, see the db_handler reference.
How the module fits into the app
The application follows a clear division of responsibility:db_handler.py owns all database I/O, while corner_grocer_main.py owns the menu logic. CornerGrocerApp (defined in corner_grocer_main.py) calls the module’s functions in sequence at startup, during file ingestion, and in response to each menu selection.
corner_grocer_main.py itself imports only the six functions it needs at runtime; clear_all_data is available for maintenance scripts or interactive resets but is not called from the main menu loop.
Startup sequence
WhenCornerGrocerApp.__init__ runs it calls two functions back to back:
connect_to_db either opens the existing grocery.db file or lets SQLite create it from scratch — either way you get back a live sqlite3.Connection. create_table immediately follows and issues CREATE TABLE IF NOT EXISTS, so the schema is guaranteed to be in place before any reads or writes happen. On a first run both calls do real work; on every subsequent run connect_to_db just opens the file and create_table is a safe no-op.
Writing data: the upsert pattern
Every item that enters the database goes throughinsert_or_update_item. The function performs a manual upsert: it queries for an existing row, then either increments the stored frequency or inserts a fresh row with frequency = 1.
CornerGrocerApp.add_item strips whitespace and lowercases the item name before passing it here, so all stored keys are consistently lowercase. The function commits after every call, which means a crash mid-file leaves the database in a consistent (though incomplete) state rather than rolling back everything.
Reading data: three query patterns
Three retrieval functions cover the different views the menu needs:get_all_frequencies returns every item sorted A–Z. It backs menu options 2 (print list), 3 (print histogram), 4 (sort alphabetically), 6 (search), and 7 (save list to file).
get_frequency_for_item looks up one item by name and returns its count, or 0 if the item is not found. Menu option 1 uses this for the single-item lookup. The 0 sentinel means callers never have to check for None or catch an exception.
get_frequencies_sorted_by_count orders items by descending frequency, breaking ties alphabetically. Menu options 5 (sort by frequency) and 8 (save histogram to file) use this to produce the ranked view.
Resetting the database
Two approaches exist for clearing stored data, depending on whether you want to remove the file entirely or keep the schema in place. Delete the file — the cleanest reset; the next run recreates everything from scratch:clear_all_data executes DELETE FROM grocery_frequency and commits. The table structure is preserved and connect_to_db / create_table will not need to be called again before the next write — the table already exists, just empty.
Error handling philosophy
Every function indb_handler.py catches sqlite3.Error internally and either returns a safe fallback value (None, 0, or {}) or calls sys.exit(1) for unrecoverable failures. The table below summarises the failure behaviour:
| Function | On error |
|---|---|
connect_to_db | Returns None; caller must check before proceeding |
create_table | Prints error and calls sys.exit(1) — cannot continue without a valid schema |
insert_or_update_item | Prints error and returns; the failed item is silently skipped |
get_all_frequencies | Returns {} |
get_frequency_for_item | Returns 0 |
get_frequencies_sorted_by_count | Returns {} |
clear_all_data | Prints error and returns without committing |
create_table (which exits) and the read/write functions (which return fallbacks) is intentional: if the schema cannot be created the application has no usable state, whereas a failed read or skipped write is a degraded-but-recoverable condition.