File Handling Capstone Projects

๐Ÿ Python 3.12+ ๐ŸŸข Chapter 29 of 65 ๐Ÿ“‚ Phase 6: Exception and File Handling ๐Ÿ“… 2026 Edition
๐Ÿ“Œ Covered in this chapter: 4 Projects ยท 1. File Notes App ยท 2. Contact Book ยท 3. CSV Expense Tracker ยท 4. JSON Student Records
Build four complete, production-grade, file-persisted applications in Python: a timestamped text notes app, an interactive contact book, a CSV-based expense tracker with budget analytics, and a JSON-backed student records CRUD management system.
1Project 1: File-Based Timestamped Notes Application

A persistent, timestamped command-line note-taking application that logs notes to a text file with automated datetime headers and keyword searching:

๐Ÿ’ป Project 1: Persistent Timestamped Notes Engine
# =========================================================================
# PROJECT 1: FILE-BASED TIMESTAMPED NOTES APPLICATION
# =========================================================================
import datetime as dt
from pathlib import Path

NOTES_FILE = Path("daily_notes.txt")

def add_note(note_text):
    """Append a new note with timestamp."""
    timestamp = dt.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    with open(NOTES_FILE, "a", encoding="utf-8") as f:
        f.write(f"[{timestamp}] {note_text.strip()}\n")
    return "โœ… Note saved successfully!"

def view_all_notes():
    """Read and display all notes from file."""
    if not NOTES_FILE.exists():
        return "โ„น๏ธ No notes recorded yet."
    with open(NOTES_FILE, "r", encoding="utf-8") as f:
        return f.read().strip()

def search_notes(keyword):
    """Search for notes containing a specific keyword."""
    if not NOTES_FILE.exists():
        return []
    matches = []
    with open(NOTES_FILE, "r", encoding="utf-8") as f:
        for line in f:
            if keyword.lower() in line.lower():
                matches.append(line.strip())
    return matches

# Run Notes App Demonstration:
print("--- ๐Ÿ“ Notes Application Demo ---")
add_note("Studied Python Exception Handling (try-except-else-finally)")
add_note("Built CSV Expense Tracker with DictReader")
add_note("Reviewing FastAPI background tasks for production")

print("\n--- All Saved Notes ---")
print(view_all_notes())

print("\n--- Search Results for 'Python' ---")
for note in search_notes("Python"):
    print("โ€ข", note)
๐Ÿ” Architectural Features:
  • Uses append mode ("a") to safely preserve all historical entries.
  • Case-insensitive streaming search using keyword.lower() in line.lower() without loading unnecessary memory.
2Project 2: Interactive Contact Book Manager

A file-backed Contact Book manager supporting contact addition, search by name or phone, and listing contacts alphabetically:

๐Ÿ’ป Project 2: JSON Contact Book Manager Engine
# =========================================================================
# PROJECT 2: FILE-BACKED CONTACT BOOK APPLICATION
# =========================================================================
import json
from pathlib import Path

CONTACTS_FILE = Path("contacts_db.json")

def load_contacts():
    """Load contacts from JSON file safely."""
    if not CONTACTS_FILE.exists():
        return {}
    try:
        with open(CONTACTS_FILE, "r", encoding="utf-8") as f:
            return json.load(f)
    except (json.JSONDecodeError, OSError):
        return {}

def save_contacts(contacts_dict):
    """Save contacts dictionary to JSON file."""
    with open(CONTACTS_FILE, "w", encoding="utf-8") as f:
        json.dump(contacts_dict, f, indent=4)

def add_contact(name, phone, email, category="Personal"):
    """Add or update a contact record."""
    contacts = load_contacts()
    contacts[name.strip().title()] = {
        "phone": phone.strip(),
        "email": email.strip().lower(),
        "category": category
    }
    save_contacts(contacts)
    return f"โœ… Contact '{name}' saved successfully!"

def search_contact(search_term):
    """Find contact by name substring or phone."""
    contacts = load_contacts()
    results = {}
    for name, details in contacts.items():
        if search_term.lower() in name.lower() or search_term in details["phone"]:
            results[name] = details
    return results

