1 The Problem
We want a to-do list you can actually use: add tasks, see them numbered, remove the ones you finish, and — crucially — have them saved to a file so they survive after you close the program. It teaches lists, a menu loop, and saving data to disk.
2 How to Think About It
Think about the loop the program lives in, before any code:
3 The Build — explained part by part
Here is the complete to-do list. Each part is explained below.
import json
import os
FILE = "tasks.json"
def load_tasks():
# If a saved file exists, read the tasks from it; otherwise start empty.
if os.path.exists(FILE):
with open(FILE) as f:
return json.load(f)
return []
def save_tasks(tasks):
# Write the tasks to the file so they survive after the program closes.
with open(FILE, "w") as f:
json.dump(tasks, f)
tasks = load_tasks()
while True:
print("\n1. Add 2. View 3. Remove 4. Quit")
choice = input("Choose: ")
if choice == "1":
task = input("New task: ")
tasks.append(task)
save_tasks(tasks)
elif choice == "2":
for i, task in enumerate(tasks, 1):
print(f"{i}. {task}")
elif choice == "3":
number = int(input("Remove which number? "))
if 1 <= number <= len(tasks):
tasks.pop(number - 1)
save_tasks(tasks)
elif choice == "4":
break
else:
print("Please choose 1 to 4.")
json turns our list into text we can save and back again; os lets us check if the save file exists.load_tasks() — if a saved file exists, read the tasks from it; if not, start with an empty list. This is what makes tasks survive between runs.
save_tasks(tasks) — write the current list to the file as JSON text. We call this every time the list changes.
while True: — the menu loop. It keeps showing the menu until the user chooses Quit (which
breaks out).tasks.append(task) — add a new task to the end of the list. tasks.pop(number - 1) removes one (we subtract 1 because people count from 1 but lists count from 0).
enumerate(tasks, 1) — walk the list while numbering each item, starting at 1, so the display reads 1, 2, 3.
save_tasks after a change — tasks vanish when you quit.tasks.pop(number) instead of number - 1 — removes the wrong task.if 1 <= number <= len(tasks).4 Test & Prove Each Part
We test the list operations — adding and removing — directly, without needing the menu or a person typing.
def add_task(tasks, task):
tasks.append(task)
return tasks
def remove_task(tasks, number):
if 1 <= number <= len(tasks):
tasks.pop(number - 1)
return tasks
def test_add():
"""Adding a task puts it in the list."""
assert add_task([], "Buy milk") == ["Buy milk"]
def test_remove():
"""Removing task 1 takes the first item out."""
assert remove_task(["a", "b"], 1) == ["b"]
def test_remove_bad_number():
"""Removing a number that does not exist leaves the list unchanged."""
assert remove_task(["a"], 9) == ["a"]
Run with pytest -v. The list logic lives in small functions so tests check it directly. The bad-number test proves the program will not crash on a wrong input.
5 The Interface
What it expects
Choose: 1
New task: Walk the dogWhat it returns
1. Walk the dog
2. Buy milk6 Run It & Automate It
python3 todo.pyAdd a few tasks, quit, run it again — your tasks are still there (saved in
tasks.json).Jenkins runs the tests automatically — each line explained below.
1. Add 2. View 3. Remove 4. Quit
Choose: 1
New task: Walk the dog
1. Add 2. View 3. Remove 4. Quit
Choose: 2
1. Walk the dogsave_tasks(tasks) after each change.json.decoder.JSONDecodeError on start.tasks.json and start fresh.// Jenkinsfile — automated tests on every change.
pipeline {
agent any
stages {
stage('Get the code') { steps { checkout scm } } // download the code
stage('Install') { steps { sh 'python3 -m venv venv && . venv/bin/activate && pip install pytest' } } // test tool
stage('Test') { steps { sh '. venv/bin/activate && pytest -v' } } // run every test
}
post {
success { echo 'All tests passed.' }
failure { echo 'A test failed — look above.' }
}
}
- Mark as done. Add a “done” flag per task instead of removing. (Teaches: dictionaries.)
- Due dates. Store a date with each task. (Teaches: more structured data.)
- Sort tasks. Show them alphabetically or by date. (Teaches: sorting.)