3. Adding, editing and deleting products

Lines 28–48

The next three methods change the products dictionary. All three follow the same pattern: validate the input and stop with raise ValueError if something is wrong, make the change, then call save to write everything to disk.

Code for this part

shop_manager.py, lines 28–48
28    def add(self, name, buy, sell, qty):29        if not name or name in self.products:30            raise ValueError('نام خالی یا کالای تکراری است')31        if not all(0 <= x < float('inf') for x in (buy, sell, qty)):32            raise ValueError('عدد منفی یا نامعتبر مجاز نیست')33        self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}34        self.save()35 36    def edit(self, name, buy, sell, qty):37        if name not in self.products:38            raise ValueError('کالا پیدا نشد')39        if not all(0 <= x < float('inf') for x in (buy, sell, qty)):40            raise ValueError('عدد منفی یا نامعتبر مجاز نیست')41        self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}42        self.save()43 44    def delete(self, name):45        if name not in self.products:46            raise ValueError('کالا پیدا نشد')47        del self.products[name]48        self.save()

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 28def add(self, name, buy, sell, qty):
add takes four arguments: name, purchase price, sale price and quantity. It is called from the module-level function add (line 94).
line 29if not name or name in self.products:
Two conditions joined by or: the name is empty, or it is already in the dictionary. An empty string is falsy in Python, so not name is True for an empty name.
line 30raise ValueError('نام خالی یا کالای تکراری است')
Raises a ValueError whose message says the name is empty or already used. raise ends the method right here, so nothing is added. The error is caught in run (line 88) and shown to the user.
line 31if not all(0 <= x < float('inf') for x in (buy, sell, qty)):
all is True only if the condition holds for every item. Each of the three numbers must lie between zero and infinity: 0 <= x < float('inf'). This rejects negative numbers, infinity (inf) and not-a-number (nan). float happily converts the strings nan and inf, and every comparison with nan is false, so 0 <= nan fails and the value is rejected. not flips the result: if any value is invalid, raise an error.
line 32raise ValueError('عدد منفی یا نامعتبر مجاز نیست')
Raises the error “negative or invalid numbers are not allowed”.
line 33self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}
If everything is valid, stores the product under its name, with a three-item dictionary as the value.
line 34self.save()
Saves the change to disk immediately (line 14).
line 35
A blank line.
line 36def edit(self, name, buy, sell, qty):
edit takes the same four arguments and is called from the module-level function edit (line 97).
line 37if name not in self.products:
The opposite of add: here the product must already exist.
line 38raise ValueError('کالا پیدا نشد')
Raises “product not found”.
line 39if not all(0 <= x < float('inf') for x in (buy, sell, qty)):
The same number check as line 31.
line 40raise ValueError('عدد منفی یا نامعتبر مجاز نیست')
Raises “negative or invalid numbers are not allowed”.
line 41self.products[name] = {'buy': buy, 'sell': sell, 'qty': qty}
Replaces the product's whole dictionary with the new values. Because the name is the dictionary key, this method cannot rename a product: if you change the name in the form, line 37 reports that the product was not found.
line 42self.save()
Saves to disk.
line 43
A blank line.
line 44def delete(self, name):
delete takes only the name. It is called from the module-level function delete (line 100).
line 45if name not in self.products:
If the product does not exist…
line 46raise ValueError('کالا پیدا نشد')
…raise “product not found”.
line 47del self.products[name]
del removes the key and its value from the dictionary. Past sales of this product in self.sales are left untouched, so the sales report does not change after a deletion.
line 48self.save()
Saves to disk.

Note

add and edit are almost identical; only the first check differs (the product must not exist / must exist). The number check (line 31 and line 39) is duplicated and could live in a helper method. An earlier version used if min(buy, sell, qty) < 0:, which accepted nan and inf: typing nan as a price stored NaN in the file, which is not valid standard JSON.

How it connects to the rest of the code

See also

Shop.add
The “add product” button: button line 134 → run line 84 → function add line 93 → shop.add(*values()) line 94.
Shop.edit
The “edit product” button: function edit line 96 → line 97.
Shop.delete
The “delete product” button: function delete line 99 → line 100. Not to be confused with table.delete on line 115, which only removes a table row.
ValueError
Every raise in this part is caught on line 88 and shown with messagebox.showerror on line 89.
values()
Returns name, buy, sell, qty in exactly the order of the parameters of add and edit (line 82).

Check your understanding

Try to answer each question before opening it.

  1. Why does not name on line 29 catch an empty name?

    Show answer

    An empty string is falsy, so not '' is True.

  2. The user types nan as the purchase price. Which line stops it, and why?

    Show answer

    line 31: every comparison with nan is false, so 0 <= nan is false, all(...) is false and not all(...) raises the error on line 32.

  3. Can edit rename a product?

    Show answer

    No. The name is the dictionary key; a new name is not found on line 37, so edit raises “product not found”.

  4. After a product is deleted, does the sales report change?

    Show answer

    No. line 47 only removes it from self.products; its sales stay in self.sales.