6. Reading the form, run() and the button handlers

Lines 77–111

This part is the bridge between the window and the Shop class. values converts the form text into numbers, run executes any action safely and reports errors, and six short functions each do the work of one button.

Code for this part

shop_manager.py, lines 77–111
77def values():78    name = fields['name'].get().strip()79    buy = float(fields['buy'].get())80    sell = float(fields['sell'].get())81    qty = int(fields['qty'].get())82    return name, buy, sell, qty83 84def run(action):85    try:86        action()87        if action is not search: refresh()88    except (ValueError, TypeError) as exc:89        messagebox.showerror('خطا', str(exc))90    except OSError as exc:91        messagebox.showerror('خطای فایل', str(exc))92 93def add():94    shop.add(*values())95 96def edit():97    shop.edit(*values())98 99def delete():100    shop.delete(fields['name'].get().strip())101 102def sell():103    shop.sell(fields['name'].get().strip(), int(fields['qty'].get()))104 105def search():106    name = fields['name'].get().strip().lower()107    refresh(name)108 109def report():110    count, income = shop.report()111    messagebox.showinfo('گزارش فروش', f'تعداد فروخته‌شده: {count}\nدرآمد کل فروش: {income:,.0f}')

Click a line number to jump to its explanation; click “line …” in an explanation to highlight that line in the code.

Line by line

line 77def values():
values reads the four form fields and returns them with the right types.
line 78name = fields['name'].get().strip()
get() returns the text in the name box and strip() removes leading and trailing spaces, so "pen " and "pen" count as the same name.
line 79buy = float(fields['buy'].get())
Converts the purchase price to a float. If the text is not a number (or is empty), a ValueError is raised and caught in run (line 88).
line 80sell = float(fields['sell'].get())
The same for the sale price.
line 81qty = int(fields['qty'].get())
The quantity is converted with int, so 2.5 is rejected.
line 82return name, buy, sell, qty
Returns the four values as a tuple, in exactly the order of the parameters of Shop.add (line 28) and Shop.edit (line 36).
line 83
A blank line.
line 84def run(action):
The central run function. Its argument action is itself a function — in Python a function can be passed around like any other value. Every button goes through run (line 134).
line 85try:
Starts a try block.
line 86action()
Calls the function it received, for example add().
line 87if action is not search: refresh()
If the action finished without an error, refreshes the table with the current data (line 113); if an error occurred, this line never runs. is not checks that action is not the search function (line 105): search has already filled the table with the filtered rows, and an unfiltered refresh() here would wipe out the search result. An if may share its line with a single statement.
line 88except (ValueError, TypeError) as exc:
Catches input and logic errors: every raise ValueError in the class and the conversion errors on line 79–line 81. as exc binds the exception object to exc.
line 89messagebox.showerror('خطا', str(exc))
Shows an error dialog. str(exc) is the message that was given to raise.
line 90except OSError as exc:
Catches file errors, for example when save (line 15) is not allowed to write.
line 91messagebox.showerror('خطای فایل', str(exc))
Shows an error dialog titled as a file error.
line 92
A blank line.
line 93def add():
Handler for the “add product” button. It shares its name with Shop.add, but they don't clash: one is a global function, the other an attribute of the shop object.
line 94shop.add(*values())
values() returns a four-item tuple and the star * unpacks it into four separate arguments for shop.add — the same as shop.add(name, buy, sell, qty).
line 95
A blank line.
line 96def edit():
Handler for the “edit product” button.
line 97shop.edit(*values())
The same pattern, with shop.edit.
line 98
A blank line.
line 99def delete():
Handler for the “delete product” button.
line 100shop.delete(fields['name'].get().strip())
Deleting only needs the name, so the name box is read directly instead of calling values(). That is why empty price fields don't cause an error here.
line 101
A blank line.
line 102def sell():
Handler for the “record sale” button.
line 103shop.sell(fields['name'].get().strip(), int(fields['qty'].get()))
Reads the name and the quantity and passes them to shop.sell. Note that the quantity box is used both for stock and for the number of items sold.
line 104
A blank line.
line 105def search():
Handler for the “search” button.
line 106name = fields['name'].get().strip().lower()
Reads the name box and lower-cases it with lower(), so searches for Latin-script names ignore case.
line 107refresh(name)
Fills the table with only the products whose name contains this text (line 113).
line 108
A blank line.
line 109def report():
Handler for the “sales report” button.
line 110count, income = shop.report()
shop.report() returns a two-item tuple (line 63), unpacked directly into count and income.
line 111messagebox.showinfo('گزارش فروش', f'تعداد فروخته‌شده: {count}\nدرآمد کل فروش: {income:,.0f}')
Shows an information dialog built with an f-string. \n starts a new line and {income:,.0f} formats the number with thousands separators and no decimals, for example 80,000,000,000.

Note

In an earlier version line 87 was a plain refresh(), and the search button did nothing visible: search() filtered the table (line 107), then the unfiltered refresh() immediately brought every product back. The if action is not search condition fixes this. It is written on the same line so that the line numbers below it stay the same.

How it connects to the rest of the code

See also

values()
Used in add line 94 and edit line 97.
run(action)
Used on line 134 for all seven buttons. It calls refresh itself on line 87, except for the search button.
add, edit, delete, sell, search, report
Paired with the button labels in the buttons list (line 129 and line 130).
Class methods
add → Shop.add line 28, edit → Shop.edit line 36, delete → Shop.delete line 44, sell → Shop.sell line 50, report → Shop.report line 62.
messagebox
Imported on line 4.

Check your understanding

Try to answer each question before opening it.

  1. What does the * do in shop.add(*values())?

    Show answer

    It unpacks the four-item tuple into four separate arguments: shop.add(name, buy, sell, qty).

  2. The user types abc as the price and clicks “add”. Follow what happens.

    Show answer

    line 79 raises ValueError, line 87 is skipped, line 88 catches the error and line 89 shows it. Nothing is added.

  3. Why doesn't delete call values()?

    Show answer

    Deleting only needs the name; values() would fail on empty price fields.

  4. What would the search button do without the condition on line 87?

    Show answer

    Nothing visible: search filters the table, then refresh() immediately shows every product again.

  5. What does {income:,.0f} print for 1234567.8?

    Show answer

    1,234,568 — thousands separators, rounded to no decimals.