Stage 5

🔹 Topic #48 | Stage 5 | Python | 📊 Level: Intermediate | 🌐 Coding5s.com
Concept: Raising Custom Exceptions (raise, assert statements)

The Agile Sprint & Business Rules: Stage 5 represents the pinnacle of the method. The AI adopts the persona of a Tech Lead handing over a “Jira Ticket” featuring a critical production bug. This simulates a real-world environment where developers must translate strict business logic (e.g., negative balances) into defensive code.

Ticket Title
PROJ-1025: Refactor Refund Processor to Include Strict Validations
Context & Ticket
Hello team,
We’ve detected a critical issue in our payments module. Currently, the process_refunds function processes any transaction sent to it, which has resulted in negative balances and the issuance of invalid refunds (even negative ones that end up adding money to the user). Additionally, the script was mistakenly executed in the “testing” environment while pointing to the real database.

Your task is to refactor this script. I need you to use assert to guarantee the system environment’s integrity before running the process, and implement custom exceptions using raise to stop illogical transactions based on business rules, without crashing the whole program.
Expected Behavior
  • The script must abort execution with an AssertionError if the API_MODE environment variable is not exactly “production”.
  • For transactions with an amount less than or equal to zero, a ValueError must be raised and caught.
  • For transactions where the refund amount exceeds the user’s current balance, a custom exception named InsufficientBalanceError must be raised and caught.
  • Only valid transactions should update the balances, and rejected transactions must print the reason for failure.
Support Files
For this project to run safely on your machine, you need to create a local configuration file. Create a .env file in your project root with the following content:
.env
API_MODE=testing
Base Code (V1.0)
Python
import os

# Simulating reading our .env file for this development environment
os.environ["API_MODE"] = "testing"

def process_refunds(transactions, balances):
    print(f"Current mode: {os.environ.get('API_MODE')}")
    for tx in transactions:
        user = tx["user"]
        amount_float = float(tx["amount"])
        
        # The balance is updated indiscriminately (BUG)
        balances[user] = balances.get(user, 0.0) - amount_float
        print(f"Refund of ${amount_float:,.2f} applied to {user}.")
        
    return balances

db_balances = {"u_101": 500.0, "u_102": 150.0}
pending_refunds = [
    {"user": "u_101", "amount": "50.0"},
    {"user": "u_102", "amount": "200.0"},
    {"user": "u_101", "amount": "-20.0"}
]

final_balances = process_refunds(pending_refunds, db_balances)
print("Final Balances:", final_balances)
Version 1.0 Console Output
Plaintext
Current mode: testing
Refund of $50.00 applied to u_101.
Refund of $200.00 applied to u_102.
Refund of $-20.00 applied to u_101.
Final Balances: {'u_101': 470.0, 'u_102': -50.0}

The Solution Boundary & Hints: To prevent passive reading, the prompt forces the student to implement the feature locally first. It provides strategic hints on logic (like how an assert works) but strictly hides the final code behind a visual barrier. This builds problem-solving resilience.

🛠️ YOUR TURN / TRY BEFORE YOU SCROLL
I highly recommend that you try to implement the solution in your own editor before reviewing the updated code. Think about how you would define a custom exception class inheriting from Exception and how you would use try…except inside the loop to avoid stopping the execution of the entire list.
Implementation Hint: Remember that the assert statement evaluates a boolean condition: assert condition, “Error message”. On the other hand, to raise a business logic error, use raise ValueError(“Your message”) followed by a try/except block to catch it. Don’t forget to change the simulated environment variable to “production” to pass the assertion!
SOLUTION BELOW BARRIER
(Do not continue reading unless you have your own solution ready or are completely stuck)
Updated Code (V1.1 – FULLY IMPLEMENTED)
Python
import os

# We adjust the environment to production so the assert validates correctly
os.environ["API_MODE"] = "production"

class InsufficientBalanceError(Exception):
    """Exception for refunds that exceed the user's balance."""
    pass

def process_secure_refunds(transactions, balances):
    # Critical state validation with assert
    assert os.environ.get("API_MODE") == "production", "The environment is not valid for processing financial operations."
    print(f"Current mode: {os.environ.get('API_MODE')}")
    
    for tx in transactions:
        usr = tx["user"]
        amount = float(tx["amount"])
        current_balance = balances.get(usr, 0.0)
        
        try:
            # Business rule validations with raise
            if amount <= 0:
                raise ValueError(f"The amount {amount} is invalid (must be greater than 0).")
            if amount > current_balance:
                raise InsufficientBalanceError(f"The amount {amount} exceeds the available balance of {current_balance}.")
            
            balances[usr] = current_balance - amount
            print(f"Success: Refund of ${amount:,.2f} processed for {usr}.")
            
        except (ValueError, InsufficientBalanceError) as err:
            print(f"Rejected ({usr}): {err}")
            
    return balances

db_balances = {"u_101": 500.0, "u_102": 150.0}
refunds = [
    {"user": "u_101", "amount": "50.0"},
    {"user": "u_102", "amount": "200.0"},
    {"user": "u_101", "amount": "-20.0"}
]

final_balances = process_secure_refunds(refunds, db_balances)

print("\nFinal System Balances:")
# We sort the items to ensure consistent and identical output every time
for usr, balance in sorted(final_balances.items()):
    print(f"{usr}: ${balance:,.2f}")

Architectural Review: The stage concludes with ‘Implementation Notes’. The AI doesn’t just provide the code; it explains why specific architectural decisions were made, elevating the student’s systems-thinking skills and technical vocabulary.

Implementation Notes
  • Creating a Custom Exception: Defining class InsufficientBalanceError(Exception): pass allows us to create specific semantic errors for our business domain, making the code more readable and traceable.
  • Use of assert: The assert line acts as a state “gatekeeper” in the development stage. It immediately guarantees that if a fundamental system configuration is missing or incorrect, the process will never iterate over sensitive data.
  • Control Flow with Exceptions: We employ a try/except block directly inside the for loop. When a raise is triggered, the execution automatically jumps to the respective except, printing the rejection reason and thus protecting the balances dictionary from receiving corrupt alterations.
Implemented Output
Plaintext
Current mode: production
Success: Refund of $50.00 processed for u_101.
Rejected (u_102): The amount 200.0 exceeds the available balance of 150.0.
Rejected (u_101): The amount -20.0 is invalid (must be greater than 0).

Final System Balances:
u_101: $450.00
u_102: $150.00
⚡ Coding5s System — Learn Programming by Writing Code
Scroll to Top