Skip to content

Commit 918e935

Browse files
committed
Add typing to supermarket kata
1 parent 8d60f43 commit 918e935

9 files changed

Lines changed: 79 additions & 58 deletions

File tree

src/supermarket_receipt/src/catalog.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1+
from src.model_objects import Product
2+
13

24
class SupermarketCatalog:
5+
def __init__(self):
6+
self.products: dict[str, Product] = {}
7+
self.prices: dict[str, float] = {}
38

4-
def add_product(self, product, price):
9+
def add_product(self, product: Product, price: float) -> None:
510
raise Exception("cannot be called from a unit test - it accesses the database")
611

7-
def unit_price(self, product):
12+
def unit_price(self, product: Product) -> float:
813
raise Exception("cannot be called from a unit test - it accesses the database")
9-

src/supermarket_receipt/src/model_objects.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
1+
from __future__ import annotations
12
from enum import Enum
23

34

45
class Product:
5-
def __init__(self, name, unit):
6+
def __init__(self, name, unit: ProductUnit):
67
self.name = name
78
self.unit = unit
89

910

1011
class ProductQuantity:
11-
def __init__(self, product, quantity):
12+
def __init__(self, product, quantity: float):
1213
self.product = product
1314
self.quantity = quantity
1415

@@ -24,15 +25,16 @@ class SpecialOfferType(Enum):
2425
TWO_FOR_AMOUNT = 3
2526
FIVE_FOR_AMOUNT = 4
2627

28+
2729
class Offer:
28-
def __init__(self, offer_type, product, argument):
30+
def __init__(self, offer_type: SpecialOfferType, product: Product, argument: float) -> None:
2931
self.offer_type = offer_type
3032
self.product = product
3133
self.argument = argument
3234

3335

3436
class Discount:
35-
def __init__(self, product, description, discount_amount):
37+
def __init__(self, product: Product, description: str, discount_amount: float) -> None:
3638
self.product = product
3739
self.description = description
3840
self.discount_amount = discount_amount
Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,37 @@
1+
from src.model_objects import Discount, Product
2+
13

24
class ReceiptItem:
3-
def __init__(self, product, quantity, price, total_price):
5+
def __init__(self, product: Product, quantity: float, price: float, total_price: float) -> None:
46
self.product = product
57
self.quantity = quantity
68
self.price = price
79
self.total_price = total_price
810

911

1012
class Receipt:
11-
def __init__(self):
12-
self._items = []
13-
self._discounts = []
13+
def __init__(self) -> None:
14+
self._items: list[ReceiptItem] = []
15+
self._discounts: list[Discount] = []
1416

15-
def total_price(self):
17+
def total_price(self) -> float:
1618
total = 0
1719
for item in self.items:
1820
total += item.total_price
1921
for discount in self.discounts:
2022
total += discount.discount_amount
2123
return total
2224

23-
def add_product(self, product, quantity, price, total_price):
25+
def add_product(self, product: Product, quantity: float, price: float, total_price: float) -> None:
2426
self._items.append(ReceiptItem(product, quantity, price, total_price))
2527

26-
def add_discount(self, discount):
28+
def add_discount(self, discount: Discount) -> None:
2729
self._discounts.append(discount)
2830

2931
@property
30-
def items(self):
32+
def items(self) -> list[ReceiptItem]:
3133
return self._items[:]
3234

3335
@property
34-
def discounts(self):
36+
def discounts(self) -> list[Discount]:
3537
return self._discounts[:]

src/supermarket_receipt/src/shopping_cart.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,38 @@
1+
from __future__ import annotations
12
import math
23

3-
from src.model_objects import ProductQuantity, SpecialOfferType, Discount
4+
from src.model_objects import Offer, Product, ProductQuantity, SpecialOfferType, Discount
5+
from src.receipt import Receipt
6+
from src.catalog import SupermarketCatalog
47

58

69
class ShoppingCart:
10+
_items: list[ProductQuantity]
11+
_product_quantities: dict[Product, float]
712

813
def __init__(self):
914
self._items = []
1015
self._product_quantities = {}
1116

1217
@property
13-
def items(self):
18+
def items(self) -> list[ProductQuantity]:
1419
return self._items
1520

16-
def add_item(self, product):
21+
def add_item(self, product: Product) -> None:
1722
self.add_item_quantity(product, 1.0)
1823

1924
@property
20-
def product_quantities(self):
25+
def product_quantities(self) -> dict[Product, float]:
2126
return self._product_quantities
2227

23-
def add_item_quantity(self, product, quantity):
28+
def add_item_quantity(self, product: Product, quantity: float) -> None:
2429
self._items.append(ProductQuantity(product, quantity))
2530
if product in self._product_quantities.keys():
2631
self._product_quantities[product] = self._product_quantities[product] + quantity
2732
else:
2833
self._product_quantities[product] = quantity
2934

30-
def handle_offers(self, receipt, offers, catalog):
35+
def handle_offers(self, receipt: Receipt, offers: dict[Product, Offer], catalog: SupermarketCatalog) -> None:
3136
for p in self._product_quantities.keys():
3237
quantity = self._product_quantities[p]
3338
if p in offers.keys():
Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
from src.model_objects import Offer
1+
from src.model_objects import Offer, Product, SpecialOfferType
2+
from src.catalog import SupermarketCatalog
23
from src.receipt import Receipt
4+
from src.shopping_cart import ShoppingCart
35

