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 Scenario | Exception Name | Parent Class | Error Message Pattern |
|---|---|---|---|
| Username not found | UserNotFoundError | Exception | "User '{username}' does not exist" |
| Password incorrect | InvalidCredentialsError | Exception | "Invalid password for user '{username}'" |
| Password too weak | WeakPasswordError | ValueError | "Password must be 8+ chars with mixed types" |
| Account locked | AccountLockedError | Exception | "Account '{username}' is locked (too many failed attempts)" |
| Account expired | AccountExpiredError | Exception | "Account '{username}' expired on {date}" |
| Session timeout | SessionExpiredError | Exception | "Session expired after {minutes} minutes of inactivity" |
Design Questions
For each exception, answer:
-
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
-
What parent class should it inherit from?
Exceptionfor most custom exceptionsValueErrorif it's specifically about invalid input valuesRuntimeErrorif it's about state violations
-
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__callsuper().__init__(message)? - What's the benefit of storing
usernameandattempt_countas instance attributes? - How would a caller differentiate between
UserNotFoundErrorandInvalidCredentialsError?
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:
- Custom exceptions need additional context beyond a message
__init__methods accept domain-specific parameters- 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
ValidationErrorexception 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:
- A base
OrderErrorexception- Subclasses for each error type
- Custom
__init__methods with relevant attributes- 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:
-
Authentication domain (4 exception types)
- Base
AuthenticationError - User-specific errors with context
- Custom
__init__methods storing username, timestamps, attempt counts
- Base
-
Validation domain (1 exception type)
ValidationErroraccepting dict of field errors- Helper methods (
has_error,get_error) - Useful for form validation
-
File processing domain (3 exception types)
- Base
FileProcessingError - Format and corruption errors
- Line numbers and file context
- Base
-
Business logic domain (5 exception types)
- E-commerce order errors
- Inventory and payment errors
- Retry logic and context for recovery
-
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:
- Is the inheritance hierarchy appropriate?
- Are the custom
__init__parameters useful for callers?- Do the error messages provide enough context?
- What exception types am I missing for these domains?
- 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."