Create exceptions.py with a proper hierarchy and update models.py to speak the app's own error language
Day 48 of 80
Right now your Prompt class probably raises a generic ValueError when something goes wrong. That works, but it's imprecise. When code that calls your class needs to handle errors, it has to catch all ValueErrors — including ones from completely different parts of Python.
Custom exceptions let you be surgical. Code that uses your app can now write:
try:
vault.add(platform="Pika", shot="...", prompt="...")
except InvalidPlatformError as e:
flash(f"Platform not supported: {e.platform}")
except EmptyFieldError as e:
flash(f"Please fill in: {e.field_name}")
except PromptVaultError as e:
flash(f"Vault error: {e}")
InvalidPlatformError and EmptyFieldError both inherit from PromptVaultError, a catch-all for vault errors still works.Create a new file called exceptions.py in your Prompt Vault project folder. This file defines the entire exception hierarchy for your app.
class PromptVaultError(Exception):
"""Base class for all Prompt Vault errors.
Catch this if you want to handle any vault error
without caring about the specific type.
"""
pass
class InvalidPlatformError(PromptVaultError):
"""Raised when an unrecognized platform is provided."""
def __init__(self, platform, valid_platforms):
self.platform = platform
self.valid_platforms = valid_platforms
super().__init__(
f"Unknown platform '{platform}'. "
f"Valid: {', '.join(valid_platforms)}"
)
class EmptyFieldError(PromptVaultError):
"""Raised when a required field is empty or whitespace-only."""
def __init__(self, field_name):
self.field_name = field_name
super().__init__(f"'{field_name}' cannot be empty")
class StorageError(PromptVaultError):
"""Raised when reading or writing storage (JSON file, database) fails."""
pass
InvalidPlatformError stores self.platform and self.valid_platforms — the calling code can access e.platform to show the user exactly what they typed wrong and what's valid. This is far more useful than a plain string message.Always call super().__init__(message) in your custom exception's __init__. This passes the message string up to Exception, which stores it. When someone catches your exception and prints it with str(e) or in an f-string, they get your message. Without that call, the exception would print as an empty string.
Now import your new exceptions and use them in Prompt.__init__ and any validation methods. Replace generic ValueError with the specific type.
from exceptions import InvalidPlatformError, EmptyFieldError
VALID_PLATFORMS = ["Kling", "Runway", "Veo", "Sora", "Hailuo"]
class Prompt:
def __init__(self, platform, shot, prompt):
# Validate before storing anything
if not platform or not platform.strip():
raise EmptyFieldError("platform")
if not shot or not shot.strip():
raise EmptyFieldError("shot")
if not prompt or not prompt.strip():
raise EmptyFieldError("prompt")
self._validate_platform(platform)
self.platform = platform.strip()
self.shot = shot.strip()
self.prompt = prompt.strip()
def _validate_platform(self, platform):
if platform not in VALID_PLATFORMS:
raise InvalidPlatformError(platform, VALID_PLATFORMS)
platform is empty, you get EmptyFieldError("platform"). If it's non-empty but unrecognized, you get InvalidPlatformError with the invalid platform and the list of valid ones. No data is half-stored in an invalid state.Create a short scratch script to verify all three exceptions fire correctly.
from models import Prompt
from exceptions import PromptVaultError, InvalidPlatformError, EmptyFieldError
print("=== Testing Custom Exceptions ===\n")
# Test 1: Empty field
try:
p = Prompt("", "golf swing", "cinematic...")
except EmptyFieldError as e:
print(f"[PASS] EmptyFieldError: {e}")
print(f" Field name: {e.field_name}")
# Test 2: Invalid platform
try:
p = Prompt("Pika", "golf swing", "cinematic...")
except InvalidPlatformError as e:
print(f"[PASS] InvalidPlatformError: {e}")
print(f" Bad platform: {e.platform}")
print(f" Valid options: {e.valid_platforms}")
# Test 3: Catch via base class
try:
p = Prompt("Pika", "golf swing", "cinematic...")
except PromptVaultError as e:
print(f"[PASS] Caught via base class: {type(e).__name__}")
# Test 4: Valid prompt — no exception
try:
p = Prompt("Kling", "Golf swing follow-through", "Cinematic slow motion...")
print(f"[PASS] Valid prompt created: [{p.platform}] {p.shot}")
except PromptVaultError as e:
print(f"[FAIL] Unexpected error: {e}")
python test_exceptions.py. All four tests should pass. Test 3 proves that InvalidPlatformError IS a PromptVaultError — the hierarchy works.Before: your models.py raised ValueError("Pika is not a valid platform"). The calling code had to catch all ValueErrors — including ones that might come from Python's built-in functions deep in your stack.
After: your models.py raises InvalidPlatformError("Pika", VALID_PLATFORMS). The calling code catches exactly that, nothing else. The Flask route can access e.platform to show the user what they typed. The error carries structured data, not just a message string.
This is the difference between "something went wrong" and "you tried to use Pika, which isn't supported; here are the valid options: Kling, Runway, Veo, Sora, Hailuo."
You don't need a custom exception for every possible error. Create them when: (1) calling code needs to handle the error differently based on its type, or (2) the error needs to carry structured data beyond a message string. If a generic ValueError or TypeError works, use it.
exceptions.py with all four exception classes (PromptVaultError, InvalidPlatformError, EmptyFieldError, StorageError)models.py to import and raise the custom exceptionstest_exceptions.py and all four tests passPromptVaultError works for all subtypessuper().__init__() call does in a custom exceptionDay 49 adds logging throughout the app. You'll create logger.py — a central logger factory — and add logger.info(), logger.warning(), and logger.error() calls to models.py, vault.py, and wherever else things happen. By end of day, your app will write a timestamped record of everything it does.