{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "17cd2526",
   "metadata": {},
   "source": [
    "# 3.2 — Saving & Loading Files\n",
    "\n",
    "**Goal:** keep your data after the program closes, using a JSON file, and handle a first run with no file.\n",
    "\n",
    "**How to use this notebook**\n",
    "\n",
    "- Run cells **top to bottom**. Click a cell and press **Shift + Enter**.\n",
    "- Cells marked **TODO** are yours. Replace the `None`, `pass` or empty list with your code.\n",
    "- Under each TODO is a **check cell**. It stays quiet when your code is right. When it's wrong, it tells you what to look at. Read that message before you ask for help.\n",
    "- Each part starts with a worked example, then gives you practice in different contexts (library, gradebook, bike shop, budget and so on). Notice the *pattern* repeating across them.\n",
    "- Finished? Run **Part: Tidy up** so your practice files don't pile up."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2b1537dd",
   "metadata": {},
   "source": [
    "## Part 1 — Save, then load\n",
    "\n",
    "When your program closes, its variables disappear. A file keeps the data between runs. JSON is a text format that stores dictionaries and lists, and Python reads it back into the same shapes.\n",
    "\n",
    "These three functions do the work. Run the cell, then read the example."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "80e91183",
   "metadata": {},
   "outputs": [],
   "source": [
    "import json\n",
    "import os\n",
    "\n",
    "def save_data(data, filename):\n",
    "    \"\"\"Write the whole data set to a JSON file, replacing what was there.\"\"\"\n",
    "    with open(filename, \"w\") as f:\n",
    "        json.dump(data, f, indent=2)\n",
    "\n",
    "def load_data(filename):\n",
    "    \"\"\"Read a JSON file and return the data it holds.\"\"\"\n",
    "    with open(filename) as f:\n",
    "        return json.load(f)\n",
    "\n",
    "def load_or_start(filename):\n",
    "    \"\"\"Load the file if it exists. On a first run, return an empty dictionary.\"\"\"\n",
    "    if os.path.exists(filename):\n",
    "        return load_data(filename)\n",
    "    return {}"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "1ff37552",
   "metadata": {},
   "source": [
    "**Worked example.** Save a gradebook, look at the file that was written, then load it back."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "1f73a273",
   "metadata": {},
   "outputs": [],
   "source": [
    "gradebook = {\"Priya\": {\"math\": [88, 92]}, \"Sam\": {\"math\": [65, 70]}}\n",
    "save_data(gradebook, \"demo_gradebook.json\")\n",
    "\n",
    "print(open(\"demo_gradebook.json\").read())   # this is the file, as text\n",
    "loaded = load_data(\"demo_gradebook.json\")\n",
    "print(loaded == gradebook)                  # True"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "544b2121",
   "metadata": {},
   "source": [
    "Look at the printed text. It's your data, written with double quotes. That's what JSON looks like."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e510f81f",
   "metadata": {},
   "source": [
    "## Part 2 — Two rules that catch people out\n",
    "\n",
    "**Rule 1: keys always come back as text.** **Rule 2: only some types can be saved.** Dictionaries, lists, text, numbers, `true`/`false` and `None` are fine. Sets and custom objects are not.\n",
    "\n",
    "Run this and read the output carefully."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0d59463b",
   "metadata": {},
   "outputs": [],
   "source": [
    "save_data({1: \"Priya\", 2: \"Sam\"}, \"demo_ids.json\")\n",
    "print(load_data(\"demo_ids.json\"))    # look at the keys: are they numbers, or text?\n",
    "\n",
    "try:\n",
    "    save_data({\"tags\": {\"red\", \"blue\"}}, \"demo_bad.json\")   # a set can't be saved\n",
    "except TypeError as error:\n",
    "    print(\"Couldn't save:\", error)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "873aaf12",
   "metadata": {},
   "source": [
    "### Practice A: quiz scores by student number\n",
    "\n",
    "The keys here are student numbers. After a save and load, they'll be text. Save `student_scores`, load it back into `reloaded`, then build `fixed` with each key turned back into an `int`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3532b0a6",
   "metadata": {},
   "outputs": [],
   "source": [
    "student_scores = {101: 88, 102: 91}\n",
    "\n",
    "# TODO: save student_scores to \"practice_scores.json\"\n",
    "# TODO: load it back into reloaded\n",
    "# TODO: build fixed, a new dictionary with each key turned back into an int\n",
    "reloaded = None\n",
    "fixed = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b2f8035e",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert fixed == student_scores, \"fixed should match the original keys and values\"\n",
    "assert all(isinstance(k, int) for k in fixed), \"every key in fixed should be an int\"\n",
    "print(\"✅ Practice A done\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f5bd603",
   "metadata": {},
   "source": [
    "### Practice B: an inventory that survives a restart\n",
    "\n",
    "Save the inventory, then load it back into `inventory_back`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "095b6f8c",
   "metadata": {},
   "outputs": [],
   "source": [
    "inventory = {\"apple\": {\"qty\": 4, \"price\": 0.5}, \"bread\": {\"qty\": 1, \"price\": 2.25}}\n",
    "\n",
    "# TODO: save inventory to \"practice_inventory.json\", then load it into inventory_back\n",
    "inventory_back = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "430ffab9",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert inventory_back == inventory, \"the data should be exactly the same after save and load\"\n",
    "print(\"✅ Practice B done\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "45904160",
   "metadata": {},
   "source": [
    "### Practice C: the first run (budget tracker)\n",
    "\n",
    "A budget app runs for the first time, and there's no file yet. Make sure `practice_budget.json` doesn't exist (delete it if it does), then set `budget` to the result of `load_or_start`. It should start empty and not crash."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73ec7e6c",
   "metadata": {},
   "outputs": [],
   "source": [
    "# TODO: if practice_budget.json exists, delete it with os.remove()\n",
    "# TODO: set budget to load_or_start(\"practice_budget.json\")\n",
    "budget = None"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0e0b2e46",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert budget == {}, \"a first run should start with empty data\"\n",
    "print(\"✅ Practice C done\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "19329211",
   "metadata": {},
   "source": [
    "### Practice D: a leaderboard that saves after every change\n",
    "\n",
    "Write `add_score`. It keeps only each player's **best** score, and returns the board. Then the check saves and reloads the board."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2d7d64c",
   "metadata": {},
   "outputs": [],
   "source": [
    "def add_score(board, player, score):\n",
    "    \"\"\"Record a score for a player, keeping only their best. Return the board.\"\"\"\n",
    "    # TODO: write this function\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9f36f744",
   "metadata": {},
   "outputs": [],
   "source": [
    "board = {\"Ana\": 50}\n",
    "add_score(board, \"Ana\", 40)     # lower: should not replace 50\n",
    "add_score(board, \"Ben\", 70)     # new player\n",
    "add_score(board, \"Ana\", 90)     # higher: should replace 50\n",
    "assert board == {\"Ana\": 90, \"Ben\": 70}, \"got \" + str(board)\n",
    "save_data(board, \"practice_leaderboard.json\")\n",
    "assert load_data(\"practice_leaderboard.json\") == board, \"the saved board should load back the same\"\n",
    "print(\"✅ Practice D done\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8ada1d43",
   "metadata": {},
   "source": [
    "## Part 3 — Which one?\n",
    "\n",
    "Replace each `None` with `\"A\"` or `\"B\"`.\n",
    "\n",
    "- **Q1:** Which mode should you use to *read* a file you saved earlier? A) `\"w\"`  B) `\"r\"`\n",
    "- **Q2:** You want the data as *text* to print, not written to a file. Which function? A) `json.dump`  B) `json.dumps`\n",
    "- **Q3:** When should you save? A) only when the program quits  B) after every change"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "64826b0f",
   "metadata": {},
   "outputs": [],
   "source": [
    "q1 = None   # \"A\" or \"B\"\n",
    "q2 = None   # \"A\" or \"B\"\n",
    "q3 = None   # \"A\" or \"B\""
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a7a41fc0",
   "metadata": {},
   "outputs": [],
   "source": [
    "assert q1 == \"B\", \"Q1: reading uses \\\"r\\\". \\\"w\\\" opens the file for writing and wipes it.\"\n",
    "assert q2 == \"B\", \"Q2: dumps returns text. dump writes to a file.\"\n",
    "assert q3 == \"B\", \"Q3: if the program crashes, anything not saved is lost.\"\n",
    "print(\"✅ Part 3 done\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d1ec36c",
   "metadata": {},
   "source": [
    "## Tidy up\n",
    "\n",
    "Run this to delete the practice files."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6768aecf",
   "metadata": {},
   "outputs": [],
   "source": [
    "for f in [\"demo_gradebook.json\", \"demo_ids.json\", \"demo_bad.json\", \"practice_scores.json\",\n",
    "          \"practice_inventory.json\", \"practice_budget.json\", \"practice_leaderboard.json\"]:\n",
    "    if os.path.exists(f):\n",
    "        os.remove(f)\n",
    "print(\"Tidied up.\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "90e7b382",
   "metadata": {},
   "source": [
    "## You're done with this lesson when…\n",
    "\n",
    "- [ ] Data I save comes back exactly the same\n",
    "- [ ] A first run with no file doesn't crash\n",
    "- [ ] I know why keys come back as text, and how to fix it\n",
    "- [ ] I save after every change, not only at the end\n",
    "- [ ] I can say which of my app's data I'd save, and in what shape"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