46

57
class Teller:
68

79
def __init__(self, catalog):
8-
self.catalog = catalog
9-
self.offers = {}
10+
self.catalog: SupermarketCatalog = catalog
11+
self.offers: dict[Product, Offer] = {}
1012

11-
def add_special_offer(self, offer_type, product, argument):
13+
def add_special_offer(self, offer_type: SpecialOfferType, product: Product, argument: float) -> None:
1214
self.offers[product] = Offer(offer_type, product, argument)
1315

14-
def checks_out_articles_from(self, the_cart):
16+
def checks_out_articles_from(self, the_cart: ShoppingCart) -> Receipt:
1517
receipt = Receipt()
1618
product_quantities = the_cart.items
1719
for pq in product_quantities:
@@ -25,5 +27,5 @@ def checks_out_articles_from(self, the_cart):
2527

2628
return receipt
2729

28-
def product_with_name(self, name):
29-
return self.catalog.products.get(name, None)
30+
def product_with_name(self, name: str):
31+
return self.catalog.products.get(name, None)

src/supermarket_receipt/tests/conftest.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,25 @@
77

88

99
@pytest.fixture
10-
def toothbrush():
10+
def toothbrush() -> Product:
1111
return Product("toothbrush", ProductUnit.EACH)
1212

1313

1414
@pytest.fixture
15-
def apples():
15+
def apples() -> Product:
1616
return Product("apples", ProductUnit.KILO)
1717

1818

1919
@pytest.fixture
20-
def catalog(toothbrush, apples):
20+
def catalog(toothbrush: Product, apples: Product) -> FakeCatalog:
2121
catalog = FakeCatalog()
2222
catalog.add_product(toothbrush, 0.99)
2323
catalog.add_product(apples, 1.99)
2424
return catalog
2525

2626

2727
@pytest.fixture
28-
def teller(catalog):
28+
def teller(catalog: FakeCatalog) -> Teller:
2929
return Teller(catalog)
3030

3131

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
from src.catalog import SupermarketCatalog
2+
from src.model_objects import Product
23

34

45
class FakeCatalog(SupermarketCatalog):
5-
def __init__(self):
6-
self.products = {}
7-
self.prices = {}
8-
9-
def add_product(self, product, price):
6+
def add_product(self, product: Product, price: float) -> None:
107
self.products[product.name] = product
118
self.prices[product.name] = price
129

13-
def unit_price(self, product):
10+
def unit_price(self, product: Product) -> float:
1411
return self.prices[product.name]
15-
Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
from src.model_objects import ProductUnit
2+
from src.receipt import Receipt, ReceiptItem
3+
24

35
class ReceiptPrinter:
46

5-
def __init__(self, columns=40):
7+
def __init__(self, columns: int = 40) -> None:
68
self.columns = columns
79

8-
def print_receipt(self, receipt):
10+
def print_receipt(self, receipt: Receipt) -> str:
911
result = ""
1012
for item in receipt.items:
1113
receipt_item = self.print_receipt_item(item)
@@ -19,15 +21,15 @@ def print_receipt(self, receipt):
1921
result += self.present_total(receipt)
2022
return str(result)
2123

22-
def print_receipt_item(self, item):
24+
def print_receipt_item(self, item: ReceiptItem) -> str:
2325
total_price_printed = self.print_price(item.total_price)
2426
name = item.product.name
2527
line = self.format_line_with_whitespace(name, total_price_printed)
2628
if item.quantity != 1:
2729
line += f" {self.print_price(item.price)} * {self.print_quantity(item)}\n"
2830
return line
2931

30-
def format_line_with_whitespace(self, name, value):
32+
def format_line_with_whitespace(self, name: str, value: str) -> str:
3133
line = name
3234
whitespace_size = self.columns - len(name) - len(value)
3335
for i in range(whitespace_size):
@@ -36,21 +38,21 @@ def format_line_with_whitespace(self, name, value):
3638
line += "\n"
3739
return line
3840

39-
def print_price(self, price):
41+
def print_price(self, price: float) -> str:
4042
return "%.2f" % price
4143

42-
def print_quantity(self, item):
44+
def print_quantity(self, item: ReceiptItem) -> str:
4345
if ProductUnit.EACH == item.product.unit:
4446
return str(item.quantity)
4547
else:
4648
return '%.3f' % item.quantity
4749

48-
def print_discount(self, discount):
50+
def print_discount(self, discount: Discount) -> str:
4951
name = f"{discount.description} ({discount.product.name})"
5052
value = self.print_price(discount.discount_amount)
5153
return self.format_line_with_whitespace(name, value)
5254

53-
def present_total(self, receipt):
55+
def present_total(self, receipt: Receipt) -> str:
5456
name = "Total: "
5557
value = self.print_price(receipt.total_price())
5658
return self.format_line_with_whitespace(name, value)