# Run Contact Book Demonstration:
print("--- ๐Ÿ“ฑ Contact Book Manager Demo ---")
add_contact("Balaji Dev", "+91 98765 43210", "balaji@example.com", "Work")
add_contact("Alex Smith", "+1 555 123 4567", "alex@example.com", "Friends")
add_contact("Chloe Davis", "+44 20 7946 0991", "chloe@techcorp.com", "Work")

print("\n--- Search Results for 'balaji' ---")
print(json.dumps(search_contact("balaji"), indent=2))
๐Ÿ” Resilience Features:

Handles missing database files gracefully by returning an empty dictionary ({}) and catching corrupted JSON with json.JSONDecodeError.

3Project 3: Production Expense Tracker Using CSV

A complete financial expense tracking system that logs transactions to CSV, calculates total expenditures, and aggregates category summaries:

๐Ÿ’ป Project 3: CSV Expense Tracker and Analytics Engine
# =========================================================================
# PROJECT 3: FINANCIAL EXPENSE TRACKER USING CSV
# =========================================================================
import csv
import datetime as dt
from pathlib import Path

EXPENSES_FILE = Path("monthly_expenses.csv")
FIELDNAMES = ["date", "category", "description", "amount"]

def initialize_expense_file():
    """Ensure CSV file exists with proper headers."""
    if not EXPENSES_FILE.exists():
        with open(EXPENSES_FILE, "w", newline="", encoding="utf-8") as f:
            writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
            writer.writeheader()

def log_expense(category, description, amount):
    """Log an expense entry to CSV."""
    initialize_expense_file()
    entry = {
        "date": dt.date.today().isoformat(),
        "category": category.title(),
        "description": description.strip(),
        "amount": f"{float(amount):.2f}"
    }
    with open(EXPENSES_FILE, "a", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDNAMES)
        writer.writerow(entry)
    return f"โœ… Logged Rs.{float(amount):.2f} for '{description}'"

