Assignment Overview
In this assignment, you will build a complete Library Management System that demonstrates your mastery of Object-Oriented Programming in Python. This project requires you to apply ALL concepts from Module 6: classes & objects, inheritance, polymorphism, magic methods, encapsulation, properties, and abstract base classes.
abc, datetime, json).
No third-party libraries allowed. This tests your understanding of pure Python OOP.
Classes & Objects (6.1)
Class definition, instantiation, instance/class variables, methods, constructors
Inheritance (6.2)
Single/multiple inheritance, MRO, super() function
Polymorphism (6.3)
Method overriding, dunder methods, operator overloading
Encapsulation (6.4)
Private members, properties, abstract base classes
The Scenario
BookWise Library System
You have been hired as a Software Developer at BookWise Libraries, a chain of modern libraries that needs a robust management system. The technical lead has given you this task:
"We need an object-oriented library system that can manage different types of library items (books, magazines, DVDs), handle member accounts with different membership tiers, process borrowing/returning transactions, and calculate fines. The system should be extensible, well-encapsulated, and follow OOP best practices."
Your Task
Create a Python project with multiple modules that implements a complete library management system. Your code must demonstrate proficiency in all OOP concepts taught in Module 6, with proper class hierarchies, inheritance, polymorphism, and encapsulation.
Class Diagram
Your system should implement the following class hierarchy:
┌─────────────────┐
│ LibraryItem │ (Abstract Base Class)
│ (ABC) │
├─────────────────┤
│ - _item_id │
│ - _title │
│ - _is_available │
├─────────────────┤
│ + borrow() │ (abstract)
│ + return_item() │ (abstract)
│ + get_info() │ (abstract)
│ + __str__() │
│ + __repr__() │
└────────┬────────┘
│
┌───────────────────────┼───────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Book │ │ Magazine │ │ DVD │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ - author │ │ - issue_number │ │ - director │
│ - isbn │ │ - publisher │ │ - duration │
│ - pages │ │ │ │ - genre │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ + borrow() │ │ + borrow() │ │ + borrow() │
│ + return_item() │ │ + return_item() │ │ + return_item() │
│ + get_info() │ │ + get_info() │ │ + get_info() │
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────┐
│ Member │ (Base Class)
├─────────────────┤
│ - _member_id │
│ - _name │
│ - _email │
│ - _borrowed │
│ - _fine_amount │
├─────────────────┤
│ + borrow_item() │
│ + return_item() │
│ + pay_fine() │
│ + __str__() │
│ + __eq__() │
└────────┬────────┘
│
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ RegularMember │ │ PremiumMember │ │ StudentMember │
├─────────────────┤ ├─────────────────┤ ├─────────────────┤
│ max_books = 3 │ │ max_books = 10 │ │ max_books = 5 │
│ loan_days = 14 │ │ loan_days = 30 │ │ loan_days = 21 │
│ fine_rate = 1.0 │ │ fine_rate = 0.5 │ │ fine_rate = 0.25│
└─────────────────┘ └─────────────────┘ └─────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Library │
├─────────────────────────────────────────────────────────────┤
│ - _name: str │
│ - _items: dict[str, LibraryItem] │
│ - _members: dict[str, Member] │
│ - _transactions: list[Transaction] │
├─────────────────────────────────────────────────────────────┤
│ + add_item(item: LibraryItem) │
│ + remove_item(item_id: str) │
│ + register_member(member: Member) │
│ + process_borrow(member_id: str, item_id: str) │
│ + process_return(member_id: str, item_id: str) │
│ + search_items(query: str) -> list[LibraryItem] │
│ + get_overdue_items() -> list[tuple] │
│ + generate_report() -> str │
│ + __len__() -> int │
│ + __contains__(item_id: str) -> bool │
│ + __iter__() │
└─────────────────────────────────────────────────────────────┘
Requirements
Your project must implement ALL of the following requirements. Each requirement is mandatory and will be tested individually.
Abstract Base Class: LibraryItem (6.4)
Create an abstract base class LibraryItem that:
- Uses
from abc import ABC, abstractmethod - Has private attributes:
_item_id,_title,_is_available - Uses
@propertydecorators for getters - Defines abstract methods:
borrow(),return_item(),get_info() - Implements
__str__and__repr__magic methods
from abc import ABC, abstractmethod
class LibraryItem(ABC):
"""Abstract base class for all library items."""
def __init__(self, item_id: str, title: str):
self._item_id = item_id
self._title = title
self._is_available = True
@property
def item_id(self) -> str:
"""Get item ID (read-only)."""
return self._item_id
@property
def title(self) -> str:
"""Get title (read-only)."""
return self._title
@property
def is_available(self) -> bool:
"""Check if item is available."""
return self._is_available
@abstractmethod
def borrow(self) -> bool:
"""Borrow this item. Returns True if successful."""
pass
@abstractmethod
def return_item(self) -> bool:
"""Return this item. Returns True if successful."""
pass
@abstractmethod
def get_info(self) -> dict:
"""Get detailed information about this item."""
pass
def __str__(self) -> str:
status = "Available" if self._is_available else "Borrowed"
return f"{self._title} ({self._item_id}) - {status}"
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self._item_id}', '{self._title}')"
Concrete Classes: Book, Magazine, DVD (6.1, 6.2)
Create three classes that inherit from LibraryItem:
Book: Additional attributes -author,isbn,pagesMagazine: Additional attributes -issue_number,publisherDVD: Additional attributes -director,duration(minutes),genre- Each must implement all abstract methods
- Use
super().__init__()properly
class Book(LibraryItem):
"""Represents a book in the library."""
def __init__(self, item_id: str, title: str, author: str,
isbn: str, pages: int):
super().__init__(item_id, title)
self.author = author
self.isbn = isbn
self.pages = pages
def borrow(self) -> bool:
if self._is_available:
self._is_available = False
return True
return False
def return_item(self) -> bool:
if not self._is_available:
self._is_available = True
return True
return False
def get_info(self) -> dict:
return {
"type": "Book",
"id": self._item_id,
"title": self._title,
"author": self.author,
"isbn": self.isbn,
"pages": self.pages,
"available": self._is_available
}
# Similarly implement Magazine and DVD classes
Base Class: Member (6.1, 6.4)
Create a Member base class with:
- Private attributes:
_member_id,_name,_email,_borrowed_items,_fine_amount - Properties with validation for email
- Class variable to track total members
- Methods:
borrow_item(),return_item(),pay_fine() - Magic methods:
__str__,__eq__,__hash__
class Member:
"""Base class for library members."""
_total_members = 0 # Class variable
def __init__(self, member_id: str, name: str, email: str):
self._member_id = member_id
self._name = name
self._email = email
self._borrowed_items: list = []
self._fine_amount: float = 0.0
Member._total_members += 1
@property
def email(self) -> str:
return self._email
@email.setter
def email(self, value: str):
if "@" not in value:
raise ValueError("Invalid email format")
self._email = value
@classmethod
def get_total_members(cls) -> int:
return cls._total_members
def __eq__(self, other) -> bool:
if isinstance(other, Member):
return self._member_id == other._member_id
return False
def __hash__(self) -> int:
return hash(self._member_id)
Member Subclasses: RegularMember, PremiumMember, StudentMember (6.2, 6.3)
Create three member types with different privileges:
RegularMember: max 3 items, 14-day loan, ₹1.00/day finePremiumMember: max 10 items, 30-day loan, ₹0.50/day fineStudentMember: max 5 items, 21-day loan, ₹0.25/day fine- Use class variables for limits and rates
- Override
borrow_item()to enforce limits - Demonstrate polymorphism through method overriding
class RegularMember(Member):
"""Regular library member with standard privileges."""
MAX_ITEMS = 3
LOAN_DAYS = 14
FINE_RATE = 1.0 # Per day
def __init__(self, member_id: str, name: str, email: str):
super().__init__(member_id, name, email)
def borrow_item(self, item: LibraryItem) -> bool:
"""Borrow an item if within limits."""
if len(self._borrowed_items) >= self.MAX_ITEMS:
return False
if item.borrow():
self._borrowed_items.append(item)
return True
return False
def calculate_fine(self, days_overdue: int) -> float:
"""Calculate fine based on overdue days."""
return days_overdue * self.FINE_RATE
# Similarly implement PremiumMember and StudentMember
Transaction Class with Magic Methods (6.3)
Create a Transaction class that:
- Records borrowing/returning transactions
- Has attributes:
transaction_id,member,item,borrow_date,return_date,due_date - Implements
__str__,__repr__,__lt__(for sorting by date) - Implements
__eq__for comparison - Has a method to check if overdue
from datetime import datetime, timedelta
class Transaction:
"""Records a borrowing transaction."""
_id_counter = 0
def __init__(self, member: Member, item: LibraryItem, loan_days: int):
Transaction._id_counter += 1
self._transaction_id = f"TXN{Transaction._id_counter:05d}"
self._member = member
self._item = item
self._borrow_date = datetime.now()
self._due_date = self._borrow_date + timedelta(days=loan_days)
self._return_date = None
def is_overdue(self) -> bool:
"""Check if transaction is overdue."""
if self._return_date:
return False
return datetime.now() > self._due_date
def days_overdue(self) -> int:
"""Calculate days overdue."""
if not self.is_overdue():
return 0
return (datetime.now() - self._due_date).days
def __lt__(self, other) -> bool:
"""Compare transactions by borrow date."""
return self._borrow_date < other._borrow_date
def __str__(self) -> str:
status = "Returned" if self._return_date else "Active"
return f"[{self._transaction_id}] {self._member._name} - {self._item.title} ({status})"
Library Class with Container Methods (6.3)
Create a Library class that:
- Manages items, members, and transactions
- Implements
__len__to return total items - Implements
__contains__forinoperator - Implements
__iter__to iterate over items - Implements
__getitem__for indexing by item_id
class Library:
"""Main library class managing items, members, and transactions."""
def __init__(self, name: str):
self._name = name
self._items: dict[str, LibraryItem] = {}
self._members: dict[str, Member] = {}
self._transactions: list[Transaction] = []
def add_item(self, item: LibraryItem) -> None:
"""Add an item to the library."""
self._items[item.item_id] = item
def __len__(self) -> int:
"""Return total number of items."""
return len(self._items)
def __contains__(self, item_id: str) -> bool:
"""Check if item exists in library."""
return item_id in self._items
def __iter__(self):
"""Iterate over all items."""
return iter(self._items.values())
def __getitem__(self, item_id: str) -> LibraryItem:
"""Get item by ID."""
return self._items[item_id]
Operator Overloading (6.3)
Add operator overloading to appropriate classes:
Library + Library: Merge two libraries (create new with combined items)Member + fine_amount: Add to member's fineMember - payment: Subtract from member's fine- Implement
__add__,__radd__,__sub__as appropriate
# In Library class
def __add__(self, other: 'Library') -> 'Library':
"""Merge two libraries."""
new_library = Library(f"{self._name} + {other._name}")
new_library._items.update(self._items)
new_library._items.update(other._items)
return new_library
# In Member class
def __add__(self, amount: float) -> 'Member':
"""Add to fine amount."""
self._fine_amount += amount
return self
def __sub__(self, amount: float) -> 'Member':
"""Pay fine (subtract from amount)."""
self._fine_amount = max(0, self._fine_amount - amount)
return self
Mixin Class: Searchable (6.2)
Create a mixin class for search functionality:
- Create
SearchableMixinclass with search methods - Methods:
search_by_title(),search_by_author(),filter_available() - Use multiple inheritance in Library class
- Demonstrate MRO (Method Resolution Order)
class SearchableMixin:
"""Mixin providing search functionality."""
def search_by_title(self, query: str) -> list:
"""Search items by title (case-insensitive)."""
return [item for item in self._items.values()
if query.lower() in item.title.lower()]
def search_by_type(self, item_type: type) -> list:
"""Filter items by type (Book, Magazine, DVD)."""
return [item for item in self._items.values()
if isinstance(item, item_type)]
def filter_available(self) -> list:
"""Get all available items."""
return [item for item in self._items.values()
if item.is_available]
class Library(SearchableMixin):
"""Library with search capabilities."""
# Now Library has all search methods via MRO
pass
# Demonstrate MRO
print(Library.__mro__)
Property with Validation (6.4)
Add properties with validation to your classes:
- Member email: Must contain "@" and valid format
- Book pages: Must be positive integer
- DVD duration: Must be positive integer
- Use
@propertyand@setterdecorators - Raise
ValueErrorwith descriptive messages
class Book(LibraryItem):
"""Book with validated properties."""
def __init__(self, item_id: str, title: str, author: str,
isbn: str, pages: int):
super().__init__(item_id, title)
self.author = author
self.isbn = isbn
self._pages = None
self.pages = pages # Use setter for validation
@property
def pages(self) -> int:
return self._pages
@pages.setter
def pages(self, value: int):
if not isinstance(value, int) or value <= 0:
raise ValueError("Pages must be a positive integer")
self._pages = value
Static and Class Methods (6.1)
Add static and class methods to your classes:
@staticmethod:LibraryItem.generate_id()- Generate unique ID@classmethod:Book.from_dict(data)- Create from dictionary@classmethod:Member.get_total_members()- Return total count- Demonstrate when to use each type
import uuid
class LibraryItem(ABC):
@staticmethod
def generate_id(prefix: str = "ITEM") -> str:
"""Generate a unique item ID."""
return f"{prefix}-{uuid.uuid4().hex[:8].upper()}"
class Book(LibraryItem):
@classmethod
def from_dict(cls, data: dict) -> 'Book':
"""Create a Book instance from a dictionary."""
return cls(
item_id=data.get('id', LibraryItem.generate_id('BOOK')),
title=data['title'],
author=data['author'],
isbn=data['isbn'],
pages=data['pages']
)
# Usage
book_data = {"title": "Python 101", "author": "John", "isbn": "123", "pages": 300}
book = Book.from_dict(book_data)
Context Manager: LibrarySession (6.3)
Create a context manager for library sessions:
- Implement
__enter__and__exit__methods - Track session start/end times
- Log transactions during the session
- Auto-save changes on exit
class LibrarySession:
"""Context manager for library sessions."""
def __init__(self, library: Library, user: str):
self.library = library
self.user = user
self.start_time = None
self.transactions = []
def __enter__(self):
self.start_time = datetime.now()
print(f"Session started by {self.user} at {self.start_time}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
end_time = datetime.now()
duration = end_time - self.start_time
print(f"Session ended. Duration: {duration}")
print(f"Transactions: {len(self.transactions)}")
# Return False to propagate exceptions
return False
# Usage
with LibrarySession(library, "Admin") as session:
session.library.add_item(new_book)
session.transactions.append("Added book")
Data Persistence: JSON Serialization (6.3)
Add methods to save/load library data:
- Implement
to_dict()method in all classes - Implement
@classmethod from_dict()for deserialization - Library methods:
save_to_json(),load_from_json() - Handle nested objects (items, members, transactions)
import json
class Library:
def save_to_json(self, filename: str) -> None:
"""Save library data to JSON file."""
data = {
"name": self._name,
"items": [item.to_dict() for item in self._items.values()],
"members": [member.to_dict() for member in self._members.values()],
"transactions": [txn.to_dict() for txn in self._transactions]
}
with open(filename, 'w') as f:
json.dump(data, f, indent=2, default=str)
@classmethod
def load_from_json(cls, filename: str) -> 'Library':
"""Load library data from JSON file."""
with open(filename, 'r') as f:
data = json.load(f)
library = cls(data['name'])
# Reconstruct items based on type
for item_data in data['items']:
if item_data['type'] == 'Book':
library.add_item(Book.from_dict(item_data))
# ... handle other types
return library
Main Program with Demonstrations
Create a main.py that demonstrates all features:
- Create library instance
- Add various items (books, magazines, DVDs)
- Register different member types
- Process borrow/return transactions
- Demonstrate search and filtering
- Show polymorphism with different member types
- Save and load library data
def main():
"""Demonstrate the Library Management System."""
print("=" * 60)
print("BOOKWISE LIBRARY MANAGEMENT SYSTEM")
print("=" * 60)
# Create library
library = Library("Central Library")
# Add items
book1 = Book("B001", "Python Mastery", "John Smith", "978-0-13-110362-7", 450)
book2 = Book("B002", "Data Science", "Jane Doe", "978-0-59-651798-4", 320)
magazine1 = Magazine("M001", "Tech Today", 42, "TechCorp")
dvd1 = DVD("D001", "Python Tutorial", "Edu Films", 120, "Educational")
for item in [book1, book2, magazine1, dvd1]:
library.add_item(item)
print(f"\nLibrary has {len(library)} items")
# Register members
regular = RegularMember("REG001", "Alice Brown", "alice@email.com")
premium = PremiumMember("PRE001", "Bob Wilson", "bob@email.com")
student = StudentMember("STU001", "Charlie Davis", "charlie@student.edu")
library.register_member(regular)
library.register_member(premium)
library.register_member(student)
# Demonstrate borrowing (polymorphism)
print("\n--- Borrowing Demo ---")
for member in [regular, premium, student]:
success = member.borrow_item(book1) if book1.is_available else False
print(f"{member._name}: {'Borrowed' if success else 'Failed'}")
# ... more demonstrations
# Save to JSON
library.save_to_json("library_data.json")
print("\nLibrary data saved!")
if __name__ == "__main__":
main()
Submission
Create a public GitHub repository with the exact name shown below:
Required Repository Name
python-library-oop
Required Files
python-library-oop/
├── models/
│ ├── __init__.py
│ ├── library_item.py # LibraryItem ABC, Book, Magazine, DVD
│ ├── member.py # Member base class and subclasses
│ ├── transaction.py # Transaction class
│ └── library.py # Library class with mixins
├── main.py # Main program with demonstrations
├── test_library.py # Unit tests (at least 15 tests)
├── library_data.json # Sample data file
├── output.txt # Output from running main.py
└── README.md # REQUIRED - see contents below
README.md Must Include:
- Your full name and submission date
- Class diagram (ASCII or image)
- Brief description of each class and its purpose
- Explanation of OOP concepts demonstrated
- Instructions to run the system and tests
Do Include
- All 13 requirements implemented
- Docstrings for every class and method
- Type hints throughout
- Proper package structure with __init__.py
- Unit tests covering all classes
- README.md with class diagram
Do Not Include
- External libraries (only abc, datetime, json, uuid)
- Any .pyc or __pycache__ files
- Virtual environment folders
- Code that doesn't follow OOP principles
- Classes without proper encapsulation
Enter your GitHub username - we'll verify your repository automatically
Grading Rubric
Your assignment will be graded on the following criteria:
| Criteria | Points | Description |
|---|---|---|
| Classes & Objects (6.1) | 35 | Proper class definition, instance/class variables, constructors, static/class methods |
| Inheritance (6.2) | 40 | Correct inheritance hierarchy, super() usage, MRO understanding, mixins |
| Polymorphism & Magic Methods (6.3) | 50 | Method overriding, all required dunder methods, operator overloading, context manager |
| Encapsulation & Properties (6.4) | 40 | Private/protected members, property decorators with validation, ABC implementation |
| Code Quality & Testing | 35 | Docstrings, type hints, project structure, comprehensive unit tests, README |
| Total | 200 |
Ready to Submit?
Make sure you have completed all requirements and reviewed the grading rubric above.
Submit Your AssignmentWhat You Will Practice
Classes & Objects (6.1)
Class definition, instantiation, instance/class variables, methods, self, constructors, static and class methods
Inheritance (6.2)
Single and multiple inheritance, Method Resolution Order (MRO), super() function, mixin classes
Polymorphism & Magic Methods (6.3)
Method overriding, dunder methods (__init__, __str__, __eq__, etc.), operator overloading, context managers
Encapsulation & Properties (6.4)
Private/protected members, @property decorators, validation, abstract base classes (ABC)
Pro Tips
Class Design Tips
- Follow Single Responsibility Principle
- Use composition over inheritance when appropriate
- Keep class hierarchies shallow (max 3 levels)
- Document class relationships clearly
Inheritance Tips
- Always call super().__init__() in subclasses
- Understand MRO before using multiple inheritance
- Use mixins for shared functionality
- Override methods only when behavior changes
Magic Methods Tips
- Implement __repr__ for debugging
- Implement __eq__ and __hash__ together
- Return NotImplemented for unsupported operations
- Keep __str__ human-readable
Common Mistakes
- Forgetting to call super().__init__()
- Using mutable default arguments
- Exposing internal state without properties
- Creating circular imports between modules