Exceptions
Custom Exceptions
Domain-Specific Errors
Your banking app needs to signal "insufficient funds". Using generic ValueError
loses meaning. A custom InsufficientFundsError carries the balance and requested
amount - meaningful error information for the specific domain.
Simple custom exception
Create your own exception type.
# Define basic custom exception
def main():
# Simple custom exception
class InsufficientFundsError(Exception):
"""Raised when account balance is insufficient"""
pass
def withdraw(balance, amount):
if amount > balance:
raise InsufficientFundsError("Not enough funds")
return balance - amount
print("Bank withdrawal:\n")
# Successful withdrawal
try:
new_balance = withdraw(100, 30)
print(f" Withdrew $30: Balance ${new_balance}")
except InsufficientFundsError as e:
print(f" Error: {e}")
# Failed withdrawal
try:
new_balance = withdraw(100, 150)
print(f" Withdrew $150: Balance ${new_balance}")
except InsufficientFundsError as e:
print(f" Error: {e}")
# Multiple custom exceptions
class InvalidUsernameError(Exception):
pass
class InvalidEmailError(Exception):
pass
class InvalidAgeError(Exception):
pass
def validate_user(username, email, age):
if len(username) < 3:
raise InvalidUsernameError("Username too short")
if "@" not in email:
raise InvalidEmailError("Invalid email format")
if age < 18:
raise InvalidAgeError("Must be 18 or older")
return True
print("\nUser validation:")
users = [
("alice", "alice@example.com", 25),
("ab", "alice@example.com", 25),
("alice", "invalid", 25),
("alice", "alice@example.com", 15)
]
for username, email, age in users:
try:
validate_user(username, email, age)
print(f" {username}: Valid")
except (InvalidUsernameError, InvalidEmailError, InvalidAgeError) as e:
print(f" {username}: {type(e).__name__} - {e}")
# Custom exception with default message
class ConfigurationError(Exception):
"""Raised when configuration is invalid"""
def __init__(self, message="Invalid configuration"):
super().__init__(message)
try:
raise ConfigurationError()
except ConfigurationError as e:
print(f"\nDefault message: {e}")
try:
raise ConfigurationError("Missing API key")
except ConfigurationError as e:
print(f"Custom message: {e}")
if __name__ == "__main__":
main()
def main(): # Simple custom exception
3def main():4 # Simple custom exception5 class InsufficientFundsError(Exception):6 """Raised when account balance is insufficient"""7 pass8 9 def withdraw(balance, amount):10 if amount > balance:11 raise InsufficientFundsError("Not enough funds")12 return balance - amount13 14 print("Bank withdrawal:\n")outputBank withdrawal:def withdraw(balance, amount):
pass 1 of 29def withdraw(balance100, amount30):10 if amount > balance:11 raise InsufficientFundsError("Not enough funds")12 return balance100 - amount30new_balance ← 70
17try:18 new_balance→ 70 = withdraw(100, 30)19 print(f" Withdrew $30: Balance ${new_balance70}")20except InsufficientFundsError as e:output Withdrew $30: Balance $70def withdraw(balance, amount):
pass 2 of 29def withdraw(balance100, amount150):10 if amount > balance:11 raise InsufficientFundsError("Not enough funds")if amount > balance:
9def withdraw(balance, amount):10 if amount150 > balance100:11 raise InsufficientFundsError("Not enough funds")12 return balance - amountexcept InsufficientFundsError as e:
26 print(f" Withdrew $150: Balance ${new_balance}")27except InsufficientFundsError as e:28 print(f" Error: {eNot enough funds}")2930#@help h1output Error: Not enough funds Error: Not enough fundsusers ← [('alice', 'alice@example.com', 25), ('ab', 'alice@example.com', 25), ('alice', 'invalid', 25), ('alice', 'alice@example.com', 15)]
30#@help h131# Custom exceptions inherit from Exception32# Name them with "Error" suffix by convention33# Use when built-in exceptions aren't specific enough34#@end3536# Multiple custom exceptions37class InvalidUsernameError(Exception):38 pass3940class InvalidEmailError(Exception):41 pass4243class InvalidAgeError(Exception):44 pass4546def validate_user(username, email, age):47 if len(username) < 3:48 raise InvalidUsernameError("Username too short")49 if "@" not in email:50 raise InvalidEmailError("Invalid email format")51 if age < 18:52 raise InvalidAgeError("Must be 18 or older")53 return True5455print("\nUser validation:")5657users→ [('alice', 'alice@example.com', 25), ('ab', 'alice@example.com', 25), ('alice', 'invalid', 25), ('alice', 'alice@example.com', 15)] = [58 ("alice", "alice@example.com", 25),59 ("ab", "alice@example.com", 25),60 ("alice", "invalid", 25),61 ("alice", "alice@example.com", 15)62]output User validation:for username, email, age in users:
pass 1 of 464for usernamealice, emailalice@example.com, age25 in users[('alice', 'alice@example.com', 25), ('ab', 'alice@example.com', 25), ('alice', 'invalid', 25), ('alice', 'alice@example.com', 15)]:65 try:66 validate_user(username, email, age)All 4 passes — pass 1 is the card above pass usernameemailage1 alice alice@example.com 25 2 ab alice@example.com 25 3 alice invalid 25 4 alice alice@example.com 15 try:
pass 1 of 464for username, email, age in users:65 try:66 validate_user(usernamealice, emailalice@example.com, age25)67 print(f" {username}: Valid")All 4 passes — pass 1 is the card above pass usernameemailage1 alice alice@example.com 25 2 ab alice@example.com 25 3 alice invalid 25 4 alice alice@example.com 15 def validate_user(username, email, age):
pass 1 of 446def validate_user(usernamealice, emailalice@example.com, age25):47 if len(username) < 3:48 raise InvalidUsernameError("Username too short")49 if "@" not in email:50 raise InvalidEmailError("Invalid email format")51 if age < 18:52 raise InvalidAgeError("Must be 18 or older")53 return TrueAll 4 passes — pass 1 is the card above pass usernameemailage1 alice alice@example.com 25 2 ab alice@example.com 25 3 alice invalid 25 4 alice alice@example.com 15 validate_user(username, email, age)
65try:66 validate_user(usernamealice, emailalice@example.com, age25)67 print(f" {usernamealice}: Valid")68except (InvalidUsernameError, InvalidEmailError, InvalidAgeError) as e:output alice: Validif len(username) < 3:
46def validate_user(username, email, age):47 if len(usernameab) < 3:48 raise InvalidUsernameError("Username too short")49 if "@" not in email:except (InvalidUsernameError, InvalidEmailError, InvalidAgeError) as e…
pass 1 of 367 print(f" {username}: Valid")68except (InvalidUsernameError, InvalidEmailError, InvalidAgeError) as e:69 print(f" {usernameab}: {type(eUsername too short).__name__} - {e}")output ab: InvalidUsernameError - Username too short ab: InvalidUsernameError - Username too shortAll 3 passes — pass 1 is the card above pass usernameeemailage1 ab Username too short invalid — 2 alice Invalid email format — 15 3 alice Must be 18 or older — — if "@" not in email:
48 raise InvalidUsernameError("Username too short")49if "@" not in emailinvalid:50 raise InvalidEmailError("Invalid email format")51if age < 18:if age < 18:
50 raise InvalidEmailError("Invalid email format")51if age15 < 18:52 raise InvalidAgeError("Must be 18 or older")53return True# Custom exception with default message
71# Custom exception with default message72class ConfigurationError(Exception):73 """Raised when configuration is invalid"""74 def __init__(self, message="Invalid configuration"):75 super().__init__(message)def __init__(self, message="Invalid configuration"):
pass 1 of 273"""Raised when configuration is invalid"""74def __init__(self(empty), messageInvalid configuration="Invalid configuration"):75 super().__init__(message)except ConfigurationError as e:
78 raise ConfigurationError()79except ConfigurationError as e:80 print(f"\nDefault message: {eInvalid configuration}")8182try:output Default message: Invalid configuration Default message: Invalid configurationdef __init__(self, message="Invalid configuration"):
pass 2 of 273"""Raised when configuration is invalid"""74def __init__(selfMissing API key, messageMissing API key="Invalid configuration"):75 super().__init__(message)except ConfigurationError as e:
83 raise ConfigurationError("Missing API key")84except ConfigurationError as e:85 print(f"Custom message: {eMissing API key}")outputCustom message: Missing API key Custom message: Missing API keymain()
87if __name__ == "__main__":88 main()
Inherit from Exception. Add pass for minimal exception.
Exception with data
Include relevant context in the exception.
# Custom exception with additional data
def main():
# Custom exception with fields
class ValidationError(Exception):
def __init__(self, message, field_name, invalid_value):
super().__init__(message)
self.field_name = field_name
self.invalid_value = invalid_value
def validate_age(age):
if not isinstance(age, int):
raise ValidationError(
"Age must be an integer",
field_name="age",
invalid_value=age
)
if age < 0:
raise ValidationError(
"Age cannot be negative",
field_name="age",
invalid_value=age
)
if age > 150:
raise ValidationError(
"Age is unrealistic",
field_name="age",
invalid_value=age
)
return True
print("Age validation with context:\n")
test_ages = [25, "invalid", -5, 200]
for age in test_ages:
try:
validate_age(age)
print(f" {age}: Valid")
except ValidationError as e:
print(f" Field: {e.field_name}")
print(f" Value: {e.invalid_value}")
print(f" Error: {e}")
print()
# Exception with error code
class DatabaseError(Exception):
def __init__(self, message, query, error_code):
super().__init__(message)
self.query = query
self.error_code = error_code
def execute_query(query):
if "DROP" in query:
raise DatabaseError(
"DROP not allowed",
query=query,
error_code=403
)
if "invalid" in query:
raise DatabaseError(
"Syntax error",
query=query,
error_code=400
)
return "SUCCESS"
print("\nDatabase queries:")
queries = [
"SELECT * FROM users",
"DROP TABLE users",
"invalid syntax"
]
for query in queries:
try:
result = execute_query(query)
print(f" '{query}': {result}")
except DatabaseError as e:
print(f" Error code: {e.error_code}")
print(f" Query: {e.query}")
print(f" Message: {e}")
print()
# Exception with multiple context fields
class PaymentError(Exception):
def __init__(self, message, amount, account_id, transaction_id):
super().__init__(message)
self.amount = amount
self.account_id = account_id
self.transaction_id = transaction_id
def __str__(self):
return (f"{super().__str__()} "
f"[Txn:{self.transaction_id}, "
f"Account:{self.account_id}, "
f"Amount:${self.amount}]")
try:
raise PaymentError(
"Insufficient funds",
amount=100.00,
account_id="ACC123",
transaction_id="TXN456"
)
except PaymentError as e:
print("Payment failure:")
print(f" {e}")
print(f" Amount: ${e.amount}")
print(f" Account: {e.account_id}")
print(f" Transaction: {e.transaction_id}")
if __name__ == "__main__":
main()
test_ages ← [25, 'invalid', -5, 200]
3def main():4 # Custom exception with fields5 class ValidationError(Exception):6 def __init__(self, message, field_name, invalid_value):7 super().__init__(message)8 self.field_name = field_name9 self.invalid_value = invalid_value10 11 def validate_age(age):12 if not isinstance(age, int):13 raise ValidationError(14 "Age must be an integer",15 field_name="age",16 invalid_value=age17 )18 if age < 0:19 raise ValidationError(20 "Age cannot be negative",21 field_name="age",22 invalid_value=age23 )24 if age > 150:25 raise ValidationError(26 "Age is unrealistic",27 field_name="age",28 invalid_value=age29 )30 return True31 32 print("Age validation with context:\n")33 34 test_ages→ [25, 'invalid', -5, 200] = [25, "invalid", -5, 200]outputAge validation with context:for age in test_ages:
pass 1 of 436for age25 in test_ages[25, 'invalid', -5, 200]:37 try:38 validate_age(age)All 4 passes — pass 1 is the card above pass age1 25 2 invalid 3 -5 4 200 try:
pass 1 of 436for age in test_ages:37 try:38 validate_age(age25)39 print(f" {age}: Valid")All 4 passes — pass 1 is the card above pass age1 25 2 invalid 3 -5 4 200 def validate_age(age):
pass 1 of 411def validate_age(age25):12 if not isinstance(age, int):13 raise ValidationError(14 "Age must be an integer",15 field_name="age",16 invalid_value=age17 )18 if age < 0:19 raise ValidationError(20 "Age cannot be negative",21 field_name="age",22 invalid_value=age23 )24 if age > 150:25 raise ValidationError(26 "Age is unrealistic",27 field_name="age",28 invalid_value=age29 )30 return TrueAll 4 passes — pass 1 is the card above pass age1 25 2 invalid 3 -5 4 200 validate_age(age)
37try:38 validate_age(age25)39 print(f" {age25}: Valid")40except ValidationError as e:output 25: Validif not isinstance(age, int):
11def validate_age(age):12 if not isinstance(ageinvalid, int):13 raise ValidationError(14 "Age must be an integer",15 field_name="age",16 invalid_value=ageinvalid17 )18 if age < 0:self.field_name ← age, self.invalid_value ← invalid
pass 1 of 35class ValidationError(Exception):6 def __init__(selfAge must be an integer, messageAge must be an integer, field_nameage, invalid_valueinvalid):7 super().__init__(message)8 self.field_name→ age = field_nameage9 self.invalid_value→ invalid = invalid_valueinvalidAll 3 passes — pass 1 is the card above pass selfmessageinvalid_valueageself.field_nameself.invalid_value1 Age must be an integer Age must be an integer invalid -5 age invalid 2 Age cannot be negative Age cannot be negative -5 200 age -5 3 Age is unrealistic Age is unrealistic 200 — age 200 except ValidationError as e:
pass 1 of 339 print(f" {age}: Valid")40except ValidationError as e:41 print(f" Field: {e.field_nameage}")42 print(f" Value: {e.invalid_valueinvalid}")43 print(f" Error: {eAge must be an integer}")44 print()output Field: age Value: invalid Error: Age must be an integerAll 3 passes — pass 1 is the card above pass e.invalid_valueeage1 invalid Age must be an integer -5 2 -5 Age cannot be negative 200 3 200 Age is unrealistic — if age < 0:
17 )18if age-5 < 0:19 raise ValidationError(20 "Age cannot be negative",21 field_name="age",22 invalid_value=age-523 )24if age > 150:if age > 150:
23 )24if age200 > 150:25 raise ValidationError(26 "Age is unrealistic",27 field_name="age",28 invalid_value=age20029 )30return Truequeries ← ['SELECT * FROM users', 'DROP TABLE users', 'invalid syntax']
46#@help h147# Add fields to exception __init__48# Store context: field names, values, error codes49# Access fields when catching exception50#@end5152# Exception with error code53class DatabaseError(Exception):54 def __init__(self, message, query, error_code):55 super().__init__(message)56 self.query = query57 self.error_code = error_code5859def execute_query(query):60 if "DROP" in query:61 raise DatabaseError(62 "DROP not allowed",63 query=query,64 error_code=40365 )66 if "invalid" in query:67 raise DatabaseError(68 "Syntax error",69 query=query,70 error_code=40071 )72 return "SUCCESS"7374print("\nDatabase queries:")7576queries→ ['SELECT * FROM users', 'DROP TABLE users', 'invalid syntax'] = [77 "SELECT * FROM users",78 "DROP TABLE users",79 "invalid syntax"80]output Database queries:for query in queries:
pass 1 of 382for querySELECT * FROM users in queries['SELECT * FROM users', 'DROP TABLE users', 'invalid syntax']:83 try:84 result = execute_query(query)All 3 passes — pass 1 is the card above pass queryselfmessageerror_codee.error_codee.queryeself.queryself.error_code1 SELECT * FROM users — — — — — — — — 2 DROP TABLE users DROP not allowed DROP not allowed 403 403 DROP TABLE users DROP not allowed DROP TABLE users 403 3 invalid syntax Syntax error Syntax error 400 400 invalid syntax Syntax error invalid syntax 400 try:
pass 1 of 382for query in queries:83 try:84 result = execute_query(querySELECT * FROM users)85 print(f" '{query}': {result}")All 3 passes — pass 1 is the card above pass queryselfmessageerror_codee.error_codee.queryeself.queryself.error_code1 SELECT * FROM users — — — — — — — — 2 DROP TABLE users DROP not allowed DROP not allowed 403 403 DROP TABLE users DROP not allowed DROP TABLE users 403 3 invalid syntax Syntax error Syntax error 400 400 invalid syntax Syntax error invalid syntax 400 def execute_query(query):
pass 1 of 359def execute_query(querySELECT * FROM users):60 if "DROP" in query:61 raise DatabaseError(62 "DROP not allowed",63 query=query,64 error_code=40365 )66 if "invalid" in query:67 raise DatabaseError(68 "Syntax error",69 query=query,70 error_code=40071 )72 return "SUCCESS"All 3 passes — pass 1 is the card above pass queryselfmessageerror_codee.error_codee.queryeself.queryself.error_code1 SELECT * FROM users — — — — — — — — 2 DROP TABLE users DROP not allowed DROP not allowed 403 403 DROP TABLE users DROP not allowed DROP TABLE users 403 3 invalid syntax Syntax error Syntax error 400 400 invalid syntax Syntax error invalid syntax 400 result ← SUCCESS
83try:84 result→ SUCCESS = execute_query(querySELECT * FROM users)85 print(f" '{querySELECT * FROM users}': {resultSUCCESS}")86except DatabaseError as e:output 'SELECT * FROM users': SUCCESSif "DROP" in query:
59def execute_query(query):60 if "DROP" in queryDROP TABLE users:61 raise DatabaseError(62 "DROP not allowed",63 query=queryDROP TABLE users,64 error_code=40365 )66 if "invalid" in query:self.query ← DROP TABLE users, self.error_code ← 403
pass 1 of 253class DatabaseError(Exception):54 def __init__(selfDROP not allowed, messageDROP not allowed, queryDROP TABLE users, error_code403):55 super().__init__(message)56 self.query→ DROP TABLE users = queryDROP TABLE users57 self.error_code→ 403 = error_code403except DatabaseError as e:
pass 1 of 285 print(f" '{query}': {result}")86except DatabaseError as e:87 print(f" Error code: {e.error_code403}")88 print(f" Query: {e.queryDROP TABLE users}")89 print(f" Message: {eDROP not allowed}")90 print()output Error code: 403 Query: DROP TABLE users Message: DROP not allowedif "invalid" in query:
65 )66if "invalid" in queryinvalid syntax:67 raise DatabaseError(68 "Syntax error",69 query=queryinvalid syntax,70 error_code=40071 )72return "SUCCESS"self.query ← invalid syntax, self.error_code ← 400
pass 2 of 253class DatabaseError(Exception):54 def __init__(selfSyntax error, messageSyntax error, queryinvalid syntax, error_code400):55 super().__init__(message)56 self.query→ invalid syntax = queryinvalid syntax57 self.error_code→ 400 = error_code400except DatabaseError as e:
pass 2 of 285 print(f" '{query}': {result}")86except DatabaseError as e:87 print(f" Error code: {e.error_code400}")88 print(f" Query: {e.queryinvalid syntax}")89 print(f" Message: {eSyntax error}")90 print()output Error code: 400 Query: invalid syntax Message: Syntax error# Exception with multiple context fields
92# Exception with multiple context fields93class PaymentError(Exception):94 def __init__(self, message, amount, account_id, transaction_id):95 super().__init__(message)96 self.amount = amount97 self.account_id = account_id98 self.transaction_id = transaction_id99 100 def __str__(self):101 return (f"{super().__str__()} "102 f"[Txn:{self.transaction_id}, "103 f"Account:{self.account_id}, "104 f"Amount:${self.amount}]")self.amount ← 100.0, self.account_id ← ACC123, self.transaction_id ← TXN456
93class PaymentError(Exception):94 def __init__(self(empty), messageInsufficient funds, amount100.0, account_idACC123, transaction_idTXN456):95 super().__init__(message)96 self.amount→ 100.0 = amount100.097 self.account_id→ ACC123 = account_idACC12398 self.transaction_id→ TXN456 = transaction_idTXN456except PaymentError as e:
112 )113except PaymentError as e:114 print("Payment failure:")115 print(f" {eInsufficient funds [Txn:TXN456, Account:ACC123, Amount:$100.0]}")116 print(f" Amount: ${e.amount100.0}")117 print(f" Account: {e.account_idACC123}")118 print(f" Transaction: {e.transaction_idTXN456}")outputPayment failure: Insufficient funds [Txn:TXN456, Account:ACC123, Amount:$100.0] Amount: $100.0 Account: ACC123 Transaction: TXN456 Transaction: TXN456main()
120if __name__ == "__main__":121 main()
Add __init__ with parameters. Store data as attributes.
Exception hierarchy
Organize related exceptions into a hierarchy.
# Exception hierarchy
def main():
# Create exception hierarchy
class AppError(Exception):
"""Base exception for this application"""
pass
class ValidationError(AppError):
"""Validation failed"""
pass
class AuthenticationError(AppError):
"""Authentication failed"""
pass
class AuthorizationError(AppError):
"""Authorization failed"""
pass
class InvalidCredentialsError(AuthenticationError):
"""Specific auth error"""
pass
def process_request(username, password, action):
# Authentication
if not username or not password:
raise InvalidCredentialsError("Missing credentials")
if password != "secret":
raise AuthenticationError("Wrong password")
# Authorization
if action == "delete" and username != "admin":
raise AuthorizationError(f"{username} cannot delete")
# Validation
if not action:
raise ValidationError("Action required")
return "SUCCESS"
print("Request processing:\n")
requests = [
("alice", "secret", "read"),
("", "secret", "read"),
("bob", "wrong", "read"),
("alice", "secret", "delete"),
("admin", "secret", "delete")
]
for user, pwd, action in requests:
try:
result = process_request(user, pwd, action)
print(f" {user} - {action}: {result}")
except InvalidCredentialsError as e:
print(f" {user} - {action}: Invalid credentials")
except AuthenticationError as e:
print(f" {user} - {action}: Auth failed")
except AuthorizationError as e:
print(f" {user} - {action}: Not authorized")
except ValidationError as e:
print(f" {user} - {action}: Validation error")
# Catch at different levels
print("\nCatching at different levels:")
try:
raise InvalidCredentialsError("Bad login")
except AuthenticationError as e:
# Catches InvalidCredentialsError (subclass)
print(f" Caught as AuthenticationError: {type(e).__name__}")
try:
raise AuthorizationError("No permission")
except AppError as e:
# Catches any AppError subclass
print(f" Caught as AppError: {type(e).__name__}")
# Module-specific hierarchy
class DataError(Exception):
"""Base for data-related errors"""
pass
class ParseError(DataError):
"""Failed to parse data"""
pass
class FormatError(DataError):
"""Invalid data format"""
pass
class EncodingError(DataError):
"""Encoding problem"""
pass
def process_data(data, format_type):
if format_type not in ["json", "xml", "csv"]:
raise FormatError(f"Unsupported format: {format_type}")
if not data:
raise ParseError("Empty data")
if not data.startswith("{"):
raise EncodingError("Invalid encoding")
return "PARSED"
print("\nData processing:")
datasets = [
('{"name": "Alice"}', "json"),
("", "json"),
("not json", "json"),
('{"name": "Bob"}', "yaml")
]
for data, fmt in datasets:
try:
result = process_data(data, fmt)
print(f" {fmt}: {result}")
except ParseError as e:
print(f" {fmt}: Parse error - {e}")
except FormatError as e:
print(f" {fmt}: Format error - {e}")
except EncodingError as e:
print(f" {fmt}: Encoding error - {e}")
except DataError as e:
# Catch any other DataError
print(f" {fmt}: Data error - {e}")
if __name__ == "__main__":
main()
requests ← [('alice', 'secret', 'read'), ('', 'secret', 'read'), ('bob', 'wrong', 'read'), ('alice', 'secret', 'delete'), ('admin', 'secret', 'delete')]
3def main():4 # Create exception hierarchy5 class AppError(Exception):6 """Base exception for this application"""7 pass8 9 class ValidationError(AppError):10 """Validation failed"""11 pass12 13 class AuthenticationError(AppError):14 """Authentication failed"""15 pass16 17 class AuthorizationError(AppError):18 """Authorization failed"""19 pass20 21 class InvalidCredentialsError(AuthenticationError):22 """Specific auth error"""23 pass24 25 def process_request(username, password, action):26 # Authentication27 if not username or not password:28 raise InvalidCredentialsError("Missing credentials")29 if password != "secret":30 raise AuthenticationError("Wrong password")31 32 # Authorization33 if action == "delete" and username != "admin":34 raise AuthorizationError(f"{username} cannot delete")35 36 # Validation37 if not action:38 raise ValidationError("Action required")39 40 return "SUCCESS"41 42 print("Request processing:\n")43 44 requests→ [('alice', 'secret', 'read'), ('', 'secret', 'read'), ('bob', 'wrong', 'read'), ('alice', 'secret', 'delete'), ('admin', 'secret', 'delete')] = [45 ("alice", "secret", "read"),46 ("", "secret", "read"),47 ("bob", "wrong", "read"),48 ("alice", "secret", "delete"),49 ("admin", "secret", "delete")50 ]outputRequest processing:for user, pwd, action in requests:
pass 1 of 552for useralice, pwdsecret, actionread in requests[('alice', 'secret', 'read'), ('', 'secret', 'read'), ('bob', 'wrong', 'read'), ('alice', 'secret', 'delete'), ('admin', 'secret', 'delete')]:53 try:54 result = process_request(user, pwd, action)All 5 passes — pass 1 is the card above pass userpwdactionusernamepassword1 alice secret read — — 2 (empty) secret read (empty) secret 3 bob wrong read — wrong 4 alice secret delete alice — 5 admin secret delete — — try:
pass 1 of 552for user, pwd, action in requests:53 try:54 result = process_request(useralice, pwdsecret, actionread)55 print(f" {user} - {action}: {result}")All 5 passes — pass 1 is the card above pass userpwdactionusernamepassword1 alice secret read — — 2 (empty) secret read (empty) secret 3 bob wrong read — wrong 4 alice secret delete alice — 5 admin secret delete — — def process_request(username, password, action): # Authenticat…
pass 1 of 525def process_request(usernamealice, passwordsecret, actionread):26 # Authentication27 if not username or not password:28 raise InvalidCredentialsError("Missing credentials")29 if password != "secret":30 raise AuthenticationError("Wrong password")31 32 # Authorization33 if action == "delete" and username != "admin":34 raise AuthorizationError(f"{username} cannot delete")35 36 # Validation37 if not action:38 raise ValidationError("Action required")39 40 return "SUCCESS"All 5 passes — pass 1 is the card above pass usernamepasswordactionuser1 alice secret read — 2 (empty) secret read (empty) 3 bob wrong read bob 4 alice secret delete alice 5 admin secret delete — result ← SUCCESS
53try:54 result→ SUCCESS = process_request(useralice, pwdsecret, actionread)55 print(f" {useralice} - {actionread}: {resultSUCCESS}")56except InvalidCredentialsError as e:output alice - read: SUCCESSif not username or not password:
26# Authentication27if not username(empty) or not passwordsecret:28 raise InvalidCredentialsError("Missing credentials")29if password != "secret":except InvalidCredentialsError as e:
55 print(f" {user} - {action}: {result}")56except InvalidCredentialsError as e:57 print(f" {user(empty)} - {actionread}: Invalid credentials")58except AuthenticationError as e:output - read: Invalid credentialsif password != "secret":
28 raise InvalidCredentialsError("Missing credentials")29if passwordwrong != "secret":30 raise AuthenticationError("Wrong password")except AuthenticationError as e:
57 print(f" {user} - {action}: Invalid credentials")58except AuthenticationError as e:59 print(f" {userbob} - {actionread}: Auth failed")60except AuthorizationError as e:output bob - read: Auth failedif action == "delete" and username != "admin":
32# Authorization33if actiondelete == "delete" and usernamealice != "admin":34 raise AuthorizationError(f"{usernamealice} cannot delete")except AuthorizationError as e:
59 print(f" {user} - {action}: Auth failed")60except AuthorizationError as e:61 print(f" {useralice} - {actiondelete}: Not authorized")62except ValidationError as e:output alice - delete: Not authorizedresult ← SUCCESS
53try:54 result→ SUCCESS = process_request(useradmin, pwdsecret, actiondelete)55 print(f" {useradmin} - {actiondelete}: {resultSUCCESS}")56except InvalidCredentialsError as e:output admin - delete: SUCCESSprint(" Catching at different levels:")
72# Catch at different levels73print("\nCatching at different levels:")output Catching at different levels:except AuthenticationError as e: # Catches InvalidCredentialsE…
76 raise InvalidCredentialsError("Bad login")77except AuthenticationError as e:78 # Catches InvalidCredentialsError (subclass)79 print(f" Caught as AuthenticationError: {type(eBad login).__name__}")8081try:output Caught as AuthenticationError: InvalidCredentialsError Caught as AuthenticationError: InvalidCredentialsErrorexcept AppError as e: # Catches any AppError subclass
82 raise AuthorizationError("No permission")83except AppError as e:84 # Catches any AppError subclass85 print(f" Caught as AppError: {type(eNo permission).__name__}")8687# Module-specific hierarchyoutput Caught as AppError: AuthorizationError Caught as AppError: AuthorizationErrordatasets ← [('{"name": "Alice"}', 'json'), ('', 'json'), ('not json', 'json'), ('{"name": "Bob"}', 'yaml')]
87# Module-specific hierarchy88class DataError(Exception):89 """Base for data-related errors"""90 pass9192class ParseError(DataError):93 """Failed to parse data"""94 pass9596class FormatError(DataError):97 """Invalid data format"""98 pass99100class EncodingError(DataError):101 """Encoding problem"""102 pass103104def process_data(data, format_type):105 if format_type not in ["json", "xml", "csv"]:106 raise FormatError(f"Unsupported format: {format_type}")107 if not data:108 raise ParseError("Empty data")109 if not data.startswith("{"):110 raise EncodingError("Invalid encoding")111 return "PARSED"112113print("\nData processing:")114115datasets→ [('{"name": "Alice"}', 'json'), ('', 'json'), ('not json', 'json'), ('{"name": "Bob"}', 'yaml')] = [116 ('{"name": "Alice"}', "json"),117 ("", "json"),118 ("not json", "json"),119 ('{"name": "Bob"}', "yaml")120]output Data processing:for data, fmt in datasets:
pass 1 of 4122for data{"name": "Alice"}, fmtjson in datasets[('{"name": "Alice"}', 'json'), ('', 'json'), ('not json', 'json'), ('{"name": "Bob"}', 'yaml')]:123 try:124 result = process_data(data, fmt)All 4 passes — pass 1 is the card above pass datafmteformat_type1 {"name": "Alice"} json — — 2 (empty) json Empty data — 3 not json json Invalid encoding — 4 {"name": "Bob"} yaml Unsupported format: yaml yaml try:
pass 1 of 4122for data, fmt in datasets:123 try:124 result = process_data(data{"name": "Alice"}, fmtjson)125 print(f" {fmt}: {result}")All 4 passes — pass 1 is the card above pass datafmteformat_type1 {"name": "Alice"} json — — 2 (empty) json Empty data — 3 not json json Invalid encoding — 4 {"name": "Bob"} yaml Unsupported format: yaml yaml def process_data(data, format_type):
pass 1 of 4104def process_data(data{"name": "Alice"}, format_typejson):105 if format_type not in ["json", "xml", "csv"]:106 raise FormatError(f"Unsupported format: {format_type}")107 if not data:108 raise ParseError("Empty data")109 if not data.startswith("{"):110 raise EncodingError("Invalid encoding")111 return "PARSED"All 4 passes — pass 1 is the card above pass dataformat_typefmte1 {"name": "Alice"} json — — 2 (empty) json json Empty data 3 not json json json Invalid encoding 4 {"name": "Bob"} yaml yaml Unsupported format: yaml result ← PARSED
123try:124 result→ PARSED = process_data(data{"name": "Alice"}, fmtjson)125 print(f" {fmtjson}: {resultPARSED}")126except ParseError as e:output json: PARSEDif not data:
106 raise FormatError(f"Unsupported format: {format_type}")107if not data(empty):108 raise ParseError("Empty data")109if not data.startswith("{"):except ParseError as e:
125 print(f" {fmt}: {result}")126except ParseError as e:127 print(f" {fmtjson}: Parse error - {eEmpty data}")128except FormatError as e:output json: Parse error - Empty dataif not data.startswith("{"):
108 raise ParseError("Empty data")109if not datanot json.startswith("{"):110 raise EncodingError("Invalid encoding")111return "PARSED"except EncodingError as e:
129 print(f" {fmt}: Format error - {e}")130except EncodingError as e:131 print(f" {fmtjson}: Encoding error - {eInvalid encoding}")132except DataError as e:output json: Encoding error - Invalid encodingif format_type not in ["json", "xml", "csv"]:
104def process_data(data, format_type):105 if format_typeyaml not in ["json", "xml", "csv"]:106 raise FormatError(f"Unsupported format: {format_typeyaml}")107 if not data:except FormatError as e:
127 print(f" {fmt}: Parse error - {e}")128except FormatError as e:129 print(f" {fmtyaml}: Format error - {eUnsupported format: yaml}")130except EncodingError as e:output yaml: Format error - Unsupported format: yamlmain()
136if __name__ == "__main__":137 main()
Base exception for module. Specialized subclasses for specific errors.
Override __str__
Customize exception's string representation.
# Override __str__ for custom formatting
def main():
# Custom __str__ method
class ProductNotFoundError(Exception):
def __init__(self, product_id, category=None):
self.product_id = product_id
self.category = category
def __str__(self):
msg = f"Product {self.product_id} not found"
if self.category:
msg += f" in category '{self.category}'"
return msg
print("Product lookup:\n")
try:
raise ProductNotFoundError(123, "Electronics")
except ProductNotFoundError as e:
print(f" Error: {e}")
print(f" Product ID: {e.product_id}")
print(f" Category: {e.category}")
try:
raise ProductNotFoundError(456)
except ProductNotFoundError as e:
print(f"\n Error: {e}")
print(f" Product ID: {e.product_id}")
# Rich error messages
class OrderError(Exception):
def __init__(self, order_id, items, total, reason):
self.order_id = order_id
self.items = items
self.total = total
self.reason = reason
def __str__(self):
return (f"Order #{self.order_id} failed: {self.reason}\n"
f" Items: {self.items}\n"
f" Total: ${self.total:.2f}")
print("\nOrder processing:")
try:
raise OrderError(
order_id=789,
items=["Laptop", "Mouse"],
total=1050.00,
reason="Payment declined"
)
except OrderError as e:
print(f"{e}")
# Exception with repr
class ApiError(Exception):
def __init__(self, status_code, endpoint, message):
self.status_code = status_code
self.endpoint = endpoint
self.message = message
def __str__(self):
return f"[{self.status_code}] {self.message}"
def __repr__(self):
return (f"ApiError(status_code={self.status_code}, "
f"endpoint='{self.endpoint}', "
f"message='{self.message}')")
error = ApiError(404, "/api/users/123", "User not found")
print("\nAPI error:")
print(f" str: {str(error)}")
print(f" repr: {repr(error)}")
# Format with multiple fields
class ValidationError(Exception):
def __init__(self, errors):
self.errors = errors
def __str__(self):
if len(self.errors) == 1:
field, msg = next(iter(self.errors.items()))
return f"Validation failed: {field} - {msg}"
lines = ["Multiple validation errors:"]
for field, msg in self.errors.items():
lines.append(f" - {field}: {msg}")
return "\n".join(lines)
print("\nValidation errors:")
try:
raise ValidationError({"age": "Must be positive"})
except ValidationError as e:
print(f"{e}\n")
try:
raise ValidationError({
"username": "Too short",
"email": "Invalid format",
"age": "Must be 18+"
})
except ValidationError as e:
print(f"{e}")
# Exception with timestamp
from datetime import datetime
class TimestampedError(Exception):
def __init__(self, message):
self.message = message
self.timestamp = datetime(2025, 1, 15, 10, 30)
def __str__(self):
time_str = self.timestamp.strftime("%Y-%m-%d %H:%M:%S")
return f"[{time_str}] {self.message}"
print("\nTimestamped error:")
try:
raise TimestampedError("Connection lost")
except TimestampedError as e:
print(f" {e}")
print(f" Time: {e.timestamp}")
if __name__ == "__main__":
main()
def main(): # Custom __str__ method
3def main():4 # Custom __str__ method5 class ProductNotFoundError(Exception):6 def __init__(self, product_id, category=None):7 self.product_id = product_id8 self.category = category9 10 def __str__(self):11 msg = f"Product {self.product_id} not found"12 if self.category:13 msg += f" in category '{self.category}'"14 return msg15 16 print("Product lookup:\n")outputProduct lookup:self.product_id ← 123, self.category ← Electronics
pass 1 of 25class ProductNotFoundError(Exception):6 def __init__(self(empty), product_id123, categoryElectronics=NoneNone):7 self.product_id→ 123 = product_id1238 self.category→ Electronics = categoryElectronicsexcept ProductNotFoundError as e:
19 raise ProductNotFoundError(123, "Electronics")20except ProductNotFoundError as e:21 print(f" Error: {eProduct 123 not found in category 'Electronics'}")22 print(f" Product ID: {e.product_id123}")23 print(f" Category: {e.categoryElectronics}")2425try:output Error: Product 123 not found in category 'Electronics' Product ID: 123 Category: Electronics Category: Electronicsself.product_id ← 456, self.category ← None
pass 2 of 25class ProductNotFoundError(Exception):6 def __init__(self(empty), product_id456, categoryNone=NoneNone):7 self.product_id→ 456 = product_id4568 self.category→ None = categoryNoneexcept ProductNotFoundError as e:
26 raise ProductNotFoundError(456)27except ProductNotFoundError as e:28 print(f"\n Error: {eProduct 456 not found}")29 print(f" Product ID: {e.product_id456}")3031#@help h1output Error: Product 456 not found Product ID: 456 Product ID: 456#@help h1
31#@help h132# Override __str__() to format exception message33# Called when exception is converted to string34# Can build message from stored fields35#@end3637# Rich error messages38class OrderError(Exception):39 def __init__(self, order_id, items, total, reason):40 self.order_id = order_id41 self.items = items42 self.total = total43 self.reason = reason44 45 def __str__(self):46 return (f"Order #{self.order_id} failed: {self.reason}\n"47 f" Items: {self.items}\n"48 f" Total: ${self.total:.2f}")4950print("\nOrder processing:")output Order processing:self.order_id ← 789, self.items ← ['Laptop', 'Mouse'], self.total ← 1050.0
38class OrderError(Exception):39 def __init__(self(empty), order_id789, items['Laptop', 'Mouse'], total1050.0, reasonPayment declined):40 self.order_id→ 789 = order_id78941 self.items→ ['Laptop', 'Mouse'] = items['Laptop', 'Mouse']42 self.total→ 1050.0 = total1050.043 self.reason→ Payment declined = reasonPayment declinedexcept OrderError as e:
58 )59except OrderError as e:60 print(f"{eOrder #789 failed: Payment declined Items: ['Laptop', 'Mouse'] Total: $1050.00}")6162# Exception with reproutputOrder #789 failed: Payment declined Items: ['Laptop', 'Mouse'] Total: $1050.00 Order #789 failed: Payment declined Items: ['Laptop', 'Mouse'] Total: $1050.00# Exception with repr
62# Exception with repr63class ApiError(Exception):64 def __init__(self, status_code, endpoint, message):65 self.status_code = status_code66 self.endpoint = endpoint67 self.message = message68 69 def __str__(self):70 return f"[{self.status_code}] {self.message}"71 72 def __repr__(self):73 return (f"ApiError(status_code={self.status_code}, "74 f"endpoint='{self.endpoint}', "75 f"message='{self.message}')")7677error = ApiError(404, "/api/users/123", "User not found")self.status_code ← 404, self.endpoint ← /api/users/123, self.message ← User not found
63class ApiError(Exception):64 def __init__(self(empty), status_code404, endpoint/api/users/123, messageUser not found):65 self.status_code→ 404 = status_code40466 self.endpoint→ /api/users/123 = endpoint/api/users/12367 self.message→ User not found = messageUser not founderror ← [404] User not found
77error→ [404] User not found = ApiError(404, "/api/users/123", "User not found")7879print("\nAPI error:")80print(f" str: {str(error[404] User not found)}")81print(f" repr: {repr(error[404] User not found)}")8283# Format with multiple fields84class ValidationError(Exception):85 def __init__(self, errors):86 self.errors = errors87 88 def __str__(self):89 if len(self.errors) == 1:90 field, msg = next(iter(self.errors.items()))91 return f"Validation failed: {field} - {msg}"92 93 lines = ["Multiple validation errors:"]94 for field, msg in self.errors.items():95 lines.append(f" - {field}: {msg}")96 return "\n".join(lines)9798print("\nValidation errors:")output API error: str: [404] User not found repr: ApiError(status_code=404, endpoint='/api/users/123', message='User not found') Validation errors:self.errors ← {'age': 'Must be positive'}
pass 1 of 284class ValidationError(Exception):85 def __init__(self(empty), errors{'age': 'Must be positive'}):86 self.errors→ {'age': 'Must be positive'} = errors{'age': 'Must be positive'}except ValidationError as e:
101 raise ValidationError({"age": "Must be positive"})102except ValidationError as e:103 print(f"{eValidation failed: age - Must be positive}\n")104105try:outputValidation failed: age - Must be positive Validation failed: age - Must be positiveself.errors ← {'username': 'Too short', 'email': 'Invalid format', 'age': 'Must be 18+'}
pass 2 of 284class ValidationError(Exception):85 def __init__(self(empty), errors{'username': 'Too short', 'email': 'Invalid format', 'age': 'Must be 18+'}):86 self.errors→ {'username': 'Too short', 'email': 'Invalid format', 'age': 'Must be 18+'} = errors{'username': 'Too short', 'email': 'Invalid format', 'age': 'Must be 18+'}except ValidationError as e:
110 })111except ValidationError as e:112 print(f"{eMultiple validation errors: - username: Too short - email: Invalid format - age: Must be 18+}")113114# Exception with timestampoutputMultiple validation errors: - username: Too short - email: Invalid format - age: Must be 18+ Multiple validation errors: - username: Too short - email: Invalid format - age: Must be 18+print(" Timestamped error:")
126print("\nTimestamped error:")output Timestamped error:self.message ← Connection lost, self.timestamp ← 2025-01-15 10:30:00
117class TimestampedError(Exception):118 def __init__(self(empty), messageConnection lost):119 self.message→ Connection lost = messageConnection lost120 self.timestamp→ 2025-01-15 10:30:00 = datetime(2025, 1, 15, 10, 30)except TimestampedError as e:
129 raise TimestampedError("Connection lost")130except TimestampedError as e:131 print(f" {e[2025-01-15 10:30:00] Connection lost}")132 print(f" Time: {e.timestamp2025-01-15 10:30:00}")output [2025-01-15 10:30:00] Connection lost Time: 2025-01-15 10:30:00 Time: 2025-01-15 10:30:00main()
134if __name__ == "__main__":135 main()
def __str__(self): controls what print(exception) shows.
When to create custom exceptions
Guidelines for when it's worth it.
# When to create custom exceptions
def main():
# Domain-specific errors
class ShoppingCartError(Exception):
"""Base for shopping cart errors"""
pass
class ItemNotInCartError(ShoppingCartError):
def __init__(self, item_id):
self.item_id = item_id
super().__init__(f"Item {item_id} not in cart")
class CartLimitExceededError(ShoppingCartError):
def __init__(self, limit, attempted):
self.limit = limit
self.attempted = attempted
super().__init__(
f"Cart limit {limit} exceeded: tried to add {attempted}"
)
class Cart:
def __init__(self, max_items=5):
self.items = []
self.max_items = max_items
def add(self, item):
if len(self.items) >= self.max_items:
raise CartLimitExceededError(
self.max_items,
len(self.items) + 1
)
self.items.append(item)
def remove(self, item_id):
for item in self.items:
if item["id"] == item_id:
self.items.remove(item)
return
raise ItemNotInCartError(item_id)
print("Shopping cart with custom exceptions:\n")
cart = Cart(max_items=3)
# Add items
try:
cart.add({"id": 1, "name": "Laptop"})
cart.add({"id": 2, "name": "Mouse"})
cart.add({"id": 3, "name": "Keyboard"})
print(f" Added 3 items: {len(cart.items)} in cart")
except CartLimitExceededError as e:
print(f" Error: {e}")
# Try to exceed limit
try:
cart.add({"id": 4, "name": "Monitor"})
except CartLimitExceededError as e:
print(f" Error: {e}")
print(f" Limit: {e.limit}, Attempted: {e.attempted}")
# Remove item
try:
cart.remove(2)
print(f"\n Removed item 2: {len(cart.items)} in cart")
except ItemNotInCartError as e:
print(f" Error: {e}")
# Try to remove non-existent
try:
cart.remove(999)
except ItemNotInCartError as e:
print(f" Error: {e}")
print(f" Item ID: {e.item_id}")
# API-style exceptions
class ApiException(Exception):
def __init__(self, status_code, message, details=None):
self.status_code = status_code
self.message = message
self.details = details
super().__init__(message)
class BadRequestError(ApiException):
def __init__(self, message, details=None):
super().__init__(400, message, details)
class NotFoundError(ApiException):
def __init__(self, resource, resource_id):
super().__init__(
404,
f"{resource} not found",
{"resource": resource, "id": resource_id}
)
class UnauthorizedError(ApiException):
def __init__(self, message="Unauthorized"):
super().__init__(401, message)
def api_request(endpoint, user_id, auth_token):
if not auth_token:
raise UnauthorizedError("Missing auth token")
if auth_token != "valid_token":
raise UnauthorizedError("Invalid token")
if not user_id:
raise BadRequestError(
"Missing required parameter",
{"missing_field": "user_id"}
)
if user_id not in [1, 2, 3]:
raise NotFoundError("User", user_id)
return {"user_id": user_id, "data": "..."}
print("\nAPI requests:")
requests = [
("/users/1", 1, "valid_token"),
("/users/999", 999, "valid_token"),
("/users/1", None, "valid_token"),
("/users/1", 1, None),
("/users/1", 1, "bad_token")
]
for endpoint, user_id, token in requests:
try:
result = api_request(endpoint, user_id, token)
print(f" {endpoint}: OK")
except UnauthorizedError as e:
print(f" {endpoint}: {e.status_code} {e.message}")
except NotFoundError as e:
print(f" {endpoint}: {e.status_code} {e.message}")
print(f" Details: {e.details}")
except BadRequestError as e:
print(f" {endpoint}: {e.status_code} {e.message}")
print(f" Details: {e.details}")
except ApiException as e:
# Catch-all for other API errors
print(f" {endpoint}: {e.status_code} {e.message}")
if __name__ == "__main__":
main()
def main(): # Domain-specific errors
3def main():4 # Domain-specific errors5 class ShoppingCartError(Exception):6 """Base for shopping cart errors"""7 pass8 9 class ItemNotInCartError(ShoppingCartError):10 def __init__(self, item_id):11 self.item_id = item_id12 super().__init__(f"Item {item_id} not in cart")13 14 class CartLimitExceededError(ShoppingCartError):15 def __init__(self, limit, attempted):16 self.limit = limit17 self.attempted = attempted18 super().__init__(19 f"Cart limit {limit} exceeded: tried to add {attempted}"20 )21 22 class Cart:23 def __init__(self, max_items=5):24 self.items = []25 self.max_items = max_items26 27 def add(self, item):28 if len(self.items) >= self.max_items:29 raise CartLimitExceededError(30 self.max_items,31 len(self.items) + 132 )33 self.items.append(item)34 35 def remove(self, item_id):36 for item in self.items:37 if item["id"] == item_id:38 self.items.remove(item)39 return40 raise ItemNotInCartError(item_id)41 42 print("Shopping cart with custom exceptions:\n")43 44 cart = Cart(max_items=3)outputShopping cart with custom exceptions:self.items ← [], self.max_items ← 3
22class Cart:23 def __init__(self<__main__.main.<locals>.Cart object at ⟨addr A⟩>, max_items3=5):24 self.items→ [] = []25 self.max_items→ 3 = max_items3cart ← <__main__.main.<locals>.Cart object at ⟨addr A⟩>
44cart→ <__main__.main.<locals>.Cart object at ⟨addr A⟩> = Cart(max_items=3)try:
46# Add items47try:48 cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.add({"id": 1, "name": "Laptop"})49 cart.add({"id": 2, "name": "Mouse"})self.items ← [{'id': 1, 'name': 'Laptop'}]
pass 1 of 427def add(self<__main__.main.<locals>.Cart object at ⟨addr A⟩>, item{'id': 1, 'name': 'Laptop'}):28 if len(self.items) >= self.max_items:29 raise CartLimitExceededError(30 self.max_items,31 len(self.items) + 132 )33 self.items→ [{'id': 1, 'name': 'Laptop'}].append(item{'id': 1, 'name': 'Laptop'})All 4 passes — pass 1 is the card above pass itemself.max_itemslimitattemptedee.limite.attemptedcartitem_iditem[”id”]self.itemsself.limitself.attempted1 {'id': 1, 'name': 'Laptop'} — — — — — — — — — [] → [{'id': 1, 'name': 'Laptop'}] — — 2 {'id': 2, 'name': 'Mouse'} — — — — — — — — — [{'id': 1, 'name': 'Laptop'}] → [{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}] — — 3 {'id': 3, 'name': 'Keyboard'} — — — — — — — — — [{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}] → [{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}, {'id': 3, 'name': 'Keyboard'}] — — 4 {'id': 4, 'name': 'Monitor'} 3 3 4 Cart limit 3 exceeded: tried to add 4 3 4 <__main__.main.<locals>.Cart object at ⟨addr A⟩> 2 2 [{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}, {'id': 3, 'name': 'Keyboard'}] → [{'id': 1, 'name': 'Laptop'}, {'id': 3, 'name': 'Keyboard'}] 3 4 cart.add({"id": 1, "name": "Laptop"})
47try:48 cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.add({"id": 1, "name": "Laptop"})49 cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.add({"id": 2, "name": "Mouse"})50 cart.add({"id": 3, "name": "Keyboard"})cart.add({"id": 2, "name": "Mouse"})
48cart.add({"id": 1, "name": "Laptop"})49cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.add({"id": 2, "name": "Mouse"})50cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.add({"id": 3, "name": "Keyboard"})51print(f" Added 3 items: {len(cart.items)} in cart")cart.add({"id": 3, "name": "Keyboard"})
49 cart.add({"id": 2, "name": "Mouse"})50 cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.add({"id": 3, "name": "Keyboard"})51 print(f" Added 3 items: {len(cart.items[{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}, {'id': 3, 'name': 'Keyboard'}])} in cart")52except CartLimitExceededError as e:output Added 3 items: 3 in carttry:
55# Try to exceed limit56try:57 cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.add({"id": 4, "name": "Monitor"})58except CartLimitExceededError as e:if len(self.items) >= self.max_items:
27def add(self, item):28 if len(self.items[{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}, {'id': 3, 'name': 'Keyboard'}]) >= self.max_items3:29 raise CartLimitExceededError(30 self.max_items3,31 len(self.items[{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}, {'id': 3, 'name': 'Keyboard'}]) + 132 )33 self.items.append(item)self.limit ← 3, self.attempted ← 4
14class CartLimitExceededError(ShoppingCartError):15 def __init__(self(3, 4), limit3, attempted4):16 self.limit→ 3 = limit317 self.attempted→ 4 = attempted418 super().__init__(except CartLimitExceededError as e:
57 cart.add({"id": 4, "name": "Monitor"})58except CartLimitExceededError as e:59 print(f" Error: {eCart limit 3 exceeded: tried to add 4}")60 print(f" Limit: {e.limit3}, Attempted: {e.attempted4}")6162# Remove itemoutput Error: Cart limit 3 exceeded: tried to add 4 Limit: 3, Attempted: 4 Limit: 3, Attempted: 4try:
62# Remove item63try:64 cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.remove(2)65 print(f"\n Removed item 2: {len(cart.items)} in cart")def remove(self, item_id):
pass 1 of 235def remove(self<__main__.main.<locals>.Cart object at ⟨addr A⟩>, item_id2):36 for item in self.items:37 if item["id"] == item_id:for item in self.items:
pass 1 of 435def remove(self, item_id):36 for item{'id': 1, 'name': 'Laptop'} in self.items[{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}, {'id': 3, 'name': 'Keyboard'}]:37 if item["id"] == item_id:38 self.items.remove(item)All 4 passes — pass 1 is the card above pass itemitem[”id”]item_idself.items1 {'id': 1, 'name': 'Laptop'} — — [{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}, {'id': 3, 'name': 'Keyboard'}] 2 {'id': 2, 'name': 'Mouse'} 2 2 [{'id': 1, 'name': 'Laptop'}, {'id': 2, 'name': 'Mouse'}, {'id': 3, 'name': 'Keyboard'}] → [{'id': 1, 'name': 'Laptop'}, {'id': 3, 'name': 'Keyboard'}] 3 {'id': 1, 'name': 'Laptop'} — — [{'id': 1, 'name': 'Laptop'}, {'id': 3, 'name': 'Keyboard'}] 4 {'id': 3, 'name': 'Keyboard'} — — [{'id': 1, 'name': 'Laptop'}, {'id': 3, 'name': 'Keyboard'}] self.items ← [{'id': 1, 'name': 'Laptop'}, {'id': 3, 'name': 'Keyboard'}]
36for item in self.items:37 if item["id"]2 == item_id2:38 self.items→ [{'id': 1, 'name': 'Laptop'}, {'id': 3, 'name': 'Keyboard'}].remove(item{'id': 2, 'name': 'Mouse'})39 return40raise ItemNotInCartError(item_id)cart.remove(2)
63try:64 cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.remove(2)65 print(f"\n Removed item 2: {len(cart.items[{'id': 1, 'name': 'Laptop'}, {'id': 3, 'name': 'Keyboard'}])} in cart")66except ItemNotInCartError as e:output Removed item 2: 2 in carttry:
69# Try to remove non-existent70try:71 cart<__main__.main.<locals>.Cart object at ⟨addr A⟩>.remove(999)72except ItemNotInCartError as e:def remove(self, item_id):
pass 2 of 235def remove(self<__main__.main.<locals>.Cart object at ⟨addr A⟩>, item_id999):36 for item in self.items:37 if item["id"] == item_id:raise ItemNotInCartError(item_id)
39 return40raise ItemNotInCartError(item_id999)self.item_id ← 999
9class ItemNotInCartError(ShoppingCartError):10 def __init__(self999, item_id999):11 self.item_id→ 999 = item_id99912 super().__init__(f"Item {item_id} not in cart")except ItemNotInCartError as e:
71 cart.remove(999)72except ItemNotInCartError as e:73 print(f" Error: {eItem 999 not in cart}")74 print(f" Item ID: {e.item_id999}")7576#@help h1output Error: Item 999 not in cart Item ID: 999 Item ID: 999requests ← [('/users/1', 1, 'valid_token'), ('/users/999', 999, 'valid_token'), ('/users/1', None, 'valid_token'), ('/users/1', 1, None), ('/users/1', 1, 'bad_token')]
76#@help h177# Create custom exceptions when:78# 1. Domain-specific errors need special handling79# 2. Need to attach context data80# 3. Want to catch specific errors differently81# 4. Building a library/API for others82#@end8384# API-style exceptions85class ApiException(Exception):86 def __init__(self, status_code, message, details=None):87 self.status_code = status_code88 self.message = message89 self.details = details90 super().__init__(message)9192class BadRequestError(ApiException):93 def __init__(self, message, details=None):94 super().__init__(400, message, details)9596class NotFoundError(ApiException):97 def __init__(self, resource, resource_id):98 super().__init__(99 404,100 f"{resource} not found",101 {"resource": resource, "id": resource_id}102 )103104class UnauthorizedError(ApiException):105 def __init__(self, message="Unauthorized"):106 super().__init__(401, message)107108def api_request(endpoint, user_id, auth_token):109 if not auth_token:110 raise UnauthorizedError("Missing auth token")111 112 if auth_token != "valid_token":113 raise UnauthorizedError("Invalid token")114 115 if not user_id:116 raise BadRequestError(117 "Missing required parameter",118 {"missing_field": "user_id"}119 )120 121 if user_id not in [1, 2, 3]:122 raise NotFoundError("User", user_id)123 124 return {"user_id": user_id, "data": "..."}125126print("\nAPI requests:")127128requests→ [('/users/1', 1, 'valid_token'), ('/users/999', 999, 'valid_token'), ('/users/1', None, 'valid_token'), ('/users/1', 1, None), ('/users/1', 1, 'bad_token')] = [129 ("/users/1", 1, "valid_token"),130 ("/users/999", 999, "valid_token"),131 ("/users/1", None, "valid_token"),132 ("/users/1", 1, None),133 ("/users/1", 1, "bad_token")134]output API requests:for endpoint, user_id, token in requests:
pass 1 of 5136for endpoint/users/1, user_id1, tokenvalid_token in requests[('/users/1', 1, 'valid_token'), ('/users/999', 999, 'valid_token'), ('/users/1', None, 'valid_token'), ('/users/1', 1, None), ('/users/1', 1, 'bad_token')]:137 try:138 result = api_request(endpoint, user_id, token)All 5 passes — pass 1 is the card above pass endpointuser_idtokenselfresourceresource_ide.status_codee.messagee.detailsmessagedetailsNoneauth_token1 /users/1 1 valid_token — — — — — — — — — — 2 /users/999 999 valid_token ('User', 999) User 999 404 User not found {'resource': 'User', 'id': 999} — — — — 3 /users/1 None valid_token ('Missing required parameter', {'missing_field': 'user_id'}) — — 400 Missing required parameter {'missing_field': 'user_id'} Missing required parameter {'missing_field': 'user_id'} None — 4 /users/1 1 None Missing auth token — — 401 Missing auth token — Missing auth token — — None 5 /users/1 1 bad_token Invalid token — — 401 Invalid token — Invalid token — — bad_token try:
pass 1 of 5136for endpoint, user_id, token in requests:137 try:138 result = api_request(endpoint/users/1, user_id1, tokenvalid_token)139 print(f" {endpoint}: OK")All 5 passes — pass 1 is the card above pass endpointuser_idtokenselfresourceresource_ide.status_codee.messagee.detailsmessagedetailsNoneauth_token1 /users/1 1 valid_token — — — — — — — — — — 2 /users/999 999 valid_token ('User', 999) User 999 404 User not found {'resource': 'User', 'id': 999} — — — — 3 /users/1 None valid_token ('Missing required parameter', {'missing_field': 'user_id'}) — — 400 Missing required parameter {'missing_field': 'user_id'} Missing required parameter {'missing_field': 'user_id'} None — 4 /users/1 1 None Missing auth token — — 401 Missing auth token — Missing auth token — — None 5 /users/1 1 bad_token Invalid token — — 401 Invalid token — Invalid token — — bad_token def api_request(endpoint, user_id, auth_token):
pass 1 of 5108def api_request(endpoint/users/1, user_id1, auth_tokenvalid_token):109 if not auth_token:110 raise UnauthorizedError("Missing auth token")111 112 if auth_token != "valid_token":113 raise UnauthorizedError("Invalid token")114 115 if not user_id:116 raise BadRequestError(117 "Missing required parameter",118 {"missing_field": "user_id"}119 )120 121 if user_id not in [1, 2, 3]:122 raise NotFoundError("User", user_id)123 124 return {"user_id": user_id1, "data": "..."}All 5 passes — pass 1 is the card above pass endpointuser_idauth_tokenselfresourceresource_ide.status_codee.messagee.detailsmessagedetailsNone1 /users/1 1 valid_token — — — — — — — — — 2 /users/999 999 valid_token ('User', 999) User 999 404 User not found {'resource': 'User', 'id': 999} — — — 3 /users/1 None valid_token ('Missing required parameter', {'missing_field': 'user_id'}) — — 400 Missing required parameter {'missing_field': 'user_id'} Missing required parameter {'missing_field': 'user_id'} None 4 /users/1 1 None Missing auth token — — 401 Missing auth token — Missing auth token — — 5 /users/1 1 bad_token Invalid token — — 401 Invalid token — Invalid token — — result ← {'user_id': 1, 'data': '...'}
137try:138 result→ {'user_id': 1, 'data': '...'} = api_request(endpoint/users/1, user_id1, tokenvalid_token)139 print(f" {endpoint/users/1}: OK")140except UnauthorizedError as e:output /users/1: OKif user_id not in [1, 2, 3]:
121if user_id999 not in [1, 2, 3]:122 raise NotFoundError("User", user_id999)def __init__(self, resource, resource_id):
96class NotFoundError(ApiException):97 def __init__(self('User', 999), resourceUser, resource_id999):98 super().__init__(99 404,self.status_code ← 404, self.message ← User not found, self.details ← {'resource': 'User', 'id': 999}
pass 1 of 485class ApiException(Exception):86 def __init__(self('User', 999), status_code404, messageUser not found, details{'resource': 'User', 'id': 999}=NoneNone):87 self.status_code→ 404 = status_code40488 self.message→ User not found = messageUser not found89 self.details→ {'resource': 'User', 'id': 999} = details{'resource': 'User', 'id': 999}90 super().__init__(message)All 4 passes — pass 1 is the card above pass selfstatus_codemessagedetailsendpointe.status_codee.messagee.detailsuser_idauth_tokenself.status_codeself.messageself.details1 ('User', 999) 404 User not found {'resource': 'User', 'id': 999} /users/999 404 User not found {'resource': 'User', 'id': 999} None — 404 User not found {'resource': 'User', 'id': 999} 2 ('Missing required parameter', {'missing_field': 'user_id'}) 400 Missing required parameter {'missing_field': 'user_id'} /users/1 400 Missing required parameter {'missing_field': 'user_id'} — None 400 Missing required parameter {'missing_field': 'user_id'} 3 Missing auth token 401 Missing auth token None /users/1 401 Missing auth token — — bad_token 401 Missing auth token None 4 Invalid token 401 Invalid token None /users/1 401 Invalid token — — — 401 Invalid token None except NotFoundError as e:
141 print(f" {endpoint}: {e.status_code} {e.message}")142except NotFoundError as e:143 print(f" {endpoint/users/999}: {e.status_code404} {e.messageUser not found}")144 print(f" Details: {e.details{'resource': 'User', 'id': 999}}")145except BadRequestError as e:output /users/999: 404 User not found Details: {'resource': 'User', 'id': 999}if not user_id:
115if not user_idNone:116 raise BadRequestError(117 "Missing required parameter",118 {"missing_field": "user_id"}119 )def __init__(self, message, details=None):
92class BadRequestError(ApiException):93 def __init__(self('Missing required parameter', {'missing_field': 'user_id'}), messageMissing required parameter, details{'missing_field': 'user_id'}=NoneNone):94 super().__init__(400, message, details)except BadRequestError as e:
144 print(f" Details: {e.details}")145except BadRequestError as e:146 print(f" {endpoint/users/1}: {e.status_code400} {e.messageMissing required parameter}")147 print(f" Details: {e.details{'missing_field': 'user_id'}}")148except ApiException as e:output /users/1: 400 Missing required parameter Details: {'missing_field': 'user_id'}if not auth_token:
108def api_request(endpoint, user_id, auth_token):109 if not auth_tokenNone:110 raise UnauthorizedError("Missing auth token")def __init__(self, message="Unauthorized"):
pass 1 of 2104class UnauthorizedError(ApiException):105 def __init__(selfMissing auth token, messageMissing auth token="Unauthorized"):106 super().__init__(401, message)except UnauthorizedError as e:
pass 1 of 2139 print(f" {endpoint}: OK")140except UnauthorizedError as e:141 print(f" {endpoint/users/1}: {e.status_code401} {e.messageMissing auth token}")142except NotFoundError as e:output /users/1: 401 Missing auth tokenif auth_token != "valid_token":
112if auth_tokenbad_token != "valid_token":113 raise UnauthorizedError("Invalid token")def __init__(self, message="Unauthorized"):
pass 2 of 2104class UnauthorizedError(ApiException):105 def __init__(selfInvalid token, messageInvalid token="Unauthorized"):106 super().__init__(401, message)except UnauthorizedError as e:
pass 2 of 2139 print(f" {endpoint}: OK")140except UnauthorizedError as e:141 print(f" {endpoint/users/1}: {e.status_code401} {e.messageInvalid token}")142except NotFoundError as e:output /users/1: 401 Invalid tokenmain()
152if __name__ == "__main__":153 main()
Create custom exceptions when you need specific handling or extra data.
Exercise: practical.py
Build a web API with domain-specific exceptions