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 a Jupyter notebook 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.13 for your work. (Older versions may work, but your code will be checked with Python 3.13.) To use tiger, use the credentials you received. If you work remotely, make sure to download the .ipynb file to turn in. If you choose to work locally, Anaconda, miniforge, or uv are probably the easiest ways to install and manage Python. You can start JupyterLab either from the Navigator application (anaconda) or via the command-line as jupyter-lab or jupyter lab.

In this assignment, we will be implementing a collection of classes that work together to orchestrate a library. There will be media items, including books, movies, and albums, library copes of those items, library patrons, and the library itself. You will need to test your classes to make sure they work properly.

Due Date

The assignment is due at 11:59pm on November 7.

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. Remember to redefine your class (execute the cell with the definition) before testing it.

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. Media Classes (20 pts)

Create four classes related to the items your library will have: MediaItem, Book, Movie, and Album. A Book should have an id, title, year of release, author, and number of pages. A Movie should have an id, title, year of release, director, and duration in minutes. An Album should have an id, title, year of release, artist, and number of tracks. MediaItem should be the base class for these items. Add constructors and method(s) to support printing human-readable strings for these items. Important: You need to define these classes using inheritance for full credit with MediaItem as the base class. You should be able to create these items as follows:

book = Book(id=1927931, title="To Kill a Mockingbird", year=1960, author="Harper Lee", pages=281)
movie = Movie(id=2890123, title="Inception", year=2010, director="Christopher Nolan", duration=148)
album = Album(id=3948123, title="Abbey Road", year=1969, artist="The Beatles", tracks=17)
print('Item:', book) # prints "Item: To Kill a Mockingbird (1927931), Harper Lee, 1960, 281 pages"
print('Item:', movie) # prints "Item: Inception (2890123), Christopher Nolan, 2010, 148 minutes"
print('Item:', album) # prints "Item: Abbey Road (3948123), The Beatles, 1969, 17 tracks"
Hints:
  • Design the base class to minimize code duplication
  • 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

2. LibraryCopy Class (20 pts)

Create a LibraryCopy class to keep track of a copy of a MediaItem and whether it is checked out or not. The library may keep multiple copies of an item. It should have a constructor that takes a MediaItem object, copy number (copy_num), and return_date. The return_date should be initialized to None indicating it is available. Create a checkout method that takes the return date as an argument, and a checkin method that takes no argument but sets the return date to None. Also, create an available property that returns an item’s availability as a boolean. If an item is already checked out and checkout is called, raise an exception. Finally, add a method that returns a human-readable string with the media item’s information as well as whether it is checked out or not. For example:

mock1 = LibraryCopy(book, copy_num=1)
mock2 = LibraryCopy(book, copy_num=2)
mock2.checkout('2025-11-15')
mock2.checkout('2025-11-16') # raise exception that already checked out
print(mock2.available) # print False
print(mock1.available) # print True
print(mock1) # prints "To Kill a Mockingbird (1927931), Harper Lee, 1960, 281 pages [Copy 1, Available]"
print(mock2) # prints "To Kill a Mockingbird (1927931), Harper Lee, 1960, 281 pages [Copy 2, Checked Out, Due 2025-11-15]"
Hints:
  • Remember that you already have a method to output the MediaItem as a human-readable string!

3. Patron Classes (20 pts)

Create AdultPatron and ChildPatron classes that represents patrons of the library. A patron has a name, id number, and a collection of items currently checked out. An adult patron may have no more than ten (10) items checked out at a time; a child patron may have no more than five (5) items checked out at a time. Add methods checkout and checkin that take a LibraryCopy object and adds or removes, respectively, that item to the patron’s collection of checked out items. If a patron tries to check out more than the allowed number of items, raise an exception. Finally, add a method that returns a human-readable string with the patron’s name, id number, and all the items currently checked out. Important: You need to design these classes using inheritance to receive full credit.

Hints:
  • Think about whether the limit on checked out items should be an instance variable or a class variable.

4. Library Class (20 pts)

Create a Library class that keeps track of both the library copies as well as the patrons. It’s constructor should take a name, a list of items, and a list of patrons. It has an add_copy method that takes a LibraryCopy object and adds it to the library’s collection. It also has an add_patron method that takes a Patron object and adds it to the library’s list of patrons. Define a checkout method that takes a patron id number, a media item id, and a return date, and checks out a library copy of item to the patron, raising exceptions if the patron does not exist, the item does not exist, there are no copies available, the patron already checked out that media item, or the patron cannot check out any more items. Similarly, define a checkin method that takes a patron id number and a media item id and returns the item from the patron, raising exceptions if the patron does not exist, the item does not exist, or the patron does not have that item checked out. Finally, add a method that returns a human-readable string with all the items in the library and all the patrons.

Hints:
  • The checkout method needs to check all copies of the item to see if one is available.
  • The checkout method needs to set the return date on the item and add it to a patron’s checked out items.
  • Think about the best data structure to store the items and patrons in. One of the classes in the collections package may be useful here.

5. [CSCI 503 Only] Add/Remove Operators (15 pts)

Add support for the += and -= operators to the Library classes to add and remove items or patrons from the library. For example, the following code should work:

library = Library(name="Sycamore Library")
library += mock1
library += mock2
library += patron
library -= mock1

Note that we have not implemented removing patrons or copies in Part 4 so you will need to add this functionality!

Hints

  • Reuse existing functionality when possible

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.

  • MediaItem
    • <method for human-readable string>
  • Book
    • <method for human-readable string>
  • Movie
    • <method for human-readable string>
  • Album
    • <method for human-readable string>
  • LibraryCopy
    • checkout method
    • checkin method
    • available property
    • <method for human-readable string>
  • AdultPatron
    • checkout method
    • checkin method
    • <method for human-readable string>
  • ChildPatron
    • checkout method
    • checkin method
    • <method for human-readable string>
  • Library
    • add_copy
    • add_patron
    • checkout
    • checkin
    • <method for human-readable string>
    • [CSCI 503] remove_copy method
    • [CSCI 503] remove_patron method
    • [CSCI 503] += operator
    • [CSCI 503] -= operator

The following code should help you test your work (Updated 2025-10-29):

book = Book(id=1927931, title="To Kill a Mockingbird", year=1960, author="Harper Lee", pages=281)
movie = Movie(id=2890123, title="Inception", year=2010, director="Christopher Nolan", duration=148)
movie2 = Movie(id=6739021, title="Lady Bird", year=2017, director="Greta Gerwig", duration=94)
album = Album(id=3948123, title="Abbey Road", year=1969, artist="The Beatles", tracks=17)
mock1 = LibraryCopy(book, copy_num=1)
mock2 = LibraryCopy(book, copy_num=2)
incep = LibraryCopy(movie, copy_num=1)
lb1 = LibraryCopy(movie2, copy_num=1)
lb2 = LibraryCopy(movie2, copy_num=2)
beatles = LibraryCopy(album, copy_num=1)
ananya = AdultPatron("Ananya Reddy", id=347)
rafael = ChildPatron("Rafael Lopez", id=651)
library = Library(name="Sycamore Library", items=[mock1, mock2, incep, lb1, lb2, beatles], patrons=[ananya, rafael])
library.checkout(347, 6739021, '2025-11-08')
library.checkout(347, 3948123, '2025-11-15')
library.checkout(651, 6739021, '2025-11-09')
library.checkout(651, 3948123, '2025-11-16') # raises exception

library.checkin(347, 3948123)
library.checkout(651, 3948123, '2025-11-17')

print(library)
print(ananya)
print(rafael)

which outputs something similar to:

Sycamore Library
 Patrons:
  Patron: Ananya Reddy (347), Checked out items: 
  Lady Bird (6739021), Greta Gerwig, 2017, 94 min [Copy 1, Checked Out, Due 2025-11-08]
  Patron: Rafael Lopez (651), Checked out items: 
  Lady Bird (6739021), Greta Gerwig, 2017, 94 min [Copy 2, Checked Out, Due 2025-11-09]
  Abbey Road (3948123), The Beatles, 1969, 17 tracks [Copy 1, Checked Out, Due 2025-11-17]
 Items:
  To Kill a Mockingbird (1927931), Harper Lee, 1960, 281 pages [Copy 1, Available]
  To Kill a Mockingbird (1927931), Harper Lee, 1960, 281 pages [Copy 2, Available]
  Inception (2890123), Christopher Nolan, 2010, 148 min [Copy 1, Available]
  Lady Bird (6739021), Greta Gerwig, 2017, 94 min [Copy 1, Checked Out, Due 2025-11-08]
  Lady Bird (6739021), Greta Gerwig, 2017, 94 min [Copy 2, Checked Out, Due 2025-11-09]
  Abbey Road (3948123), The Beatles, 1969, 17 tracks [Copy 1, Checked Out, Due 2025-11-17]
Ananya Reddy (347), Checked out items: 
  Lady Bird (6739021), Greta Gerwig, 2017, 94 min [Copy 1, Checked Out, Due 2025-11-08]
Rafael Lopez (651), Checked out items: 
  Lady Bird (6739021), Greta Gerwig, 2017, 94 min [Copy 2, Checked Out, Due 2025-11-09]
  Abbey Road (3948123), The Beatles, 1969, 17 tracks [Copy 1, Checked Out, Due 2025-11-17]

You should also consider testing the exceptions. For example,

for test_album_num in range(10):
    test_album = Album(id=test_album_num, title=f"Test Album {i}", year=2025, artist="Me", tracks=67)
    test_copy = LibraryCopy(test_album, copy_num=1)
    library.add_item(test_copy)
  
for test_album_num in range(10):
    print("Checking out", test_album_num)
    library.checkout(347, test_album_num, "2025-11-20")
    # raises exception on test_ablum 9

for test_album_num in range(10):
    print("Checking in", test_album_num)
    library.checkin(347, test_album_num)

for test_album_num in range(10):
    print("Checking out", test_album_num)
    library.checkout(651, test_album_num, "2025-11-21") 
    # raises exception on test_album 3

Extra Credit

  • CSCI 490 Students may do Part 5 for extra credit
  • Implement Part 1 using dataclasses (10 pts)
  • Add an overdue property to LibraryCopy using the datetime library and its now method and incorporate this into the human readable string (15 pts)