def generate_expense_analytics():
    """Calculate total spending and category breakdown."""
    if not EXPENSES_FILE.exists():
        return 0.0, {}
        
    total_spent = 0.0
    category_totals = {}
    
    with open(EXPENSES_FILE, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        for row in reader:
            amt = float(row["amount"])
            cat = row["category"]
            total_spent += amt
            category_totals[cat] = category_totals.get(cat, 0.0) + amt
            
    return round(total_spent, 2), category_totals

# Run Expense Tracker Demonstration:
print("--- ๐Ÿ’ฐ Financial Expense Tracker Demo ---")
log_expense("Food", "Grocery Supermart Shopping", 1250.00)
log_expense("Utilities", "High-Speed Internet Bill", 999.00)
log_expense("Food", "Team Coffee & Snacks", 350.00)
log_expense("Education", "Python Masterclass Subscription", 1499.00)

total, breakdown = generate_expense_analytics()
print(f"\n๐Ÿ“Š Total Monthly Expenditure: Rs.{total:,.2f}")
print("--- Category Breakdown ---")
for cat, amt in breakdown.items():
    pct = (amt / total) * 100 if total > 0 else 0
    print(f"โ€ข {cat:12}: Rs.{amt:,.2f} ({pct:.1f}%)")
๐Ÿ” Analytics Breakdown:

Uses csv.DictReader to stream expense rows and computes aggregate percentage breakdowns per category dynamically.

4Project 4: JSON Student Academic Records Management System

A comprehensive academic records CRUD engine managing student enrollments, subject mark sheets, GPA calculations, and student record exports:

๐Ÿ’ป Project 4: JSON Student Records CRUD System
# =========================================================================
# PROJECT 4: JSON STUDENT ACADEMIC RECORDS SYSTEM
# =========================================================================
import json
from pathlib import Path

STUDENT_DB = Path("students_database.json")

def load_db():
    if not STUDENT_DB.exists(): return {"students": {}}
    with open(STUDENT_DB, "r", encoding="utf-8") as f:
        return json.load(f)

def save_db(data):
    with open(STUDENT_DB, "w", encoding="utf-8") as f:
        json.dump(data, f, indent=4)

def register_student(student_id, name, branch):
    """Create a new student profile."""
    db = load_db()
    if student_id in db["students"]:
        return f"โŒ Student ID {student_id} already exists!"
        
    db["students"][student_id] = {
        "name": name,
        "branch": branch,
        "marks": {},
        "gpa": 0.0
    }
    save_db(db)
    return f"๐ŸŽ“ Student '{name}' registered successfully under ID: {student_id}"

def record_marks(student_id, subject, score):
    """Add subject score and recalculate GPA."""
    db = load_db()
    student = db["students"].get(student_id)
    if not student:
        return f"โŒ Student {student_id} not found!"
        
    student["marks"][subject] = score
    all_scores = list(student["marks"].values())
    student["gpa"] = round(sum(all_scores) / len(all_scores), 2)
    save_db(db)
    return f"โœ… Recorded {subject}: {score}/100 | New GPA: {student['gpa']}"

def get_student_report_card(student_id):
    """Generate a clean formatted report card."""
    db = load_db()
    student = db["students"].get(student_id)
    if not student: return "Student not found!"
    
    lines = [
        "=" * 45,
        f"๐Ÿ“‹ REPORT CARD: {student['name']} (ID: {student_id})",
        f"Branch: {student['branch']} | Overall GPA: {student['gpa']}%",
        "-" * 45,
        "Subject Scores:"
    ]
    for sub, mark in student["marks"].items():
        lines.append(f"  โ€ข {sub:20}: {mark}/100")
    lines.append("=" * 45)
    return "\n".join(lines)

# Run Student Records Demonstration:
print("--- ๐ŸŽ“ Student Records Management System ---")
register_student("STU101", "Balaji", "Computer Science")
record_marks("STU101", "Python Programming", 98)
record_marks("STU101", "Data Structures", 92)
record_marks("STU101", "Database Systems", 95)

print(get_student_report_card("STU101"))
๐Ÿ” Production Quality Features:
  • Full CRUD persistence in structured JSON.
  • Automatic re-calculation of dynamic metrics (GPA).
  • Modular functions with input validation and clean error returns.
โš ๏ธ Common Developer Pitfall: Failing to Handle Missing or Corrupted JSON Database Files

If your JSON file is empty or contains invalid syntax, json.load() raises json.JSONDecodeError: Expecting value. Always wrap file loading in a try-except block and provide an empty dictionary fallback.

๐Ÿ’ป Hands-on Interactive Practice Challenge

Create a mini bookmark manager that stores website URLs with categories in a JSON file "bookmarks.json" and prints all bookmarked sites.

Python 3 Practice Challenge โ–ถ Run in Compiler
import json
from pathlib import Path

bookmarks_file = Path("bookmarks.json")

bookmarks = {
    "Python": ["https://python.org", "https://docs.python.org"],
    "Compilers": ["https://www.ourcompiler.com"],
    "Tools": ["https://github.com", "https://stackoverflow.com"]
}

with open(bookmarks_file, "w", encoding="utf-8") as f:
    json.dump(bookmarks, f, indent=2)

print("Saved Bookmarks:")
with open(bookmarks_file, "r", encoding="utf-8") as f:
    data = json.load(f)
    for cat, urls in data.items():
        print(f"๐Ÿ“ {cat}:")
        for u in urls:
            print(f"   ๐Ÿ”— {u}")
Run This Challenge in Online Python IDE โ†’
โ“ Frequently Asked Questions (FAQ)

Q How do I choose between saving data in Text, CSV, or JSON?

Use Text (.txt) for unformatted logs and timestamped notes. Use CSV (.csv) for flat tabular rows and spreadsheets. Use JSON (.json) for nested objects, configuration files, and REST API data interchange.

Q How can I ensure atomic writes to prevent data corruption during crashes?

Write data to a temporary file (e.g. "data.json.tmp") first, and then atomically rename it to "data.json" using Path.rename(). Renames are atomic on modern operating systems.

Q When should I migrate from JSON files to a database like SQLite or PostgreSQL?

Migrate to a database when: (1) multiple concurrent threads/users write simultaneously, (2) dataset exceeds tens of megabytes, or (3) you need complex relational SQL joins and indexing.

OC
Written by Our Compiler Technical Editorial Team
Reviewed for accuracy & tested on Python 3.12+ runtime ยท Last updated August 2026