Lesson 20 · 20 of 20
Project: a tested inventory · Python
A small Python inventory that brings together classes, dataclasses and custom exceptions, checked with assert tests. Run it in DMarlon Studio.

This project pulls the course together. Product is a dataclass: three fields, one with a default, and a free __repr__ that you can see in the output. OutOfStock is a custom exception for the one error the inventory knows how to handle.
Inventory is an ordinary class. Its __init__ takes any number of products through *products and stores them in a dictionary keyed by sku with a comprehension. sell() looks up the product (an unknown code raises KeyError, a different error on purpose), checks the stock, takes the units off and returns the amount. value() adds up the value of the whole warehouse.
The second half is tests. `assert condition` does nothing when the condition is true and raises AssertionError when it is false, with the optional message after the comma. Each assert pins down one behaviour: the starting value is 955, selling 4 chairs charges 198 and leaves 6.
To test that something fails, try it inside a try: if sell("TABLE", 5) did not raise OutOfStock, the code would reach assert False and the test would fail. Since it does raise, the except catches it and prints the message. If the program gets to All tests passed, every check has held.
In real projects these tests live in test_... functions and are run by pytest, which uses these very same assert statements. Note that python -O switches assert off, so do not use it to validate data in production.
Code · main.py
from dataclasses import dataclassclass OutOfStock(Exception): pass # our own error for this program@dataclassclass Product: sku: str price: float stock: int = 0class Inventory: def __init__(self, *products): self.products = {p.sku: p for p in products} def sell(self, sku, qty): product = self.products[sku] # KeyError if the SKU is unknown if qty > product.stock: raise OutOfStock(f"only {product.stock} x {sku} left") product.stock -= qty return qty * product.price def value(self): return sum(p.price * p.stock for p in self.products.values())shop = Inventory(Product("CHAIR", 49.5, 10), Product("TABLE", 230.0, 2))assert shop.value() == 955.0 # a test: stops here if Falseassert shop.sell("CHAIR", 4) == 198.0assert shop.products["CHAIR"].stock == 6, "stock was not updated"try: shop.sell("TABLE", 5) assert False, "OutOfStock expected"except OutOfStock as error: print("Expected error:", error)print(shop.products["CHAIR"], "| value:", shop.value())print("All tests passed")Output · python3 main.py
Expected error: only 2 x TABLE left Product(sku='CHAIR', price=49.5, stock=6) | value: 757.0 All tests passed
Key points
- A dataclass for the data, an ordinary class for the behaviour.
- Custom exceptions for the errors your program can handle.
assert condition, "message"stops the program when something is off.- To test an error:
try, the call,assert False, thenexcept. - pytest runs
test_...functions with these sameassertstatements.
Exercise
Add a restock(sku, qty) method that rejects negative quantities with ValueError, and write two tests: one that restocks 3 tables and one that checks the error.