Assignment 6

Goals

The goal of this assignment is to work with object-oriented programming and exceptions in Python.

Instructions

You will be doing your work in Python for this assignment. You may choose to work on this assignment on a hosted environment (e.g. tiger) or on your own local installation of Jupyter and Python. You should use Python 3.9 or higher for your work. To use tiger, use the credentials you received. If you work remotely, make sure to download the .py files to turn in. If you choose to work locally, Anaconda is the easiest way to install and manage Python. If you work locally, you may launch Jupyter Lab either from the Navigator application or via the command-line as jupyter-lab.

In this assignment, we will be implementing a collection of classes that work together to orchestrate an online market. There will be products, including both grocery and household, the inventory, and a customer’s shopping cart. You will need to test your classes to make sure they work properly.

Due Date

The assignment is due at 11:59pm on Wednesday, April 20.

Submission

You should submit the completed notebook file required for this assignment on Blackboard. The filename of the notebook should be a6.ipynb.

Details

Please make sure to follow instructions to receive full credit. Because you will be writing classes and adding to them, you do not need to separate each part of the assignment. Please document any shortcomings with your code. You may put the code for each part into one or more cells.

0. Name & Z-ID (5 pts)

The first cell of your notebook should be a markdown cell with a line for your name and a line for your Z-ID. If you wish to add other information (the assignment name, a description of the assignment), you may do so after these two lines.

1. Product Classes (20 pts)

Create four classes related to the products your online store will sell: StoreItem, HouseholdItem, GroceryItem and BulkItem.

StoreItem is the base class which has a name, SKU and price. The sku, which stands for "stock keeping unit", is a unique identifier for each item.

GroceryItem subclasses from StoreItem and has a nutrition field that defaults to an empty dictionary.

HouseholdItem also is a subclass of StoreItem but has a brand name.

BulkItem subclasses from GroceryItem and adds a unit field (lbs, kg, ml, oz).

Add constructors and method(s) to support printing human-readable strings for these items. Note that the bulk items should display their units with the strings.

Think about which class to put the method(s) in. For example,

cereal = GroceryItem("Cheerios", 38942, 3.99, {'calories': 200, 'fat': 0})
bananas = BulkItem("Bananas", 4011, 0.59, "lb")
print(f"Two items: {cereal}, {bananas}")

prints "Two items: Cheerios (38942) @ 3.99, Bananas (4011) @ 0.59/lb"

Hints:
  • Make use of the base class’s constructor. Don’t rewrite what it is already doing
  • Remember which of the dunder methods we should use for human-readable strings
  • You can override methods in subclasses

2. Inventory Class (20 pts)

Create an Inventory class that stores both the items the store carries and the amount of each item that the store has in stock.

It should have add_items and remove_items methods that take a StoreItem object and an amount and adds or removes, respectively, that to the current inventory.

In addition, add a find_item method that takes a SKU and returns the StoreItem object with that SKU.

It should also have a method has_enough that given an item and amount, returns a boolean that indicates whether the inventory has more than that that amount.

Finally, add a method that again returns a human-readable string with all the items in the inventory and their amounts.

Hints:
  • It will be probably be useful to be able to refer to items and amounts by SKU so set up the correct objects to store the data
  • Remember the dunder methods when supporting operators.
  • Use the methods in Part 1 to help with the dunder method here.

3. ShoppingCart Class (20 pts)

Create a ShoppingCart class that tracks the items a customer wishes to buy. It should store products and the amount of each item in the cart.

It should have add_items and remove_items methods that take a StoreItem object and an amount and adds or removes, respectively, that to the current cart.

Also, add a method that returns a human-readable string with all the items in the cart and their amounts.

In addition, add support for += and -= operators that will add or remove a single (1) item (or unit) from the cart.

Finally, add a cost method that computes the total cost for all of the items in the cart.

Important: Think about the similarities between classes and whether you can use OOP concepts to minimize your work for this class.

Hints:
  • Find the correct dunder methods to support incremental addition and subtraction

4. Store Class (20 pts)

The Store class will keep track of both the inventory and the customers shopping.

