← Back to course
Code Crew G6+ / Week 12 / Wednesday
3/6
Week 12 Β· Ship and demo

Wednesday

Polish and snapshot
// Test, polish, snapshot, graduate
⏱ about 30 min

Wednesday: Polish and snapshot

Mission Log passes every test and survives every trick Raven can think of. Rocket wants to add a new feature right away. Raven puts a hand on the desk.

"What do you notice about the version on your screen?" she asks. "It works perfectly. If the next change goes wrong, how do you get this exact version back?"

Rocket scratches his head. "I would have to undo a lot of typing," he says, "and hope I remember everything."

Nova projects a neat row of folders labeled v1, v2, v3. "Would you like a hint?" she asks. "Before a big change, save a copy with a version number. That copy is a snapshot. You never edit it, so it is always safe."

Rocket opens the File menu.

The complete Mission Log

Here is the finished program with both fixes. Read it top to bottom and check the comments and docstrings. Every function says what it does.

# mission_log.py
# Mission Log: keep dated notes about your missions.
# Entries are saved in mission_log.json, in the same folder.
# Run this file to use the menu. test_mission_log.py checks the functions.

import json
from datetime import date

LOG_FILE = "mission_log.json"


def load_entries():
    """Read saved entries, or start an empty list on the first run.

    Gives back None if the file is damaged, so nothing is overwritten.
    """
    try:
        with open(LOG_FILE) as f:
            return json.load(f)
    except FileNotFoundError:
        return []
    except json.JSONDecodeError:
        return None


def save_entries(entries):
    """Write every entry to the JSON file."""
    with open(LOG_FILE, "w") as f:
        json.dump(entries, f, indent=2)


def make_entry(text, tag, day):
    """Build one entry as a dictionary."""
    return {"date": day, "tag": tag, "text": text}


def format_entry(number, entry):
    """One entry as a line of text."""
    return f"{number}. {entry['date']} [{entry['tag']}] {entry['text']}"


def search_entries(entries, word):
    """Entries whose text or tag contains word, ignoring capitals."""
    word = word.lower()
    found = []
    for entry in entries:
        if word in entry["text"].lower() or word in entry["tag"]:
            found.append(entry)
    return found


def count_tags(entries):
    """A dictionary of how many entries have each tag."""
    counts = {}
    for entry in entries:
        tag = entry["tag"]
        counts[tag] = counts.get(tag, 0) + 1
    return counts


def add_entry(entries):
    """Ask for a new entry, add it to the list and save."""
    text = input("What happened? ").strip()
    if text == "":
        print("Nothing typed, so nothing saved.")
        return
    tag = input("Tag (build, test, idea): ").strip().lower()
    if tag == "":
        tag = "note"
    entries.append(make_entry(text, tag, date.today().isoformat()))
    save_entries(entries)
    print(f"Saved entry {len(entries)}.")


def show_entries(entries):
    """Print every entry with its number."""
    if len(entries) == 0:
        print("No entries yet.")
        return
    for number, entry in enumerate(entries, start=1):
        print(format_entry(number, entry))


def run_search(entries):
    """Ask for a word and print the matching entries."""
    word = input("Search for: ").strip()
    if word == "":
        print("Type a word to search for.")
        return
    found = search_entries(entries, word)
    for number, entry in enumerate(found, start=1):
        print(format_entry(number, entry))
    print(f"Matches found: {len(found)}")


def show_stats(entries):
    """Print the number of entries and a count for each tag."""
    print(f"Entries in total: {len(entries)}")
    for tag, count in count_tags(entries).items():
        print(f"  {tag}: {count}")


def main():
    """Load the log, then show the menu until the user quits."""
    entries = load_entries()
    if entries is None:
        print(f"{LOG_FILE} is damaged. Rename it, then run Mission Log again.")
        return
    print(f"Mission Log ready. Entries loaded: {len(entries)}")
    while True:
        print()
        print("1 Add   2 List   3 Search   4 Stats   5 Quit")
        choice = input("Choose 1 to 5: ").strip()
        if choice == "1":
            add_entry(entries)
        elif choice == "2":
            show_entries(entries)
        elif choice == "3":
            run_search(entries)
        elif choice == "4":
            show_stats(entries)
        elif choice == "5":
            print("Log saved. See you next mission!")
            break
        else:
            print("Please type a number from 1 to 5.")


if __name__ == "__main__":
    main()

A final check run, with three entries already saved:

Mission Log ready. Entries loaded: 3

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 3
Search for: 
Type a word to search for.

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 3
Search for: sound
1. 2026-04-11 [idea] Add a sound when the clock ends
Matches found: 1

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 4
Entries in total: 3
  build: 1
  test: 1
  idea: 1

1 Add   2 List   3 Search   4 Stats   5 Quit
Choose 1 to 5: 5
Log saved. See you next mission!

Saving a snapshot

  1. Run test_mission_log.py and make sure every test passes.
  2. In the mission_log.py window, choose File, then Save Copy As.
  3. Name the copy mission_log_v1.py and save it in your mission folder.
  4. Keep working in mission_log.py. Never edit the v1 copy.
  5. Before the next big change, save another copy as mission_log_v2.py.

Save Copy As saves a copy but keeps your window on the original file, which is exactly what a snapshot needs.

You already have mission_log_v1.py. What do you name the next snapshot?
How many functions are defined in the final mission_log.py, including main?
SNAPSHOTS AND DOCS
  • Read the question.
  • Tap your answer.
A change breaks Mission Log and you cannot find the bug. What does the snapshot let you do?
What is the most useful docstring for search_entries?
At your computer
1. Compare your mission_log.py with the complete program, line by line.
2. Run test_mission_log.py. Fix anything until all tests pass.
3. Run mission_log.py and try every menu choice once.
4. Choose File, then Save Copy As, and save mission_log_v1.py in your mission folder.
5. Open your mission folder in the file browser and check that both files are there.

Your tool is tested, documented and safely saved. Tomorrow you will demo it to a real user.

← Tuesday