Search⌘ K
AI Features

Error Handling and Refinement

Explore how to enhance your Python budget tracker with error handling techniques like try-except blocks, input validation, and category checks. Learn to prevent crashes from invalid user input and make your app resilient, stable, and easy to use.

We kicked off this project by gathering requirements from a simulated client. Our AI co-pilot helped us quickly generate a functional core, and it worked flawlessly in our tests. The code accepted income and logged expenses just as we wanted. But in the real world, users aren't always perfect. What happens when they make a mistake?

Let's find out.

The widget below contains the code we just generated. Run it as instructed. When you see the prompt that says Enter your monthly income, type the words five hundred instead of entering the number 500.

income = 0.0
expenses = [] # Each item is a dict: {"category": str, "amount": float}

# Common categories to suggest to the user
allowed_categories = [
    "Food", "Rent", "Transport", "Entertainment",
    "Utilities", "Health", "Education", "Other"
]

def add_income(amount):
    """Set or update the monthly income (float > 0)."""
    global income
    income = float(amount)

def add_expense(category, amount):
    """Append an expense entry with category, amount (>0)."""
    new_expense = {
        "category": category,
        "amount": float(amount)
    }
    expenses.append(new_expense)

def delete_expense(idx):
    """Delete an expense by zero-based"""
    return expenses.pop(idx)

def total_spent(expenses_list=None):
    """Return the sum of all expense amounts from a list or the global store."""
    if expenses_list is None:
        expenses_list = expenses
    return sum(entry["amount"] for entry in expenses_list)

def compute_category_totals(expenses_list=None):
    """Return a dict mapping category -> total amount from expenses."""
    if expenses_list is None:
        expenses_list = expenses
    
    totals = {}
    for entry in expenses_list:
        category = entry["category"]
        amount = entry["amount"]
        totals[category] = totals.get(category, 0) + amount
    return totals

def print_report(income_value=None, expenses_list=None):
    """Print a formatted budget report with category totals and status line."""
    if income_value is None:
        income_value = income
    if expenses_list is None:
        expenses_list = expenses

    total = total_spent(expenses_list)
    remaining_balance = income_value - total
    category_totals = compute_category_totals(expenses_list)
    
    print("\n--- Budget Report ---")
    print(f"Income: ${income_value:.2f}")
    print(f"Total Spent: ${total:.2f}")
    
    print("\nCategory Spending:")
    for category, amount in category_totals.items():
        print(f"  - {category}: ${amount:.2f}")
    
    print("-" * 27)
    print(f"Remaining Balance: ${remaining_balance:.2f}")
    
def show_menu():
    """Display simple command menu for the CLI."""
    print("\n--- Budget Tracker Menu ---")
    print("1. Set Monthly Income")
    print("2. Add an Expense")
    print("3. Delete an Expense")
    print("4. Show Budget Report")
    print("5. Reset All Data")
    print("6. Exit")

# Helper function to list expenses
def list_expenses():
    """Display all expenses with their index."""
    if not expenses:
        print("No expenses recorded yet.")
        return
    print("\nCurrent Expenses:")
    for i, expense in enumerate(expenses):
        print(f"{i}: {expense['category']} - ${expense['amount']:.2f}")
        
def reset_data():
    """Clear income and expenses; return to a clean initial state."""
    global income, expenses
    income = 0.0
    expenses.clear()
    print("All budget data has been cleared.")

def run_cli():
    """Main loop: set income, add expenses, show report/export, reset, exit."""
    global income
    print("Welcome to Budget Tracker!")
    income_input = input("Enter your monthly income: ")
    add_income(income_input)

    while True:
        show_menu()
        choice = input("Choose an option: ").strip()

        if choice == "1":
            amount = input("Enter new monthly income: ")
            add_income(amount)
        elif choice == "2":
            # Display categories to the user
            print("\nAvailable Categories:")
            for i, cat in enumerate(allowed_categories):
                print(f"{i}: {cat}")
            
            # Prompt for category and amount
            category_idx_str = input("Enter the number for the category: ")
            category_idx = int(category_idx_str)

            if 0 <= category_idx < len(allowed_categories):
                category = allowed_categories[category_idx]
                amount = input(f"Enter expense amount for {category}: ")
                add_expense(category, amount)
                print("Expense added.")
            else:
                print("Invalid category number. Please try again.")

        elif choice == "3":
            list_expenses()  # Display list before prompting for deletion
            if not expenses:
                continue
            
            idx_str = input("Enter the index of the expense to delete: ")
            idx = int(idx_str)
            
            if 0 <= idx < len(expenses):
                deleted_expense = delete_expense(idx)
                print(f"Deleted expense: {deleted_expense['category']} - ${deleted_expense['amount']:.2f}")
            else:
                print("Invalid index. No expense deleted.")
        elif choice == "4":
            print_report()
        elif choice == "5":
            reset_data()
        elif choice == "6":
            print("Goodbye!")
            break
        else:
            print("Invalid choice. Please try again.")

