Congratulations! You've learned a tremendous amount about intermediate Python. You understand advanced data structures, functional programming, object-oriented programming, modules, packages, and much more. Now it's time to put it all together and build a complete application.
In this final lesson, we'll create a Library Management System that demonstrates real-world Python development. This project combines classes, inheritance, modules, error handling, file I/O, and best practices. By building this application, you'll see how all the pieces fit together and gain confidence in your intermediate Python skills.
What You'll Learn
- Building a complete intermediate-level application
- Combining OOP, modules, and advanced features
- Project structure and organization
- Best practices for intermediate Python development
- Real-world code organization patterns
Project Overview: Library Management System
We'll build a system that manages books, members, and borrowing operations. The system will:
- Manage a collection of books
- Track library members
- Handle book borrowing and returning
- Save and load data from JSON files
- Provide a command-line interface
- Use object-oriented design with inheritance
- Organize code into modules
Project Structure
library_system/
__init__.py
models/
__init__.py
book.py
member.py
transaction.py
utils/
__init__.py
file_handler.py
validators.py
main.py
data/
books.json
members.json
transactions.json
Step 1: Book Model
Let's start with the Book class:
# models/book.py
from datetime import datetime
class Book:
"""Represents a book in the library."""
def __init__(self, isbn, title, author, year, copies=1):
if not isbn or not title or not author:
raise ValueError("ISBN, title, and author are required")
if year < 0 or year > datetime.now().year:
raise ValueError("Invalid publication year")
if copies < 0:
raise ValueError("Copies cannot be negative")
self.isbn = isbn
self.title = title
self.author = author
self.year = year
self.total_copies = copies
self.available_copies = copies
def borrow(self):
"""Borrow a copy of the book."""
if self.available_copies > 0:
self.available_copies -= 1
return True
return False
def return_book(self):
"""Return a copy of the book."""
if self.available_copies < self.total_copies:
self.available_copies += 1
return True
return False
def is_available(self):
"""Check if book is available."""
return self.available_copies > 0
def to_dict(self):
"""Convert book to dictionary for JSON storage."""
return {
"isbn": self.isbn,
"title": self.title,
"author": self.author,
"year": self.year,
"total_copies": self.total_copies,
"available_copies": self.available_copies
}
@classmethod
def from_dict(cls, data):
"""Create book from dictionary."""
book = cls(
data["isbn"],
data["title"],
data["author"],
data["year"],
data.get("total_copies", 1)
)
book.available_copies = data.get("available_copies", book.total_copies)
return book
def __str__(self):
return f"{self.title} by {self.author} ({self.year})"
def __repr__(self):
return f"Book(isbn='{self.isbn}', title='{self.title}')"
Step 2: Member Model
Now the Member class:
# models/member.py
from datetime import datetime
class Member:
"""Represents a library member."""
def __init__(self, member_id, name, email):
if not member_id or not name or not email:
raise ValueError("Member ID, name, and email are required")
if "@" not in email:
raise ValueError("Invalid email format")
self.member_id = member_id
self.name = name
self.email = email
self.borrowed_books = []
self.join_date = datetime.now().strftime("%Y-%m-%d")
def borrow_book(self, isbn):
"""Add book to borrowed list."""
if isbn not in self.borrowed_books:
self.borrowed_books.append(isbn)
return True
return False
def return_book(self, isbn):
"""Remove book from borrowed list."""
if isbn in self.borrowed_books:
self.borrowed_books.remove(isbn)
return True
return False
def has_book(self, isbn):
"""Check if member has borrowed a book."""
return isbn in self.borrowed_books
def to_dict(self):
"""Convert member to dictionary."""
return {
"member_id": self.member_id,
"name": self.name,
"email": self.email,
"borrowed_books": self.borrowed_books,
"join_date": self.join_date
}
@classmethod
def from_dict(cls, data):
"""Create member from dictionary."""
member = cls(
data["member_id"],
data["name"],
data["email"]
)
member.borrowed_books = data.get("borrowed_books", [])
member.join_date = data.get("join_date", member.join_date)
return member
def __str__(self):
return f"{self.name} ({self.member_id})"
def __repr__(self):
return f"Member(id='{self.member_id}', name='{self.name}')"
Step 3: Transaction Model
Track borrowing transactions:
# models/transaction.py
from datetime import datetime
class Transaction:
"""Represents a book borrowing transaction."""
def __init__(self, transaction_id, member_id, isbn, transaction_type):
self.transaction_id = transaction_id
self.member_id = member_id
self.isbn = isbn
self.transaction_type = transaction_type # "borrow" or "return"
self.date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
def to_dict(self):
"""Convert transaction to dictionary."""
return {
"transaction_id": self.transaction_id,
"member_id": self.member_id,
"isbn": self.isbn,
"transaction_type": self.transaction_type,
"date": self.date
}
@classmethod
def from_dict(cls, data):
"""Create transaction from dictionary."""
transaction = cls(
data["transaction_id"],
data["member_id"],
data["isbn"],
data["transaction_type"]
)
transaction.date = data.get("date", transaction.date)
return transaction
def __str__(self):
return f"{self.transaction_type.title()}: {self.isbn} by {self.member_id} on {self.date}"
Step 4: File Handler Utility
Handle JSON file operations:
# utils/file_handler.py
import json
import os
from pathlib import Path
class FileHandler:
"""Handle file operations for the library system."""
def __init__(self, data_dir="data"):
self.data_dir = Path(data_dir)
self.data_dir.mkdir(exist_ok=True)
self.books_file = self.data_dir / "books.json"
self.members_file = self.data_dir / "members.json"
self.transactions_file = self.data_dir / "transactions.json"
def load_books(self):
"""Load books from JSON file."""
if not self.books_file.exists():
return []
try:
with open(self.books_file, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(f"Error loading books: {e}")
return []
def save_books(self, books):
"""Save books to JSON file."""
try:
data = [book.to_dict() if hasattr(book, "to_dict") else book for book in books]
with open(self.books_file, "w") as f:
json.dump(data, f, indent=2)
return True
except IOError as e:
print(f"Error saving books: {e}")
return False
def load_members(self):
"""Load members from JSON file."""
if not self.members_file.exists():
return []
try:
with open(self.members_file, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(f"Error loading members: {e}")
return []
def save_members(self, members):
"""Save members to JSON file."""
try:
data = [member.to_dict() if hasattr(member, "to_dict") else member for member in members]
with open(self.members_file, "w") as f:
json.dump(data, f, indent=2)
return True
except IOError as e:
print(f"Error saving members: {e}")
return False
def save_transaction(self, transaction):
"""Append transaction to file."""
transactions = self.load_transactions()
transactions.append(transaction.to_dict() if hasattr(transaction, "to_dict") else transaction)
try:
with open(self.transactions_file, "w") as f:
json.dump(transactions, f, indent=2)
return True
except IOError as e:
print(f"Error saving transaction: {e}")
return False
def load_transactions(self):
"""Load transactions from JSON file."""
if not self.transactions_file.exists():
return []
try:
with open(self.transactions_file, "r") as f:
return json.load(f)
except (json.JSONDecodeError, IOError) as e:
print(f"Error loading transactions: {e}")
return []
Step 5: Library System Class
Main system class that ties everything together:
# library_system.py
from models.book import Book
from models.member import Member
from models.transaction import Transaction
from utils.file_handler import FileHandler
from collections import defaultdict
class LibrarySystem:
"""Main library management system."""
def __init__(self):
self.file_handler = FileHandler()
self.books = {}
self.members = {}
self.transactions = []
self._load_data()
def _load_data(self):
"""Load all data from files."""
# Load books
books_data = self.file_handler.load_books()
self.books = {book["isbn"]: Book.from_dict(book) for book in books_data}
# Load members
members_data = self.file_handler.load_members()
self.members = {member["member_id"]: Member.from_dict(member) for member in members_data}
# Load transactions
self.transactions = self.file_handler.load_transactions()
def add_book(self, isbn, title, author, year, copies=1):
"""Add a new book to the library."""
if isbn in self.books:
# Update existing book
self.books[isbn].total_copies += copies
self.books[isbn].available_copies += copies
else:
self.books[isbn] = Book(isbn, title, author, year, copies)
self._save_books()
return self.books[isbn]
def add_member(self, member_id, name, email):
"""Add a new member."""
if member_id in self.members:
raise ValueError("Member ID already exists")
self.members[member_id] = Member(member_id, name, email)
self._save_members()
return self.members[member_id]
def borrow_book(self, member_id, isbn):
"""Borrow a book."""
if member_id not in self.members:
raise ValueError("Member not found")
if isbn not in self.books:
raise ValueError("Book not found")
member = self.members[member_id]
book = self.books[isbn]
if member.has_book(isbn):
raise ValueError("Member already has this book")
if not book.is_available():
raise ValueError("Book not available")
# Perform transaction
if book.borrow() and member.borrow_book(isbn):
transaction = Transaction(
f"T{len(self.transactions) + 1:04d}",
member_id,
isbn,
"borrow"
)
self.transactions.append(transaction.to_dict())
self.file_handler.save_transaction(transaction)
self._save_books()
self._save_members()
return True
return False
def return_book(self, member_id, isbn):
"""Return a book."""
if member_id not in self.members:
raise ValueError("Member not found")
if isbn not in self.books:
raise ValueError("Book not found")
member = self.members[member_id]
book = self.books[isbn]
if not member.has_book(isbn):
raise ValueError("Member doesn't have this book")
# Perform transaction
if book.return_book() and member.return_book(isbn):
transaction = Transaction(
f"T{len(self.transactions) + 1:04d}",
member_id,
isbn,
"return"
)
self.transactions.append(transaction.to_dict())
self.file_handler.save_transaction(transaction)
self._save_books()
self._save_members()
return True
return False
def _save_books(self):
"""Save books to file."""
self.file_handler.save_books(list(self.books.values()))
def _save_members(self):
"""Save members to file."""
self.file_handler.save_members(list(self.members.values()))
def list_books(self):
"""List all books."""
return list(self.books.values())
def list_members(self):
"""List all members."""
return list(self.members.values())
def search_books(self, query):
"""Search books by title or author."""
query = query.lower()
results = []
for book in self.books.values():
if query in book.title.lower() or query in book.author.lower():
results.append(book)
return results
Step 6: Main Application
Command-line interface:
# main.py
from library_system import LibrarySystem
def print_menu():
"""Display main menu."""
print("\n" + "=" * 50)
print(" LIBRARY MANAGEMENT SYSTEM")
print("=" * 50)
print("1. Add Book")
print("2. Add Member")
print("3. Borrow Book")
print("4. Return Book")
print("5. List Books")
print("6. List Members")
print("7. Search Books")
print("8. Exit")
print("=" * 50)
def main():
"""Main application loop."""
library = LibrarySystem()
while True:
print_menu()
choice = input("Enter your choice (1-8): ").strip()
try:
if choice == "1":
isbn = input("ISBN: ")
title = input("Title: ")
author = input("Author: ")
year = int(input("Year: "))
copies = int(input("Copies (default 1): ") or "1")
book = library.add_book(isbn, title, author, year, copies)
print(f"Added: {book}")
elif choice == "2":
member_id = input("Member ID: ")
name = input("Name: ")
email = input("Email: ")
member = library.add_member(member_id, name, email)
print(f"Added: {member}")
elif choice == "3":
member_id = input("Member ID: ")
isbn = input("ISBN: ")
library.borrow_book(member_id, isbn)
print("Book borrowed successfully!")
elif choice == "4":
member_id = input("Member ID: ")
isbn = input("ISBN: ")
library.return_book(member_id, isbn)
print("Book returned successfully!")
elif choice == "5":
books = library.list_books()
print(f"\nTotal books: {len(books)}")
for book in books:
status = "Available" if book.is_available() else "Checked out"
print(f" {book} - {status} ({book.available_copies}/{book.total_copies})")
elif choice == "6":
members = library.list_members()
print(f"\nTotal members: {len(members)}")
for member in members:
print(f" {member} - {len(member.borrowed_books)} books borrowed")
elif choice == "7":
query = input("Search query: ")
results = library.search_books(query)
print(f"\nFound {len(results)} book(s):")
for book in results:
print(f" {book}")
elif choice == "8":
print("Thank you for using Library Management System!")
break
else:
print("Invalid choice. Please try again.")
except ValueError as e:
print(f"Error: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
if __name__ == "__main__":
main()
Key Features Demonstrated
This project showcases:
- Object-Oriented Design: Classes with inheritance, methods, and properties
- Error Handling: Try-except blocks and validation
- File I/O: JSON reading and writing
- Modules and Packages: Organized code structure
- Data Persistence: Saving and loading state
- Real-world Patterns: Practical application structure
Try It Yourself
Enhance the project:
- Add Features: Due dates, fines, reservations, book categories
- Improve UI: Better formatting, colors, search filters
- Add Validation: More robust input validation and error messages
- Statistics: Generate reports on popular books, active members
- Testing: Write unit tests for the classes and methods
Summary
Congratulations on completing the Intermediate Python course! You've learned advanced data structures, functional programming, object-oriented programming, modules, packages, and much more. This final project demonstrates how all these concepts work together in a real application.
You now have a solid foundation in intermediate Python programming. Continue practicing, building projects, and exploring Python's vast ecosystem. The skills you've learned will serve you well as you continue your programming journey.
What's Next?
You've completed the Intermediate Python course! Here are suggestions for continuing:
- Practice: Build more projects using the concepts you've learned
- Explore Libraries: Learn popular libraries (pandas, requests, flask, etc.)
- Advanced Topics: Study async programming, design patterns, and advanced OOP
- Specialize: Choose an area (web development, data science, automation)
- Contribute: Contribute to open-source projects
- Keep Learning: Python has a vast ecosystem—there's always more to discover!
Keep coding, keep building, and most importantly, have fun! Python is a powerful language, and you now have the intermediate skills to build amazing things with it.