CIT 590 Assignment 6: Library
Spring 2010, 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 differences between the behavior and the implementation.

I don't think you will need any additional classes, but you are likely to need some additional methods, not listed below. Feel free to write more methods (but remember to test them).

Classes and methods

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 (starting from 0) to keep track of how many days have passed. We only need 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).
class Book(object)
A book has these attributes (instance variables): its title, its author (only one author per book), and its due_date. The due date is None if the book is not checked out.
__init__(self, title, author)
The constructor. Saves the provided information. When created, the book is not checked out.
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. It is not this Book's job to remove itself from the Library's collection of available books.
check_in(self)
Sets the due date of this Book to None. It is not this Book's job to return itself to the Library's collection of available books.
__str__(self)
Returns a string of the form 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 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.
take(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 OverdueNotice(object)
Represents an "overdue notice" that contains a list of all the books checked out to this user, along with their due dates, and indicates which ones are overdue.
__init__(self, set_of_books)
Constructs an overdue notice. (What it actually does is just keep a copy of the set_of_books, along with today's date.)
__str__(self)
Returns as a string, in a nice, humanly readable format, an overdue notice. The notice should list the books along with their due dates, and call attention to which ones are overdue. (This method allows the Patron to easily print out the overdue 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 result in an informative printout, for example, "Library card issued to Amy Gutmann."
__init__(self)
Constructs the library object (you should create exactly one of these). Asks the user for the name of a file, then reads in from the file a list of (title, author) tuples, creates a Book from each tuple, and saves these in some appropriate data structure of your choice. These Books form the library's collection. Do not assume that there is only one copy of each book.

Of course, when the library is created, none of the books are checked out. There are not yet any patrons, either, but you should define an empty dictionary (whose keys will be the names of patrons and whose values will be the corresponding Patron objects).
open(self)
Starts the day (by advancing the Calendar). Then sends overdue notices to all patrons with overdue books (see the next method). Do whatever else needs to be done to indicate that the library is open (set a variable, maybe).
send_overdue_notices(self)
Sends overdue notices to all patrons with overdue books. (Since this is a standard part of opening the library, it should be done automatically when the library is opened. In other words, while the librarian could enter this command directly, it should be called from the open method, so that the librarian doesn't have to do it as a separate command.)
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.
serve(self, name_of_patron)
Begin checking books out to (or in from) the named patron. 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. In addition, the method should print out a numbered list of the books (if any) currently checked out to this patron.
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 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.
search(self, string)
Returns a set of 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 returned. In addition, to keep from returning too many books, require that the search string be at least 4 characters long.
check_out(self, *book_numbers)
Either checks out the book to the Patron currently being served (there must be one!), or tells why the operation is not permitted. The book_numbers are one or more of the books returned by the most 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.
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.
quit(self)
End the program. The mayor, citing a budget crisis, has stopped all funding for the library. Can happen at any time.
allow_printing(self, allow)
Sets a library variable to indicate whether the rest of the Library methods should be allowed to print anything. When allow is given as True, the methods may print; when False, no printing is done.
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.
     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.

Note on new syntax

Two of the methods above, check_out and check_in, 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_numbers is a tuple of all the numbers (zero or more) that you gave it.

Testing

General rules:

Problem, and solution

All of the methods in Library may be called directly by the librarian. Whenever the librarian calls one of these methods, some output should result. However, these methods also do quite a bit of work, and should be tested. So, do these methods print something out, or don't they?

My solution is to put in an additional variable, initialized to True, that these methods must check before doing any printing. An additional method, allow_printing, is added to the Library class, so that the test methods have a way to turn off (or on) printing. This method should normally be called only from the test methods.

Hints

You can put multiple classes in a single file. However, please turn in two files, named library.py and library_test.py. Tests should normally be kept separate from the program being tested.

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().

The test methods for each class can include a setup(self) method. This is a good place to declare global variables. For example, my test methods use

    global lib
    lib = library.Library() 

Grading:

You and your partner should decide who is to turn in the program, and you should submit one program for the two of you. Both of you will receive the same grade on the program.

Due date:

Midnight Thursday, February 25. Zip all files and submit via Blackboard. The author or authors of each class should be prominently displayed in a comment at the top of the class.