Assignment 6-A

OOP Design Project

Build a complete Library Management System that demonstrates mastery of Object-Oriented Programming: classes, inheritance hierarchies, polymorphism, magic methods, encapsulation, properties, and abstract base classes.

6-8 hours
Challenging
200 Points
Submit Assignment
What You'll Practice
  • Design class hierarchies
  • Implement inheritance & MRO
  • Use magic/dunder methods
  • Apply encapsulation patterns
  • Create abstract base classes
Contents
01

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.

No External Libraries: You must use ONLY Python's built-in modules (abc, datetime, json). No third-party libraries allowed. This tests your understanding of pure Python OOP.
Skills Applied: This assignment tests your understanding of Classes & Objects (6.1), Inheritance (6.2), Polymorphism & Magic Methods (6.3), and Encapsulation & Properties (6.4) from Module 6.
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

Ready to submit? Already completed the assignment? Submit your work now!
Submit Now
02

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.

03

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__()                                               │
└─────────────────────────────────────────────────────────────┘
04

Requirements

Your project must implement ALL of the following requirements. Each requirement is mandatory and will be tested individually.

1
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 @property decorators 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}')"
2
Concrete Classes: Book, Magazine, DVD (6.1, 6.2)

Create three classes that inherit from LibraryItem:

  • Book: Additional attributes - author, isbn, pages
  • Magazine: Additional attributes - issue_number, publisher
  • DVD: 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
3
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)
4
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 fine
  • PremiumMember: max 10 items, 30-day loan, ₹0.50/day fine
  • StudentMember: 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
5
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})"
6
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__ for in operator
  • 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]
7
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 fine
  • Member - 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
8
Mixin Class: Searchable (6.2)

Create a mixin class for search functionality:

  • Create SearchableMixin class 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__)
9
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 @property and @setter decorators
  • Raise ValueError with 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
10
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)
11
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")
12
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
13
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()
05

Submission

Create a public GitHub repository with the exact name shown below:

Required Repository Name
python-library-oop
github.com/<your-username>/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
Important: Before submitting, run your code to make sure it executes without errors. Test all OOP features work correctly!
Submit Your Assignment

Enter your GitHub username - we'll verify your repository automatically

06

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 Assignment
07

What 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)

08

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
09

Pre-Submission Checklist

OOP Requirements
Repository Requirements