Pydantic models, automatic validation, proper HTTP status codes — the real production-grade API for the DVP Prompt Vault.
Day 58 of 80
A Pydantic model is a class that describes what data should look like — its fields, their types, and any custom rules. FastAPI uses these models to automatically validate incoming requests before they reach your code.
Think of it as a contract: "If you want to create a prompt, the request body must have these fields, and they must be these types." If the request doesn't match, FastAPI returns a clear 422 error — you never write the validation logic yourself.
You'll define two models today:
PromptCreate — the shape of data coming in (a POST request body)PromptResponse — the shape of data going out (what the API returns)api.pyThis is the full production API. It connects to your existing SQLite database via the Database class you built in earlier weeks. Create this file in your DVP Prompt Vault project root.
from fastapi import FastAPI, HTTPException
from fastapi.responses import JSONResponse
from pydantic import BaseModel, field_validator
from typing import Optional
from database import Database
app = FastAPI(
title="DVP Prompt Vault API",
description="REST API for managing AI video generation prompts",
version="1.0.0"
)
db = Database()
# ─── Pydantic Models ───────────────────────────────────────────
class PromptCreate(BaseModel):
"""Shape of data required to create a new prompt."""
platform: str
shot: str
prompt_text: str
@field_validator("platform")
@classmethod
def platform_must_be_valid(cls, v):
allowed = ["Kling", "Runway", "Veo", "Pika", "Sora"]
if v not in allowed:
raise ValueError(f"Platform must be one of: {', '.join(allowed)}")
return v
@field_validator("shot", "prompt_text")
@classmethod
def must_not_be_empty(cls, v):
if not v.strip():
raise ValueError("Field cannot be empty or whitespace")
return v.strip()
class PromptResponse(BaseModel):
"""Shape of data returned by the API for a prompt."""
id: int
platform: str
shot: str
prompt_text: str
created_at: Optional[str] = None
@field_validator decorator runs your custom check after Pydantic's basic type check passes. If your validator raises a ValueError, Pydantic converts it into a clean 422 response with the error message you wrote.# ─── Routes ────────────────────────────────────────────────────
@app.get("/prompts", response_model=list[PromptResponse])
def get_prompts(platform: Optional[str] = None):
"""Return all prompts, with optional platform filter."""
try:
if platform:
results = db.get_prompts_by_platform(platform)
else:
results = db.get_all_prompts()
return results
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/prompts/{prompt_id}", response_model=PromptResponse)
def get_prompt(prompt_id: int):
"""Return a single prompt by ID."""
prompt = db.get_prompt(prompt_id)
if not prompt:
raise HTTPException(status_code=404, detail=f"Prompt {prompt_id} not found")
return prompt
@app.post("/prompts", response_model=PromptResponse, status_code=201)
def create_prompt(prompt: PromptCreate):
"""Create a new prompt. Returns 201 Created on success."""
try:
new_id = db.add_prompt(
platform=prompt.platform,
shot=prompt.shot,
prompt_text=prompt.prompt_text
)
return db.get_prompt(new_id)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.delete("/prompts/{prompt_id}", status_code=204)
def delete_prompt(prompt_id: int):
"""Delete a prompt. Returns 204 No Content on success."""
prompt = db.get_prompt(prompt_id)
if not prompt:
raise HTTPException(status_code=404, detail=f"Prompt {prompt_id} not found")
db.delete_prompt(prompt_id)
return None # 204 = No Content, intentionally empty response
@app.get("/stats")
def get_stats():
"""Return summary statistics about the prompt vault."""
all_prompts = db.get_all_prompts()
platforms = {}
for p in all_prompts:
platform = p["platform"]
platforms[platform] = platforms.get(platform, 0) + 1
return {
"total_prompts": len(all_prompts),
"by_platform": platforms,
"platforms_count": len(platforms)
}
HTTPException returns a proper JSON error response with the correct HTTP status code. 404 = resource not found. 422 = validation error (FastAPI generates these automatically). 500 = server error. Always use HTTPException instead of returning error dicts manually.Every HTTP response has a status code. These are the ones your API uses today:
status_code=201 in the decorator.response_model DoesWhen you add response_model=PromptResponse to a route decorator, FastAPI does two things:
/docs UI shows exactly what fields the endpoint returns, including their types.PromptResponse. This is a security feature — you can't accidentally expose data you didn't intend to./docsOpen http://localhost:8000/docs and run through each endpoint:
Replace the db = Database() lines with the in-memory prompts list from Day 57. The goal today is understanding the routing and validation patterns — you can swap in the real database once you've confirmed the API structure works correctly.
api.py with Pydantic models (PromptCreate and PromptResponse)@field_validator to reject invalid platform names automaticallyHTTPException instead of returning error dicts manually/docs — including an invalid POST to confirm 422 validationresponse_model does and why it matters for securityYou'll build a simple HTML page that uses fetch() to call your FastAPI backend. This closes the loop: Python API serving JSON to a JavaScript frontend — the same pattern used in every modern web app.