if __name__ == "__main__":
    run_cli()
Test your project with wrong user input

The app crashed, and the terminal is filled with red text: ValueError: could not convert string to float: 'five hundred'.

What went wrong?

The AI-generated code expected a numeric value. It attempted to convert my text five hundred into a number using the float() function. Because the text was not numeric, the program raised a ValueError and stopped running.

The AI’s goal was to generate a solution based on the most common and ideal scenario. It is assumed that a user would always input a valid number because that’s what a budget tracker implies. This kind of code is great for a rapid prototype, but it’s brittle. It lacks the defensive layers that make a professional application resilient.

This is a classic example of an edge case, a situation that’s not a typical use but can easily occur. In this case, the edge case was a simple user typo. We also need to handle exceptions, the specific errors, like ValueError, that Python raises when it can’t perform an operation.

The solution: Building a bulletproof project

To fix this, we need to plan for possible mistakes and make the program handle them smoothly. That means adding edge case checks and exception handling. One way is to use a try-except block, which listens for errors like ValueError and prevents the program from crashing. Instead of letting the app crash, the except block will catch the error, print a friendly message like: That’s not a valid number. Please try again, and allow the program to continue running.

You’ve built a functional Budget Tracker, but as a good developer, your next step is to make it resilient. Our goal is to make the app bulletproof against unexpected or malicious user input. A great app doesn’t just work when everything goes right; it handles errors gracefully when things go wrong.

What we’ll fix

In this lesson, we’ll implement strong error handling to address common issues:

  • Parsing numbers: We’ll use try-except blocks to catch ValueError when a user enters text instead of a number, preventing the app from crashing.

  • Validation: We’ll add checks to ensure that income and expense amounts are positive numbers greater than zero.

  • Categories: We’ll handle case-insensitive input for categories and guide the user toward valid options.

  • Deleting expenses: We’ll validate user input to ensure it’s a valid integer that corresponds to an existing expense index.

  • User interface: We’ll refine the CLI to provide clear, informative messages for every action, whether it’s a success or a failure.

Let’s begin by enhancing the core functions of the app to make them more resilient.

Error-proof the heartbeat

We’ll start with the most critical functions: add_income() and add_expense(). These are the heartbeat of our app, so making them resilient is our top priority. The original functions assumed the user would always enter a valid number. We will now add try-except blocks to handle invalid input.

income = 0.0
# A list of expense dictionaries, e.g. [{"category": str, "amount": float}, ...]
expenses = []

allowed_categories = {
    "Food", "Rent", "Transport", "Entertainment",
    "Utilities", "Health", "Education", "Other"
}

_allowed_map = {c.lower(): c for c in allowed_categories}

def add_income(amount):
    """Set or update the monthly income (must be a positive number). Return True/False."""
    global income
    try:
        val = float(amount)
    except (TypeError, ValueError):
        print("Invalid income. Please enter a number like 1200 or 1200.50.")
        return False
    if val <= 0.0:
        print("Income must be greater than 0.")
        return False
    income = val
    print("Income set to ${0:.2f}".format(income))
    return True

def add_expense(category, amount):
    """Record an expense if category is allowed and amount > 0. Return True/False."""
    global expenses
    if category is None:
        print("Please provide a category.")
        return False

    key = str(category).strip().lower()
    if key not in _allowed_map:
        print("Invalid category. Choose from: " + ", ".join(allowed_categories))
        return False
    canon_cat = _allowed_map[key]

    try:
        val = float(amount)
    except (TypeError, ValueError):
        print("Invalid amount. Please enter a number like 12 or 12.50.")
        return False
    if val <= 0.0:
        print("Amount must be greater than 0.")
        return False

    entry = {"category": canon_cat, "amount": val}
    expenses.append(entry)
    print("Expense added: {0} - ${1:.2f}".format(canon_cat, val))
    return True

