Replace the loose load/save functions with a single object that owns your entire prompt collection.
Day 44 of 80
Yesterday you built the Prompt class — a single prompt with behavior. Today you build PromptLibrary — a class that manages the entire collection of prompts.
This replaces the loose load_prompts() / save_prompts() functions from helpers.py with a single object that owns both the data and all operations on it.
Open models.py and add these two imports at the top, next to the existing from datetime import datetime:
from datetime import datetime
import json
from pathlib import Path
json is the module you've been using since Week 3 — now you're moving the load/save logic into the class itself.
pathlib.Path is a modern way to handle file paths. Path("folder") / "file.json" builds correct paths on Windows, Mac, and Linux without worrying about backslashes vs forward slashes.
Add this class to the bottom of models.py, after the Prompt class:
class PromptLibrary:
"""Manages a collection of Prompts — load, save, search, filter.
This replaces the loose functions in helpers.py with a single object
that handles everything related to your prompt collection.
"""
def __init__(self, file_path="prompts.json"):
"""Set up the library with a path to the data file."""
# Path() converts a string to a path object — cleaner than raw strings.
self.file_path = Path(file_path)
# Load prompts immediately when the library is created.
self.prompts = self._load()
def _load(self):
"""Load prompts from the JSON file into Prompt objects."""
if not self.file_path.exists():
return [] # no file yet — start fresh
with open(self.file_path, "r") as f:
raw_data = json.load(f)
# Convert each dictionary into a Prompt object using from_dict().
# This is where from_dict() pays off — it knows the shape of the data.
return [Prompt.from_dict(item) for item in raw_data]
def save(self):
"""Save all prompts back to the JSON file."""
# Convert each Prompt object to a dict, then write to JSON.
data = [p.to_dict() for p in self.prompts]
with open(self.file_path, "w") as f:
json.dump(data, f, indent=2)
def add(self, prompt):
"""Add a Prompt to the library and save immediately."""
self.prompts.append(prompt)
self.save()
def delete(self, index):
"""Delete a prompt by index. Returns the deleted Prompt or None."""
if 0 <= index < len(self.prompts):
removed = self.prompts.pop(index)
self.save()
return removed
return None
def search(self, query):
"""Search prompts by keyword (checks platform, shot, and prompt text)."""
query = query.lower()
return [
p for p in self.prompts
if query in p.platform.lower()
or query in p.shot.lower()
or query in p.prompt_text.lower()
]
def filter_by_platform(self, platform):
"""Return only prompts for a specific platform."""
return [p for p in self.prompts if p.platform.lower() == platform.lower()]
def platform_counts(self):
"""Count prompts per platform. Returns a dictionary."""
counts = {}
for p in self.prompts:
counts[p.platform] = counts.get(p.platform, 0) + 1
return counts
def __len__(self):
"""len(library) returns the number of prompts."""
return len(self.prompts)
def __iter__(self):
"""for prompt in library: — lets you loop over prompts directly."""
return iter(self.prompts)
_load() is called in __init__ — the library loads its data automatically when you create it. No separate "load" step. PromptLibrary("prompts.json") opens the file and gives you a ready-to-use object.
add() calls save() immediately — you can't add a prompt without saving it. This keeps the in-memory list and the file in sync. Never "add without saving" by accident.
__len__ and __iter__ are dunder methods that make your class behave like a built-in Python container. len(library) and for prompt in library: work naturally.
The search uses a list comprehension — the same filter logic you wrote in Week 3, but now it lives inside the object that owns the data. One place to find it, one place to change it.
Before wiring it into the app, make sure it works. Create a temporary test_library.py:
from models import Prompt, PromptLibrary
# --- Create a library (uses test_prompts.json so we don't touch real data) ---
library = PromptLibrary("test_prompts.json")
print(f"Loaded: {len(library)} prompts")
# --- Add some prompts ---
p1 = Prompt("Kling", "Golf swing follow-through", "Cinematic slow motion golf swing, golden hour")
p2 = Prompt("Runway", "Ocean aerial", "Aerial tracking shot over turquoise ocean")
p3 = Prompt("Kling", "Crowd reaction", "Slow motion crowd, bokeh background, stadium")
library.add(p1)
library.add(p2)
library.add(p3)
print(f"After adding 3: {len(library)} prompts")
# --- Test iteration (uses __iter__) ---
print("\nAll prompts:")
for p in library:
print(f" {p}") # uses __str__ from the Prompt class
# --- Test search ---
print("\nSearch 'golf':")
for p in library.search("golf"):
print(f" {p}")
# --- Test filter ---
print("\nFilter Kling:")
for p in library.filter_by_platform("Kling"):
print(f" {p}")
# --- Platform counts ---
print("\nPlatform counts:")
print(library.platform_counts()) # {'Kling': 2, 'Runway': 1}
# --- Delete one ---
removed = library.delete(0)
print(f"\nDeleted: {removed}")
print(f"After delete: {len(library)} prompts")
# --- Test persistence: reload the file ---
library2 = PromptLibrary("test_prompts.json")
print(f"\nReloaded from disk: {len(library2)} prompts")
# Should match — the file saved after each add/delete
The persistence test at the end is the most important one. Creating a second PromptLibrary from the same file confirms that your data actually survived. If library2 has the same prompts as library, your load/save cycle works.
PromptLibrary class added to models.py with all methods__len__ makes len(library) work__iter__ makes for p in library: worksearch() returns matching prompts (case-insensitive)filter_by_platform() returns only prompts for a given platformDay 45 wires the PromptLibrary class into vault.py — replacing all the loose function calls with clean object methods. You'll also see how the Flask web app benefits from the same refactor.