CIT 590/591 Assignment 5: Library
Fall 2012, David Matuszek

Purpose of this assignment

General idea of the assignment

You are going to implement software for your local library. The user of the software will be the librarian, who uses your program to issue library cards, check books out to patrons, check books back in, send out overdue notices, and open and close the library.

Library rules:

Details

In a number of methods, we will make a distinction between what the program appears to do (its behavior), and what it actually does (its implementation). For example, when we "issue a library card" to a patron, we don't create some kind of a "library_card object" and give it to a patron object; the implementation will achieve the same result by simply recording the patron's name in a dictionary. The visible behavior is the same: A person who has been "issued a library card" will be able to check out books. Don't let yourself be confused by the terminology.

You should not need any additional classes, but if you would like additional methods, feel free to write (and test!) them.

Classes and methods

Create the following classes. Each class should be on a separate file, named after the class but lowercase: The Calendar class should be on a file named calendar.py, the Patron class on a file named patron.py, etc. Test classes should be named TestCalendar, TestPatron, etc., and should be on files named test_calendar.py, test_patron.py, etc. All classes should be defined in a file named library.py, and all test classes should be on a separate file named library_test.py.

class Calendar(object)
We need to deal with the passage of time, so we need to keep track of dates. Python has a perfectly good date class, but to keep things simple, we will just use an integer to keep track of how many days have passed. This class needs only a couple of methods:
__init__(self)
The constructor. Sets the date to 0. You should create one and only one Calendar object; time is the same for everyone.
get_date(self)
Returns (as an integer) the current date.
advance(self)
Increment the date (move ahead to the next day), and returns the new date.
class Book(object)
A book has these attributes (instance variables): id, title, author (only one author per book), and due_date. The due date is None if the book is not checked out.
__init__(self, id, title, author)
The constructor. Saves the provided information. When created, the book is not checked out.
get_id(self)
Returns this book's unique identification number.
get_title(self)
Returns this book's title.
get_author(self)
Returns this book's author.
get_due_date(self)
Returns the date (as an integer) that this book is due.
check_out(self, due_date)
Sets the due date of this Book. Doesn't return anything.
check_in(self)
Sets the due date of this Book to None. Doesn't return anything.
__str__(self)
Returns a string of the form "id: title, by author".
class Patron(object)
A patron is a "customer" of the library. A patron must have a library card in order to check out books. A patron with a card may have no more than three books checked out at any time.
__init__(self, name, library)
Constructs a patron with the given name, and no books. The patron must also have a reference to the Library object that he/she uses.
get_name(self)
Returns this patron's name.
check_out(self, book)
Adds this Book object to the set of books checked out by this patron.
give_back(self, book)
Removes this Book object from the set of books checked out by this patron. (Since patrons are usually said to "return" books, I wish I could name this method return, but I hope you can see why I can't do that.)
get_books(self)
Returns the set of Book objects checked out to this patron (may be the empty set, set([])).
send_overdue_notice(self, notice)
Tells this patron that he/she has overdue books. (What the method actually does is just print out this patron's name along with the notice.)
class Library(object)
This is the "master" class. It has a number of methods which are called by the "librarian" (the user), not by patrons. The librarian does all the work. Since these methods (other than the constructor) will be typed in by the librarian, all the parameters must be strings or numbers, not objects (how would you type in a Book object?). Furthermore, to reassure the librarian that his/her action has worked, all these methods should return a result, for example, "Library card issued to Amy Gutmann."
__init__(self)
Constructs the Library object (you should create exactly one of these).
  • Read in, from a file named collection.txt, a list of (title, author) tuples, Create a Book from each tuple, and save these books in some appropriate data structure of your choice. Give each book a unique id number (starting from 1, not 0). You may have many copies of the "same" book (same title and author), but each will have its own id.
  • Create a Calendar object (you should create exactly one of these).
  • Define an empty dictionary of patrons. The keys will be the names of patrons and the values will be the corresponding Patron objects.
  • Set a flag variable to indicate that the library is not open.
  • Sets the current patron (the one being served) to None
open(self)
If the library is already open, raises an Exception with the message "The library is already open!". Otherwise, starts the day by advancing the Calendar, and setting the flag to indicate that the library is open. ().
Returns: The string "Today is day n."
find_all_overdue_books(self)
Returns Prints a nicely formatted, multiline string, listing the names of patrons who have overdue books, and for each such patron, the books that are overdue. Or, it returns prints the string "No books are overdue.".
issue_card(self, name_of_patron)
Issues a library card to the person with this name. (What this actually does is create a Patron object, and save it as the key in a dictionary. The value should be the empty set, representing the books currently checked out to this patron.) However, no patron should be permitted to have more than one library card.
Returns either "Library card issued to name_of_patron." or "name_of_patron already has a library card.".
Possible Exception: "The library is not open.".
serve(self, name_of_patron)
Specifies which patron is about to be served (and quits serving the previous patron, if any). The purpose of this method is so that you don't have to type in the person's name again and again for every book that is to be checked in or checked out. What the method should actually do is to look up the patron's name in the dictionary, and save the returned Patron object in a data variable of this library.
Returns either "Now serving name_of_patron." or "name_of_patron does not have a library card.".
Possible Exception: "The library is not open."
find_overdue_books(self)
Returns Prints a multiline string, each line containing one book (as returned by the book's __str__ method), of the books that have been checked out by the patron currently being served, and which are overdue. If the patron has no overdue books, the value None (not the string "None") is returned. the string "None" is printed.
May throw an Exception with an appropriate message:
  • "The library is not open."
  • "No patron is currently being served."
check_in(self, *book_numbers)
The book is being returned by the current patron (there must be one!), so return it to the collection and remove it from the set of books currently checked out to the patron. The book_numbers are taken from the list printed by the serve search command. Checking in a Book will involve both telling the Book that it is checked in and returning the Book to this library's collection of available Books.
If successful, returns "name_of_patron has returned n books.".
May throw an Exception with an appropriate message:
  • "The library is not open."
  • "No patron is currently being served."
  • "The patron does not have book id."
search(self, string)
Finds those Books whose title or author (or both) contains this string. For example, the string "tact" might return, among other things, the book Contact, by Carl Sagan. The search should be case-insensitive; that is, "saga" would also return this book. Only books which are currently available (not checked out) will be found. If there is more than one copy of a book (with the same title and same author), only one will be found. In addition, to keep from returning too many books, require that the search string be at least 4 characters long.
Returns one of:
  • "No books found."
  • "Search string must contain at least four characters."
  • A multiline string, each line containing one book (as returned by the book's __str__ method.)
 
check_out(self, *book_ids)
Checks out the book to the patron currently being served (there must be one!), or tells why the operation is not permitted. The book_ids could have been found by a recent call to the search method. Checking out a book will involve both telling the book that it is checked out and removing the book from this library's collection of available books.
If successful, returns "n books have been checked out to name_of_patron.".
May throw an Exception with an appropriate message:
  • "The library is not open."
  • "No patron is currently being served."
  • "The library does not have book id."
 
renew(self, *book_ids)
Renews the books for the patron currently being served (by setting their due dates to today's date plus 7) or tells why the operation is not permitted.
If successful, returns "n books have been renewed for name_of_patron.".
May throw an Exception with an appropriate message:
  • "The library is not open."
  • "No patron is currently being served."
  • "The patron does not have book id."
 
close(self)
Shut down operations and go home for the night. None of the other operations (except quit) can be used when the library is closed.
If successful, returns the string "Good night.".
May throw an Exception with the message "The library is not open."
quit(self)
End the program. The mayor, citing a budget crisis, has stopped all funding for the library. Can happen at any time. Returns the string "The library is now closed for renovations.".
Checking books in and out is slightly complex, so here is what the librarian needs to know:
To check books in:
     1. If you have not already done so, serve the patron. This will print a numbered list of books checked out to that patron.
     2. check_in the books by the numbers given above.
To check books out:
     1. If you have not already done so, serve the patron. You can ignore the list of books that this will print out.
     2. search for a book wanted by the patron (unless you already know its id).
     3. check_out zero or more books by the numbers returned from the search command.
     4. If more books are desired, you can do another search.

Programming details

Instance variables are variables that belong to an object, and instance methods are methods that are defined for an object. Instance variables and methods must be prefixed with self. , when used by the object itself. Local variables (including parameters) and global variables should not be prefixed with self. When an object uses the methods of another object, those methods must be prefixed by a variable that refers to the object; for example, book.get_title().

Three of the methods above, check_out, check_in, and renew, have an asterisk in front of the last parameter. That asterisk is new syntax--it means that, when you call the method, you can give as many book numbers as you please; you don't have to give just one. Inside the method, the parameter book_ids is a tuple of all the numbers (zero or more) that you gave it.

Grading

Zip together and turn in two files: library.py, containing all of the above classes, and library_test.py, containing all the unit tests.

It would be a good idea to read How Assignments Are Graded, but here's a summary: Do exactly what the assignment says to do, use good programming style, test your program carefully, zip up your program (if it's more than a single file), and turn it in via Blackboard. Late programs will lose points.

Due date

Your program is due before