Skip to main content

Raising and Custom Exceptions

So far, you've written code that catches exceptions—code that anticipates errors and handles them gracefully. But what about the functions you write? How do they signal errors to their callers?

This lesson teaches you to think defensively from the function designer's perspective. You'll start by designing domain exceptions for real-world systems, learn from AI about when to create custom vs use built-in exceptions, challenge AI with exception inheritance and __init__ customization, and finally build a custom exception library for domain modeling.


Part 1: Design Domain Exceptions

Your Role: Domain modeler designing exception types for business logic

Before learning syntax, think about error categories in real systems. Professional developers design exception hierarchies that communicate business domain knowledge.

Domain Design Exercise: User Authentication System

You're building a user authentication system. Identify what can go wrong and design appropriate exception types.

System Requirements:

  • Users must provide username and password
  • Passwords must meet complexity requirements (8+ characters, mix of types)
  • User accounts can be locked after failed attempts
  • User accounts can expire
  • Sessions can time out

Your Design Task

Before writing code, design exception types for each error scenario:

Error ScenarioException NameParent ClassError Message Pattern
Username not foundUserNotFoundErrorException"User '{username}' does not exist"
Password incorrectInvalidCredentialsErrorException"Invalid password for user '{username}'"
Password too weakWeakPasswordErrorValueError"Password must be 8+ chars with mixed types"
Account lockedAccountLockedErrorException"Account '{username}' is locked (too many failed attempts)"
Account expiredAccountExpiredErrorException"Account '{username}' expired on {date}"
Session timeoutSessionExpiredErrorException"Session expired after {minutes} minutes of inactivity"

Design Questions

For each exception, answer:

  1. Should this be a custom exception or use a built-in?

    • Custom if it represents domain-specific business logic
    • Built-in if it represents a generic validation error
  2. What parent class should it inherit from?

    • Exception for most custom exceptions
    • ValueError if it's specifically about invalid input values
    • RuntimeError if it's about state violations
  3. What information should the error message include?

    • What went wrong (the error)
    • What was expected (the rule)
    • What value caused the error (the context)

Your Design Deliverable

Create a file called auth_exceptions_design.md:

# Authentication System Exception Design

## Exception Hierarchy

Exception ├── AuthenticationError (base for all auth errors) │ ├── UserNotFoundError │ ├── InvalidCredentialsError │ ├── AccountLockedError │ └── AccountExpiredError ├── ValueError │ └── WeakPasswordError └── SessionExpiredError


## Exception Specifications

### UserNotFoundError
**When raised**: User lookup fails in database
**Parent**: AuthenticationError
**Required info**: username attempted
**Error message**: `"User '{username}' does not exist in the system"`
**Recovery strategy**: Prompt user to check spelling or register

### InvalidCredentialsError
**When raised**: Password doesn't match stored hash
**Parent**: AuthenticationError
**Required info**: username (NOT password for security)
**Error message**: `"Invalid credentials for user '{username}'"`
**Recovery strategy**: Prompt user to retry or reset password

[Continue for all exception types...]

## Design Rationale

### Why AuthenticationError base class?
Allows callers to catch all auth-related errors with single except block:
```python
try:
authenticate(username, password)
except AuthenticationError:
# Handle any authentication failure
except ValueError:
# Handle validation errors (weak password)

Why WeakPasswordError inherits from ValueError?

Password validation is input validation—ValueError is semantically correct. Users familiar with ValueError will understand this pattern.


**Deliverable**: Complete `auth_exceptions_design.md` with exception hierarchy, specifications for all 6 exception types, and design rationale explaining your inheritance choices.

---

## Part 2: Learn When to Create Custom vs Use Built-In

**Your Role**: Student learning exception design principles from AI Teacher

Now that you've designed exceptions, understand when custom exceptions add value vs when built-ins suffice.

### AI Teaching Prompt

Ask your AI companion:

> "I've designed a custom exception hierarchy for an authentication system:
> - AuthenticationError (base)
> - UserNotFoundError
> - InvalidCredentialsError
> - AccountLockedError
> - WeakPasswordError (inherits from ValueError)
>
> Explain:
> 1. When should I create custom exceptions vs using built-in ValueError, TypeError, or RuntimeError?
> 2. What are the benefits of a base exception class like AuthenticationError?
> 3. Show me how callers can catch exceptions at different levels of granularity (specific error vs category of errors).
> 4. Is WeakPasswordError better as a custom exception or inheriting from ValueError? What are the tradeoffs?"

### What You'll Learn from AI

**Expected AI Response** (summary):

**When to create custom exceptions**:
- Domain-specific business logic errors (not generic validation)
- When you want callers to handle different error types differently
- When error messages need domain-specific context
- When you want to add custom attributes (e.g., `username`, `lock_reason`)

**Benefits of base exception classes**:
- Callers can catch all related errors with one except block
- Organizes exception hierarchy by domain
- Makes error handling intent clearer
- Allows for shared behavior (custom `__init__`, `__str__`)

**Granularity example**:
```python
# Catch specific error
try:
authenticate(user, pwd)
except UserNotFoundError:
print("Create account?")
except InvalidCredentialsError:
print("Forgot password?")

# Catch category of errors
try:
authenticate(user, pwd)
except AuthenticationError:
print("Authentication failed")

# Catch validation errors separately
try:
set_password(new_pwd)
except WeakPasswordError:
print("Password too weak")
except ValueError:
print("Invalid input")

WeakPasswordError tradeoffs:

  • Inherit from ValueError: Semantically correct (input validation), familiar to Python developers
  • Custom exception: More explicit about domain, easier to catch specifically
  • Recommendation: Inherit from ValueError for input validation, custom for business logic

