Learning

Custom Exceptions

Custom Exceptions

Why Custom Exceptions?

Built-in exceptions are too generic for business logic:

  • ValueError could mean anything (negative age, invalid email, wrong format...)
  • Custom exceptions let callers catch SPECIFIC domain errors

Defining Custom Exceptions

python
class InvalidAgeError(Exception): pass # Raising it raise InvalidAgeError('Age cannot be negative')

Adding Custom Attributes

python
def __init__(self, field, message): self.field = field self.message = message super().__init__(f'{field}: {message}') try: raise ValidationError('email', 'Invalid format') except ValidationError as e: print(e) # email: Invalid format print(e.field) # 'email' print(e.message) # 'Invalid format'

Exception Hierarchies

python
class AppError(Exception): pass # Sub-categories class NetworkError(AppError): pass class DatabaseError(AppError): pass # Specific errors class ConnectionTimeoutError(NetworkError): pass class QueryError(DatabaseError): pass # Caller can catch at any level try: raise ConnectionTimeoutError('Server not responding') except ConnectionTimeoutError: print('Handle timeout specifically') except NetworkError: print('Handle any network issue') except AppError: print('Handle any app error')

Real-World Example: Bank Account

python
def __init__(self, balance, amount): self.balance = balance self.amount = amount super().__init__(f'Cannot withdraw {amount}. Balance: {balance}') class BankAccount: def __init__(self, balance=0): self.balance = balance def withdraw(self, amount): if amount <= 0: raise ValueError('Amount must be positive') if amount > self.balance: raise InsufficientFundsError(self.balance, amount) self.balance -= amount try: acc = BankAccount(100) acc.withdraw(150) except InsufficientFundsError as e: print(e) # Cannot withdraw 150. Balance: 100 print(f'Short by: {e.amount - e.balance}')

Best Practices

python
class MyError(Exception): pass # ❌ DON'T: Inherit from BaseException # (catches SystemExit, KeyboardInterrupt, GeneratorExit) class MyError(BaseException): pass # ✅ DO: Provide useful default messages # ✅ DO: Group related exceptions in a hierarchy
Key Rules
  • •Always inherit from Exception (or a custom subclass of Exception) — never from BaseException
  • •Call super().__init__() in your __init__ to ensure the base class is properly initialized
  • •Add custom attributes (like self.field) to carry extra context that the handler might need
  • •Build exception hierarchies so callers can catch broad categories or specific errors as needed
  • •Use descriptive names ending in 'Error' (e.g., InvalidEmailError, DatabaseConnectionError)
Your Task

Create an exception hierarchy: `AppError` (base), `AuthError(AppError)`, `InvalidCredentialsError(AuthError)`, `PermissionDeniedError(AuthError)`. Add a custom `status_code` attribute to all. Create a function `login(username, password, role)` that raises `InvalidCredentialsError` (401) if username/password is wrong, or `PermissionDeniedError` (403) if role is not 'admin'.

EditorPython · JSX
PreviewUpdates on Run Tests
Loading preview…
Tests
Should define AppError base class
Should define AuthError inheriting AppError
Should set status_code attribute
Should call super().__init__
Should raise InvalidCredentialsError
Should catch AuthError in test block