1. Project Overview & Architecture
Welcome to Major Project 2: File-Backed Expense & Task Tracker! In this project, you will elevate your Python applications from temporary in-memory programs to persistent software that saves and loads real data from the file system. You will apply key concepts from Modules 4–7: structured data (lists of dictionaries), JSON file handling, string manipulation, and comprehensive try-except exception management.
Project Core Objectives
- Persistent Storage: Load existing data on startup and automatically save changes to a local
.jsonfile on disk. - CRUD Operations: Implement full Create, Read, and Summary operations for financial transactions.
- Robust Error Recovery: Catch file corruption, missing files, and invalid numeric conversions without crashing the application.
- Data Aggregation: Calculate total spending, category breakdowns, and average transaction costs using Python loops and list comprehension techniques.
2. Functional Requirements & Feature Specifications
Your Expense Tracker must implement the following mandatory components:
| Feature Component | Functional Specification | Python Mechanism |
|---|---|---|
| File I/O Engine | Reads/writes standard JSON data files safely. | json.dump(), json.load(), pathlib.Path |
| Numeric Sanitization | Guarantees expenses are valid positive floating-point numbers. | try-except ValueError inside input loops. |
| Category Summary | Group expenses by category (Food, Utilities, Entertainment) with totals. | Dictionary aggregation (dict.get() logic). |
| Corrupted File Protection | Recovers cleanly if the local JSON file is unreadable or malformed. | json.JSONDecodeError catch blocks. |
3. Complete Reference Implementation
Review the complete Python implementation below to see how persistent JSON storage and input error handling work together:
import json
from pathlib import Path
from datetime import datetime
# Persistent storage file path
DATA_FILE = Path("expenses.json")
def load_expenses():
"""Loads expenses from JSON file. Returns an empty list if file doesn't exist or is corrupted."""
if not DATA_FILE.exists():
return []
try:
with open(DATA_FILE, "r", encoding="utf-8") as file:
return json.load(file)
except (json.JSONDecodeError, IOError) as err:
print(f"⚠️ Warning: Could not read '{DATA_FILE}' ({err}). Starting with fresh data.")
return []
def save_expenses(expenses):
"""Saves current expense list to JSON storage with clean formatting."""
try:
with open(DATA_FILE, "w", encoding="utf-8") as file:
json.dump(expenses, file, indent=4)
print("💾 Data saved successfully.")
except IOError as err:
print(f"❌ Critical Error: Unable to save data ({err}).")
def get_positive_float(prompt):
"""Prompts until a valid positive float is provided."""
while True:
try:
val = float(input(prompt).strip())
if val > 0:
return val
print("⚠️ Amount must be greater than $0.00.")
except ValueError:
print("⚠️ Invalid number format! Please enter a numeric value (e.g., 12.50).")
def add_expense(expenses):
"""Collects user details and appends a new expense record."""
print("\n--- ➕ Add New Expense ---")
title = input("Enter description/title: ").strip()
while not title:
print("⚠️ Title cannot be empty.")
title = input("Enter description/title: ").strip()
category = input("Enter category (e.g., Food, Transport, Utilities): ").strip().title()
if not category:
category = "General"
amount = get_positive_float("Enter amount ($): ")
date_str = datetime.now().strftime("%Y-%m-%d %H:%M")
record = {
"title": title,
"category": category,
"amount": round(amount, 2),
"date": date_str
}
expenses.append(record)
save_expenses(expenses)
print(f"✅ Added: '${title}' (${amount:.2f}) under [{category}]")
def view_expenses(expenses):
"""Displays all recorded expenses in a clean formatted table."""
print("\n--- 📝 All Recorded Expenses ---")
if not expenses:
print("No expenses recorded yet!")
return
print(f"{'#':<4} {'Date':<17} {'Category':<15} {'Title':<20} {'Amount':>10}")
print("-" * 70)
for idx, item in enumerate(expenses, start=1):
print(f"{idx:<4} {item['date']:<17} {item['category']:<15} {item['title']:<20} ${item['amount']:>9.2f}")
print("-" * 70)
def view_summary(expenses):
"""Calculates and displays financial summaries and category totals."""
print("\n--- 📊 Expense Summary & Analytics ---")
if not expenses:
print("No data available for summary calculation.")
return
total_spent = sum(item["amount"] for item in expenses)
avg_spent = total_spent / len(expenses)
# Aggregate by category
by_category = {}
for item in expenses:
cat = item["category"]
by_category[cat] = by_category.get(cat, 0.0) + item["amount"]
print(f"Total Transactions: {len(expenses)}")
print(f"Total Expenditure: ${total_spent:,.2f}")
print(f"Average Expense: ${avg_spent:,.2f}\n")
print("Spending by Category:")
for cat, cat_total in by_category.items():
percentage = (cat_total / total_spent) * 100
print(f" • {cat:<15}: ${cat_total:>8.2f} ({percentage:>5.1f}%)")
def main():
expenses = load_expenses()
while True:
print("\n==================================")
print(" 💰 FILE-BACKED EXPENSE TRACKER")
print("==================================")
print("1) Add New Expense")
print("2) View All Expenses")
print("3) View Summary & Category Stats")
print("4) Exit Program")
choice = input("\nSelect choice (1-4): ").strip()
if choice == "1":
add_expense(expenses)
elif choice == "2":
view_expenses(expenses)
elif choice == "3":
view_summary(expenses)
elif choice == "4":
print("\nExiting Expense Tracker. Goodbye!")
break
else:
print("⚠️ Invalid choice. Please select 1, 2, 3, or 4.")
if __name__ == "__main__":
main()
4. Common Project Pitfalls
⚠️ Project Watchlist:
- Forgetting File Encoding: Always specify
encoding="utf-8"when opening files to avoid platform-dependent character encoding issues between Windows and Mac/Linux. - Unsafe Float Conversion: Direct call to
float(input())without atry-exceptblock will crash the entire program if a user types currency symbols like"$15". - In-Memory Memory Disconnect: Modifying the
expenseslist in memory without callingsave_expenses()causes data loss if the terminal exits abruptly.
5. Interactive Project Workspace
Build & Test: Expense Tracker
Use the code editor below to customize your expense tracker. Try adding a feature to filter expenses by category or export a text receipt!
Major Project 2 Completed!
Fantastic work! Your Python scripts now possess persistent memory and file handling resilience.
6. Key Takeaways & Milestone Achievement
- JSON provides a human-readable, highly compatible structure for storing dictionary and list data permanently on disk.
- Wrap file reads inside
try-exceptblocks to safeguard against missing or corrupted storage files. - Input sanitization prevents string conversion crashes before bad data reaches your state storage.
🏆 Milestone 2 Complete
Congratulations on completing Major Project 2! Up next: **Major Project 3: Desktop Automated File & System Organizer** where you will write automation scripts to interact with your operating system!