Convergence Activity

After AI explains, test your understanding:

Ask AI: "Show me an authentication function that raises all these exception types. For each exception, include context (username, attempt count, etc.) in the error message."

Example AI might show:

Loading Python environment...

Your turn: Explain back to AI:

  • Why does UserNotFoundError.__init__ call super().__init__(message)?
  • What's the benefit of storing username and attempt_count as instance attributes?
  • How would a caller differentiate between UserNotFoundError and InvalidCredentialsError?

Deliverable: Write a 1-paragraph summary explaining when to create custom exceptions (domain logic) vs when to use built-in exceptions (generic validation), with examples from the authentication system.


Part 3: Challenge AI with Exception Inheritance and __init__ Customization

Your Role: Student teaching AI by exploring advanced exception patterns

Now reverse the roles. You'll design challenges that test AI's understanding of exception customization, particularly __init__ methods and instance attributes.

Challenge Design Pattern

Create scenarios where:

  1. Custom exceptions need additional context beyond a message
  2. __init__ methods accept domain-specific parameters
  3. Exception attributes enable programmatic error handling

Challenge 1: Exception with Validation Context

Your prompt to AI:

"I want an exception that captures multiple validation errors. Design a ValidationError exception that:

  • Accepts a list of field names and error messages
  • Stores them as instance attributes
  • Formats them nicely in the exception message
  • Allows callers to iterate through individual field errors

Show me the exception class and an example of how to use it."

Expected AI Response:

Loading Python environment...

Your follow-up: "Now show me how to extend this to support error codes for internationalization."

Challenge 2: Exception Chaining with Context

Your prompt to AI:

"Explain exception chaining with raise ... from e. Show me an example where:

  • An inner function raises a json.JSONDecodeError
  • An outer function catches it and raises a custom ConfigurationError
  • The original exception is preserved for debugging
  • The new exception adds business context

Why is this better than just catching and re-raising the original exception?"

Expected AI Response:

Loading Python environment...

Key insight: Exception chaining preserves debugging information while adding business context.

Challenge 3: Exception Hierarchy for E-Commerce

Your prompt to AI:

"Design an exception hierarchy for an e-commerce order system. The system has:

  • Product not found
  • Insufficient inventory
  • Invalid payment method
  • Payment declined
  • Shipping address invalid

Create:

  1. A base OrderError exception
  2. Subclasses for each error type
  3. Custom __init__ methods with relevant attributes
  4. Example usage showing how callers can catch at different levels

Explain: which exceptions should be recoverable (user can retry) vs fatal (cancel order)?"

Expected AI Response (structure):

Loading Python environment...

Deliverable: Document three advanced exception challenges you posed to AI, AI's solutions, and your analysis of:

  • When to add custom __init__ methods
  • When to store exception context as instance attributes
  • When to use exception chaining vs simple re-raising

Part 4: Build Custom Exception Library for Domain Modeling

Your Role: Library designer creating reusable exception types

Now integrate everything into a production-ready exception library that models domain concepts clearly.

Your Custom Exception Library

Create a Python file called domain_exceptions.py with these patterns:

Loading Python environment...

Library Requirements

Your exception library must include:

  1. Authentication domain (4 exception types)

    • Base AuthenticationError
    • User-specific errors with context
    • Custom __init__ methods storing username, timestamps, attempt counts
  2. Validation domain (1 exception type)

    • ValidationError accepting dict of field errors
    • Helper methods (has_error, get_error)
    • Useful for form validation
  3. File processing domain (3 exception types)

    • Base FileProcessingError
    • Format and corruption errors
    • Line numbers and file context
  4. Business logic domain (5 exception types)

    • E-commerce order errors
    • Inventory and payment errors
    • Retry logic and context for recovery
  5. Example usage for each domain

    • Demonstrates exception raising
    • Shows attribute access
    • Illustrates recovery strategies

Validation with AI

Once your library is complete, validate it by asking AI:

"Review my custom exception library. For each exception:

  1. Is the inheritance hierarchy appropriate?
  2. Are the custom __init__ parameters useful for callers?
  3. Do the error messages provide enough context?
  4. What exception types am I missing for these domains?
  5. Are there any patterns I should add (e.g., exception chaining, error codes)?"

Deliverable: Complete domain_exceptions.py with all 4 domains, comprehensive __init__ methods, clear error messages, and example usage for each exception type.


Try With AI

Ready to design custom exceptions that communicate domain logic and enable intelligent error recovery?

🔍 Explore Custom vs Built-In:

"Compare these approaches: 1) raising ValueError for invalid email vs 2) creating InvalidEmailError exception. For each, explain when it's appropriate, what callers can do differently, and how error messages differ. When does custom exception add value?"

🎯 Practice Exception Hierarchies:

"Help me design exceptions for a payment system: payment declined (insufficient funds, card expired, fraud detected), payment timeout, invalid payment method. Create a hierarchy with base PaymentError. Show how callers catch specific errors vs all payment errors."

🧪 Test Exception Chaining:

"Show me a function that loads JSON config, catches JSONDecodeError, and raises custom ConfigurationError with 'from e' chaining. Explain what's preserved in the traceback and why this is better than just raising ConfigurationError without chaining. How do callers access the original exception?"

🚀 Apply to API Client:

"I'm building an API client that can fail with: network timeout, invalid response format, authentication error, rate limit exceeded. Design custom exceptions with __init__ parameters that store context (URL, status code, retry-after). Show how callers use exception attributes for retry logic."