src/supermarket_receipt/tests/test_supermarket.py

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
import approvaltests
22
import pytest
33

4-
from src.model_objects import SpecialOfferType
4+
from src.model_objects import SpecialOfferType, Product
55
from tests.receipt_printer import ReceiptPrinter
66

7+
from src.teller import Teller
8+
from src.shopping_cart import ShoppingCart
79

8-
def test_empty_basket(teller, cart, toothbrush, apples):
10+
11+
def test_empty_basket(teller: Teller, cart: ShoppingCart, toothbrush: Product, apples: Product) -> None:
912
receipt = teller.checks_out_articles_from(cart)
1013

1114
approvaltests.verify(ReceiptPrinter().print_receipt(receipt))
1215

13-
def test_no_discount(teller, cart, toothbrush, apples):
16+
17+
def test_no_discount(teller: Teller, cart: ShoppingCart, toothbrush: Product, apples: Product) -> None:
1418
teller.add_special_offer(SpecialOfferType.TEN_PERCENT_DISCOUNT, toothbrush, 10.0)
1519
cart.add_item_quantity(apples, 2.5)
1620

@@ -19,7 +23,7 @@ def test_no_discount(teller, cart, toothbrush, apples):
1923
approvaltests.verify(ReceiptPrinter().print_receipt(receipt))
2024

2125

22-
def test_ten_percent_discount(teller, cart, toothbrush):
26+
def test_ten_percent_discount(teller: Teller, cart: ShoppingCart, toothbrush: Product) -> None:
2327
teller.add_special_offer(SpecialOfferType.TEN_PERCENT_DISCOUNT, toothbrush, 10.0)
2428
cart.add_item_quantity(toothbrush, 2)
2529

@@ -28,15 +32,16 @@ def test_ten_percent_discount(teller, cart, toothbrush):
2832
approvaltests.verify(ReceiptPrinter().print_receipt(receipt))
2933

3034

31-
def test_three_for_two_discount(teller, cart, toothbrush):
35+
def test_three_for_two_discount(teller: Teller, cart: ShoppingCart, toothbrush: Product) -> None:
3236
teller.add_special_offer(SpecialOfferType.THREE_FOR_TWO, toothbrush, 10.0)
3337
cart.add_item_quantity(toothbrush, 3)
3438

3539
receipt = teller.checks_out_articles_from(cart)
3640

3741
approvaltests.verify(ReceiptPrinter().print_receipt(receipt))
3842

39-
def test_three_for_two_discount_too_few(teller, cart, toothbrush):
43+
44+
def test_three_for_two_discount_too_few(teller: Teller, cart: ShoppingCart, toothbrush: Product) -> None:
4045
teller.add_special_offer(SpecialOfferType.THREE_FOR_TWO, toothbrush, 10.0)
4146
cart.add_item_quantity(toothbrush, 2)
4247

@@ -45,31 +50,34 @@ def test_three_for_two_discount_too_few(teller, cart, toothbrush):
4550
approvaltests.verify(ReceiptPrinter().print_receipt(receipt))
4651

4752

48-
def test_five_for_amount_discount(teller, cart, toothbrush):
53+
def test_five_for_amount_discount(teller: Teller, cart: ShoppingCart, toothbrush: Product) -> None:
4954
teller.add_special_offer(SpecialOfferType.FIVE_FOR_AMOUNT, toothbrush, 4.0)
5055
cart.add_item_quantity(toothbrush, 5)
5156

5257
receipt = teller.checks_out_articles_from(cart)
5358

5459
approvaltests.verify(ReceiptPrinter().print_receipt(receipt))
5560

56-
def test_five_for_amount_discount_bought_too_few(teller, cart, toothbrush):
61+
62+
def test_five_for_amount_discount_bought_too_few(teller: Teller, cart: ShoppingCart, toothbrush: Product) -> None:
5763
teller.add_special_offer(SpecialOfferType.FIVE_FOR_AMOUNT, toothbrush, 4.0)
5864
cart.add_item_quantity(toothbrush, 4)
5965

6066
receipt = teller.checks_out_articles_from(cart)
6167

6268
approvaltests.verify(ReceiptPrinter().print_receipt(receipt))
6369

64-
def test_two_for_amount_discount(teller, cart, toothbrush):
70+
71+
def test_two_for_amount_discount(teller: Teller, cart: ShoppingCart, toothbrush: Product) -> None:
6572
teller.add_special_offer(SpecialOfferType.TWO_FOR_AMOUNT, toothbrush, 1.80)
6673
cart.add_item_quantity(toothbrush, 5)
6774

6875
receipt = teller.checks_out_articles_from(cart)
6976

7077
approvaltests.verify(ReceiptPrinter().print_receipt(receipt))
7178

72-
def test_two_for_amount_discount_bought_too_few(teller, cart, toothbrush):
79+
80+
def test_two_for_amount_discount_bought_too_few(teller: Teller, cart: ShoppingCart, toothbrush: Product):
7381
teller.add_special_offer(SpecialOfferType.TWO_FOR_AMOUNT, toothbrush, 1.80)
7482
cart.add_item_quantity(toothbrush, 1)
7583

0 commit comments

Comments
 (0)