To that end, it has a get_cart method that returns a new ShoppingCart object and keeps track of it.

Define a MAX_CARTS class constant that specifies the ten (10) as the maximum number of carts in the store, and raise an exception if too many carts are in use.

In addition, write a checkout method that checks the inventory to make sure the number of items being bought in the shopping cart are available and then deducts the quantities selected in the shopping cart from the inventory.

If there is not enough left in the inventory, the checkout method should raise an exception alerting that about which item(s) are not in stock. checkout should return the cost of all the items bought and remove the cart from the store.

Hints:

  • Remember where class variables are defined
  • Make sure to put a helpful error message for each exception

5. [CS503 Only] Exceptions (15 pts)

We should add more exceptions to our classes to avoid particular issues. Here is the list of exceptions to raise and the circumstances:

  • Raise an exception if a user tries to add or remove a non-integer amount of an item that is not a BulkItem. Bulk items do allow fractional amounts!
  • Raise a KeyError in find_item if the SKU doesn’t exist
  • Raise an exception in remove_items if more items are removed from the inventory than exist.

6. Summary and Testing

The final list of classes and methods to be added. Note that all classes should have constructors that properly initialize objects, and the instance fields are not listed here.

  • StoreItem
    • <method for human-readable string>
  • HouseholdItem
    • <method for human-readable string>
  • GroceryItem
    • <method for human-readable string>
  • BulkItem
    • <method for human-readable string>
  • Inventory
    • add_items
    • remove_items
    • find_item
    • has_enough
    • <method for human-readable string>
  • ShoppingCart
    • add_items
    • remove_items
    • += (operator)
    • -= (operator)
    • <method for human-readable string>
  • Store
    • get_cart
    • checkout

The following code should help you test your work:

store = Store()
store.inventory.add_items(GroceryItem("Cheerios", 38942, 3.99, {'calories': 200, 'fat': 0}), 10)
store.inventory.add_items(HouseholdItem("Trash Bags", 38902, 5.99, "Hefty"), 30)
store.inventory.add_items(BulkItem("Bananas", 4011, 0.59, "lb"), 20)
store.inventory.add_items(BulkItem("Lettuce", 3982, 2.99, "head"), 25)
store.inventory.add_items(GroceryItem("Oreos", 27894, 3.29), 1)

print("Inventory:")
print(store.inventory)

my_cart = store.get_cart()
my_cart.add_items(store.inventory.find_item(4011), 3)
my_cart.add_items(store.inventory.find_item(38902))
my_cart.add_items(store.inventory.find_item(27894), 1)
print("Cart:")
print(my_cart)

total_cost = store.checkout(my_cart)
print(f"You spent ${total_cost}")

print("Updated Inventory:")
print(store.inventory)

which outputs something similar to:

Inventory:
10 Cheerios (38942) @ 3.99
30 Trash Bags (38902) @ 5.99
20 Bananas (4011) @ 0.59/lb
25 Lettuce (3982) @ 2.99/head
 1 Oreos (27894) @ 3.29
Cart:
 3 Bananas (4011) @ 0.59/lb
 1 Trash Bags (38902) @ 5.99
 1 Oreos (27894) @ 3.29
You spent $11.05
Updated Inventory:
10 Cheerios (38942) @ 3.99
29 Trash Bags (38902) @ 5.99
17 Bananas (4011) @ 0.59/lb
25 Lettuce (3982) @ 2.99/head
 0 Oreos (27894) @ 3.29

This code should raise an exception (too many carts):

for i in range(20):
    store.get_cart()

For CSCI 503 students, the following can be used to test Part 5:

my_cart = store.get_cart()
my_cart.add_items(store.inventory.find_item(4013), 3) # raises your  exception
my_cart.add_items(store.inventory.find_item(38902), 1.5) # raises your exception
store.inventory.remove_items(store.inventory.find_item(27894), 3) # raises your exception

Extra Credit

  • [15 pts] CSCI 490 Students may do Part 5 for extra credit
  • [10 pts] Add support for transferring items from one shopping cart to another