Wire Up Save and Load
Call load on startup and save after every change to persist expenses
💻
Writing code and entering commands is only available on desktop. Open this page on a larger screen to complete this chapter.
Two connections to make
You have a save_expenses function and a load_expenses function, but neither one runs yet. You need to connect them to the rest of your code in two places:
- Load on startup: Call
load_expenses()right after the default values are set. This restores saved data when the server starts. If no file exists, the function does nothing and the defaults stay in place. - Save after changes: Call
save_expenses()inside every endpoint that modifies data. That meanscreate_expense,delete_expense, andupdate_expense. Thelist_expensesandget_expenseendpoints only read data, so they do not need a save call.
After this chapter, you can stop the server, restart it, and see all your expenses still there.
Instructions
Wire the save and load functions into the existing code.
- Add
load_expenses()on the line aftercounter = 0. This runs once when the server starts. - In
create_expense, addsave_expenses()after the line that stores the expense in the dictionary, before thereturnstatement. - In
delete_expense, addsave_expenses()after the 404 check, before thereturnstatement. - In
update_expense, addsave_expenses()after the line that applies the updates, before thereturnstatement.
import json
from datetime import datetime
from pathlib import Path
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field, field_validator
from typing import Literal, Optional
app = FastAPI()
DATA_FILE = Path("expenses.json")
class Expense(BaseModel):
description: str = Field(min_length=1)
amount: float = Field(gt=0)
category: Literal["food", "transport", "entertainment", "utilities", "other"]
date: Optional[str] = None
@field_validator("date")
@classmethod
def validate_date_format(cls, v):
if v is not None:
try:
datetime.strptime(v, "%Y-%m-%d")
except ValueError:
raise ValueError("Date must be in YYYY-MM-DD format")
return v
class ExpenseUpdate(BaseModel):
description: Optional[str] = Field(default=None, min_length=1)
amount: Optional[float] = Field(default=None, gt=0)
category: Optional[Literal["food", "transport", "entertainment", "utilities", "other"]] = None
def save_expenses():
data = {"counter": counter, "expenses": expenses}
DATA_FILE.write_text(json.dumps(data, indent=2))
def load_expenses():
global expenses, counter
if DATA_FILE.exists():
data = json.loads(DATA_FILE.read_text())
expenses = {int(k): v for k, v in data["expenses"].items()}
counter = data["counter"]
expenses = {}
counter = 0
# Step 1: Call load_expenses() here
@app.post("/expenses", status_code=201)
def create_expense(expense: Expense):
global counter
counter += 1
expenses[counter] = {"id": counter, **expense.model_dump()}
# Step 2: Call save_expenses() here
return expenses[counter]
@app.get("/expenses")
def list_expenses(category: str = None):
if category:
return [e for e in expenses.values() if e["category"] == category]
return list(expenses.values())
@app.get("/expenses/{expense_id}")
def get_expense(expense_id: int):
if expense_id not in expenses:
raise HTTPException(status_code=404, detail="Expense not found")
return expenses[expense_id]
@app.delete("/expenses/{expense_id}")
def delete_expense(expense_id: int):
if expense_id not in expenses:
raise HTTPException(status_code=404, detail="Expense not found")
# Step 3: Call save_expenses() here
return expenses.pop(expense_id)
@app.patch("/expenses/{expense_id}")
def update_expense(expense_id: int, updates: ExpenseUpdate):
if expense_id not in expenses:
raise HTTPException(status_code=404, detail="Expense not found")
expenses[expense_id].update(updates.model_dump(exclude_unset=True))
# Step 4: Call save_expenses() here
return expenses[expense_id]
Interactive Code Editor
Sign in to write and run code, track your progress, and unlock all chapters.
Sign In to Start Coding