def run_cli():
    while True:
        print("Welcome to Budget Tracker — Capstone Project")
        print("1. Add income")
        print("2. Add expense")
        print("3. Exit")
        choice = input("Choose an option (1-3): ").strip()  # Trim any extra spaces

        if choice == "1":
            amount = input("Enter your monthly income: $")
            add_income(amount)
        elif choice == "2":
            print("\nNow, let's add an expense.")
            category = input("Enter the expense category (" + ", ".join(allowed_categories) + "): ")
            amount = input("Enter the expense amount: $")
            add_expense(category, amount)
        elif choice == "3":
            print("Exiting Budget Tracker.")
            break
        else:
            print("Invalid choice. Please select a valid option.")

if __name__ == "__main__":
    run_cli()
Adding expenses

In the above code:

  • _allowed_map:

    • The _allowed_map is a lookup table that ensures all categories are stored consistently. It converts user input like food or FoOd into the standardized Food, preventing duplicates and ensuring accurate reporting.

  • Add income function:

    • This function now uses a try-except block to prevent the app from crashing if a user enters a non-numeric value (e.g., abc). It also validates that the income amount is a positive number. If the input fails either check, the function provides a clear error message and does not change the user’s income, returning False to signal failure.

  • Add expense function:

    • This function performs two critical checks before adding a new expense:

      • Category validation: It checks the user’s input against the _allowed_map. If the category is not recognized, the function rejects it and lists the available options, preventing invalid data from being added.

      • Amount validation: Similar to add_income(), it uses a try-except block to catch non-numeric input and ensure the amount is a positive number.

    • Only when both the category and amount are validated does the function successfully add the expense to the tracker, ensuring clean and reliable data.

Why this helps: both functions now never crash on bad input; they return False and keep state unchanged.

Ship the remaining pieces

Now, since we have set the base, it’s time for you to take own from here and build the rest. You can use AI Copilot to scaffold the rest of the implementation:

  • delete_expense

  • total_spent

  • compute_category_totals

  • print_report

  • show_menu

  • reset_data

  • run_cli

Powered by AI
10 Prompts Remaining
Prompt AI WidgetOur tool is designed to help you to understand concepts and ask any follow up questions. Ask a question to get started.
AI agent to add exception handling and handling edge cases for the rest of the functions

Test your code

The next step is to test the code you have generated. Add it to the widget below and see how it works.

income = 0.0
# A list of expense dictionaries, e.g. [{"category": str, "amount": float}, ...]
expenses = []

allowed_categories = {
    "Food", "Rent", "Transport", "Entertainment",
    "Utilities", "Health", "Education", "Other"
}

_allowed_map = {c.lower(): c for c in allowed_categories}

def add_income(amount):
    """Set or update the monthly income (must be a positive number). Return True/False."""
    global income
    try:
        val = float(amount)
    except (TypeError, ValueError):
        print("Invalid income. Please enter a number like 1200 or 1200.50.")
        return False
    if val <= 0.0:
        print("Income must be greater than 0.")
        return False
    income = val
    print("Income set to ${0:.2f}".format(income))
    return True

def add_expense(category, amount):
    """Record an expense if category is allowed and amount > 0. Return True/False."""
    global expenses
    if category is None:
        print("Please provide a category.")
        return False

    key = str(category).strip().lower()
    if key not in _allowed_map:
        print("Invalid category. Choose from: " + ", ".join(allowed_categories))
        return False
    canon_cat = _allowed_map[key]

    try:
        val = float(amount)
    except (TypeError, ValueError):
        print("Invalid amount. Please enter a number like 12 or 12.50.")
        return False
    if val <= 0.0:
        print("Amount must be greater than 0.")
        return False

    entry = {"category": canon_cat, "amount": val}
    expenses.append(entry)
    print("Expense added: {0} - ${1:.2f}".format(canon_cat, val))
    return True

# Add your code here

def run_cli():
    while True:
        print("Welcome to Budget Tracker — Capstone Project")
        print("1. Add income")
        print("2. Add expense")
        print("3. Exit")
        choice = input("Choose an option (1-3): ").strip()  # Trim any extra spaces

        if choice == "1":
            amount = input("Enter your monthly income: $")
            add_income(amount)
        elif choice == "2":
            print("\nNow, let's add an expense.")
            category = input("Enter the expense category (" + ", ".join(allowed_categories) + "): ")
            amount = input("Enter the expense amount: $")
            add_expense(category, amount)
        elif choice == "3":
            print("Exiting Budget Tracker.")
            break
        else:
            print("Invalid choice. Please select a valid option.")

if __name__ == "__main__":
    run_cli()
Test your edge cases and exception handling

If you’re stuck, click the “Show Complete Solution” button.

You've successfully transformed a basic prototype into a powerful command-line application. The Budget Tracker now handles unexpected user input gracefully, providing a stable and reliable user experience.