Raising Custom Exceptions: Enforcing Domain Rules

Module 4 • Session 28 • Allocation: 1 Contact Hr

raise custom exceptions exception inheritance raise from error chaining

While Python includes many general-purpose exceptions, production applications need application-specific error handling. Using the raise statement along with custom exception classes allows code to express business logic constraints cleanly.

1. The raise Statement

The raise keyword triggers an exception manually when business conditions fail or invalid parameters are provided.

def validate_age(age): if not isinstance(age, int): raise TypeError("Age must be an integer value.") if age < 0 or age > 120: raise ValueError("Age must be between 0 and 120.") return True try: validate_age(-5) except ValueError as err: print(f"Validation failed: {err}")

2. Creating Custom Exception Classes

To define a custom exception, create a new class that inherits from Python's built-in Exception class.

class InsufficientBalanceError(Exception): """Raised when an account withdrawal exceeds available funds.""" def __init__(self, balance, amount): self.balance = balance self.amount = amount self.message = f"Cannot withdraw ${amount}. Current balance is ${balance}." super().__init__(self.message) class BankAccount: def __init__(self, balance): self.balance = balance def withdraw(self, amount): if amount > self.balance: raise InsufficientBalanceError(self.balance, amount) self.balance -= amount return self.balance account = BankAccount(100) try: account.withdraw(150) except InsufficientBalanceError as err: print(f"Transaction Error: {err}") print(f"Shortfall Amount: ${err.amount - err.balance}")

3. Exception Chaining (raise ... from)

When catching a lower-level error and throwing a domain-specific error, exception chaining preserves the original root cause trace.

class DatabaseConnectionError(Exception): """Raised when database connection fails.""" pass def fetch_data(): try: # Simulate network error raise ConnectionRefusedError("Server port 5432 unreachable.") except ConnectionRefusedError as original_error: raise DatabaseConnectionError("Failed to access user database.") from original_error try: fetch_data() except DatabaseConnectionError as err: print("Caught Top-Level Error:", err) print("Root Cause Error:", err.__cause__)
Best Practice: Keep exception class hierarchies structured. Group application errors under a base domain exception (e.g., AppError(Exception)) so downstream code can catch either general module errors or specific failures.
Pattern Syntax Primary Use Case
Direct Trigger raise ValueError("msg") Signal invalid inputs using standard types
Custom Exception class MyError(Exception): pass Signal domain-specific business rule violations
Re-raising raise inside except Log or audit errors while letting them propagate up
Chained Exception raise HighLevelError() from low_err Wrap low-level technical errors in domain context

🏋️ Try It Yourself: Practice Challenges

Challenge 1: Password Validator

Define two custom exceptions: PasswordTooShortError and MissingSpecialCharError. Write a validator function that raises them depending on password rules.

Challenge 2: App Base Exception Pattern

Create a base exception PaymentGatewayError and two sub-classes: CardExpiredError and InsufficientFundsError. Test catching both using the base exception class in a single except block.

Challenge 3: Exception Wrapping with Trace

Simulate reading a config file. Catch a KeyError and raise a custom ConfigurationError using from err to retain original context.

⚡ Interactive Sandbox (Custom Exceptions)
Console Output:
Click "Run Code" above to execute custom exception code...

📝 Knowledge Check Quiz

1. What class should user-defined custom exceptions inherit from?
2. Which keyword syntax is used for explicit exception chaining in Python?
3. What happens when you execute a bare `raise` statement inside an `except` block?
Advertisement
Horizontal Ad Banner Slot (728x90 / Responsive)