AiTechWorlds
AiTechWorlds
A fully functional Library Management System that ties together every OOP concept from this course: abstract classes, inheritance, composition, magic methods, properties, design patterns, and SOLID principles.
By the end of this lesson, you will have a working system that tracks books, manages members, handles borrowing and returns, calculates fines, and sends overdue notifications — all built with clean, principled OOP design.
| Feature | Description |
|---|---|
| Items | Books, Magazines, DVDs — each with unique attributes |
| Members | Regular and Premium members with different borrowing limits |
| Borrowing | Check out items, enforce limits, track due dates |
| Returns | Return items, calculate fines for late returns |
| Notifications | Alert members and staff when items are overdue |
| Reporting | Search catalog, view borrowing history |
from abc import ABC, abstractmethod
from datetime import date, timedelta
from typing import Optional
class LibraryItem(ABC):
"""Abstract base: every item in the library must implement these."""
def __init__(self, item_id: str, title: str, year: int):
self._item_id = item_id # Unique catalog identifier
self._title = title # Display title
self._year = year # Publication/release year
self._available = True # True = on shelf, False = checked out
@property
def item_id(self) -> str:
return self._item_id
@property
def title(self) -> str:
return self._title
@property
def available(self) -> bool:
return self._available
@abstractmethod
def item_type(self) -> str:
"""Every subclass must declare its type."""
pass
@abstractmethod
def borrow_duration_days(self) -> int:
"""How many days this item type can be borrowed."""
pass
def checkout(self):
if not self._available:
raise ValueError(f"'{self._title}' is not available")
self._available = False # Mark as checked out
def return_item(self):
self._available = True # Mark as returned to shelf
def __repr__(self) -> str:
status = "Available" if self._available else "Checked Out"
return f"{self.item_type()} [{self._item_id}]: '{self._title}' ({self._year}) — {status}"
class Book(LibraryItem):
def __init__(self, item_id: str, title: str, year: int, author: str, isbn: str):
super().__init__(item_id, title, year)
self.author = author # Author's full name
self.isbn = isbn # International Standard Book Number
def item_type(self) -> str:
return "Book"
def borrow_duration_days(self) -> int:
return 21 # Books: 3-week loan period
class Magazine(LibraryItem):
def __init__(self, item_id: str, title: str, year: int, issue: str):
super().__init__(item_id, title, year)
self.issue = issue # e.g. "Vol. 12, No. 4"
def item_type(self) -> str:
return "Magazine"
def borrow_duration_days(self) -> int:
return 7 # Magazines: 1-week loan (high demand)
class DVD(LibraryItem):
def __init__(self, item_id: str, title: str, year: int, director: str, runtime_mins: int):
super().__init__(item_id, title, year)
self.director = director # Film director's name
self.runtime_mins = runtime_mins # Film length in minutes
def item_type(self) -> str:
return "DVD"
def borrow_duration_days(self) -> int:
return 5 # DVDs: 5-day loan period
class BorrowingRecord:
"""Represents one borrowing transaction."""
FINE_PER_DAY = 0.25 # $0.25 fine per overdue day
def __init__(self, item: LibraryItem, member_id: str):
self.item = item # The borrowed item
self.member_id = member_id # Who borrowed it
self.borrow_date = date.today() # When it was checked out
self.due_date = date.today() + timedelta(days=item.borrow_duration_days())
self.return_date: Optional[date] = None # Set when returned
@property
def is_overdue(self) -> bool:
if self.return_date:
return self.return_date > self.due_date # Returned late
return date.today() > self.due_date # Still out, past due
@property
def fine_amount(self) -> float:
if not self.is_overdue:
return 0.0
if self.return_date:
overdue_days = (self.return_date - self.due_date).days
else:
overdue_days = (date.today() - self.due_date).days
return overdue_days * self.FINE_PER_DAY # Accumulate daily fine
def complete_return(self):
self.return_date = date.today() # Mark as returned today
self.item.return_item() # Make item available again
class LibraryMember:
"""Regular library member."""
MAX_ITEMS = 3 # Regular members: borrow up to 3 items
def __init__(self, member_id: str, name: str, email: str):
self.member_id = member_id # Unique membership number
self.name = name # Member's full name
self.email = email # Contact email
self._active_borrows: list[BorrowingRecord] = [] # Current checkouts
self._history: list[BorrowingRecord] = [] # All past records
@property
def active_borrows(self) -> list:
return self._active_borrows
@property
def can_borrow(self) -> bool:
return len(self._active_borrows) < self.MAX_ITEMS # Under the limit
@property
def total_fines(self) -> float:
# Sum fines from active overdue borrows only (completed fines paid at return)
return sum(r.fine_amount for r in self._active_borrows)
def add_borrow(self, record: BorrowingRecord):
self._active_borrows.append(record) # Track new checkout
def complete_return(self, item_id: str) -> BorrowingRecord:
# Find the active record for this item
for record in self._active_borrows:
if record.item.item_id == item_id:
record.complete_return() # Mark returned
self._active_borrows.remove(record) # Remove from active
self._history.append(record) # Move to history
return record
raise ValueError(f"No active borrow found for item {item_id}")
def __repr__(self) -> str:
return f"Member [{self.member_id}]: {self.name} ({len(self._active_borrows)} items borrowed)"
class PremiumMember(LibraryMember):
"""Premium members get extended borrowing privileges."""
MAX_ITEMS = 10 # 10-item limit instead of 3
def __init__(self, member_id: str, name: str, email: str):
super().__init__(member_id, name, email)
self.tier = "Premium" # Membership tier label
@property
def fine_discount(self) -> float:
return 0.5 # Premium members pay 50% of fines
@property
def total_fines(self) -> float:
# Override: apply discount to all fines
base_fines = super().total_fines
return base_fines * (1 - self.fine_discount)
class OverdueObserver(ABC):
@abstractmethod
def on_overdue(self, member: LibraryMember, record: BorrowingRecord):
pass
class EmailOverdueNotifier(OverdueObserver):
def on_overdue(self, member: LibraryMember, record: BorrowingRecord):
fine = record.fine_amount
print(f"[EMAIL] To: {member.email} | '{record.item.title}' is overdue. Fine: ${fine:.2f}")
class StaffAlertNotifier(OverdueObserver):
def on_overdue(self, member: LibraryMember, record: BorrowingRecord):
print(f"[STAFF ALERT] {member.name} has overdue item: '{record.item.title}'")
class Library:
"""Central system managing the entire library."""
def __init__(self, name: str):
self.name = name
self._catalog: dict[str, LibraryItem] = {} # item_id → LibraryItem
self._members: dict[str, LibraryMember] = {} # member_id → Member
self._observers: list[OverdueObserver] = [] # Overdue notification subscribers
# --- Catalog Management ---
def add_item(self, item: LibraryItem):
self._catalog[item.item_id] = item # Register item in catalog
def search(self, query: str) -> list[LibraryItem]:
# Case-insensitive search across titles
q = query.lower()
return [item for item in self._catalog.values()
if q in item.title.lower()]
# --- Member Management ---
def register_member(self, member: LibraryMember):
self._members[member.member_id] = member # Register new member
# --- Borrowing Operations ---
def checkout(self, member_id: str, item_id: str) -> BorrowingRecord:
member = self._members.get(member_id)
if not member:
raise ValueError(f"Member {member_id} not found")
item = self._catalog.get(item_id)
if not item:
raise ValueError(f"Item {item_id} not found in catalog")
if not member.can_borrow:
raise ValueError(f"{member.name} has reached their borrowing limit")
item.checkout() # Mark item unavailable
record = BorrowingRecord(item, member_id) # Create borrow record
member.add_borrow(record) # Add to member's borrows
print(f"Checked out '{item.title}' to {member.name}. Due: {record.due_date}")
return record
def return_item(self, member_id: str, item_id: str):
member = self._members.get(member_id)
if not member:
raise ValueError(f"Member {member_id} not found")
record = member.complete_return(item_id) # Process return
fine = record.fine_amount
if fine > 0:
print(f"Returned '{record.item.title}'. Late fine: ${fine:.2f}")
else:
print(f"Returned '{record.item.title}'. No fines.")
# --- Observer Pattern Integration ---
def add_observer(self, observer: OverdueObserver):
self._observers.append(observer) # Register notification handler
def check_overdue_items(self):
"""Scan all active borrows and notify observers of overdue items."""
for member in self._members.values():
for record in member.active_borrows:
if record.is_overdue:
for observer in self._observers:
observer.on_overdue(member, record)
# --- Setup ---
library = Library("City Public Library")
# Register overdue notification handlers
library.add_observer(EmailOverdueNotifier())
library.add_observer(StaffAlertNotifier())
# Add items to catalog
library.add_item(Book("B001", "Clean Code", 2008, "Robert C. Martin", "978-0132350884"))
library.add_item(Book("B002", "The Pragmatic Programmer", 2019, "Hunt & Thomas", "978-0135957059"))
library.add_item(Magazine("M001", "National Geographic", 2024, "Vol. 245, No. 3"))
library.add_item(DVD("D001", "Inception", 2010, "Christopher Nolan", 148))
# Register members
library.register_member(LibraryMember("M001", "Alice Chen", "alice@example.com"))
library.register_member(PremiumMember("M002", "Bob Smith", "bob@example.com"))
# --- Borrowing ---
library.checkout("M001", "B001")
library.checkout("M001", "M001")
library.checkout("M002", "B002")
library.checkout("M002", "D001")
# --- Search ---
results = library.search("clean")
for item in results:
print(item)
# --- Return with no fine ---
library.return_item("M001", "B001")
# --- Check overdue (in a real scenario, some items would be past due) ---
library.check_overdue_items()
Sample Output:
Checked out 'Clean Code' to Alice Chen. Due: 2026-06-24
Checked out 'National Geographic' to Alice Chen. Due: 2026-06-10
Checked out 'The Pragmatic Programmer' to Bob Smith. Due: 2026-06-24
Checked out 'Inception' to Bob Smith. Due: 2026-06-08
Book [B001]: 'Clean Code' (2008) — Checked Out
Returned 'Clean Code'. No fines.
| Principle | How This Project Follows It |
|---|---|
| Single Responsibility | LibraryItem handles item data; BorrowingRecord handles transactions; Library coordinates operations |
| Open/Closed | Add new item types (Audiobook, eBook) by subclassing LibraryItem — no existing code changes |
| Liskov Substitution | PremiumMember is fully substitutable for LibraryMember — all checkout/return operations work identically |
| Interface Segregation | OverdueObserver is small and focused — observers only implement what they need |
| Dependency Inversion | Library depends on LibraryItem and LibraryMember abstractions, not concrete Book or PremiumMember |
LibraryItem) with three concrete implementationsPremiumMember extends LibraryMember)Library contains items, members, records, and observers)available, can_borrow, total_fines)You've mastered Object-Oriented Programming — from classes and inheritance all the way to design patterns and architectural principles. Your next steps:
pytest and mock objectsEvery large system — operating systems, databases, web frameworks — is built from small, well-designed classes. You now have the tools to build them.
Get this course's notes on Telegram!
Free cheat sheets, summaries & practice exercises