Putting It All Together

Daniel Sarney
Python Intermediate

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:

  1. Add Features: Due dates, fines, reservations, book categories
  2. Improve UI: Better formatting, colors, search filters
  3. Add Validation: More robust input validation and error messages
  4. Statistics: Generate reports on popular books, active members
  5. 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:

  1. Practice: Build more projects using the concepts you've learned
  2. Explore Libraries: Learn popular libraries (pandas, requests, flask, etc.)
  3. Advanced Topics: Study async programming, design patterns, and advanced OOP
  4. Specialize: Choose an area (web development, data science, automation)
  5. Contribute: Contribute to open-source projects
  6. 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.

Video Tutorial Coming Soon

The video tutorial for this lesson will be available soon. Check back later!

Continue Learning

12 lessons
Python For Beginners

Start your learning journey with Python For Beginners. This course includes comprehensive lessons covering everything you need to know.