AI Hints
Home›Python Tutorial›Three Real Projects
Python · Lesson 39 of 49

Project: A To-Do App

Build a real program, start to finish.

⏱ 2 min read▶ Video at 1:58:54🎓 Free
Watch this lesson in the Python courseWatch on YouTube ↗Full Python course

Time to build something real. Ava wants a to do list for her shop tasks, with adding, completing, and saving them between runs. We will plan it, build it in small steps, and test each step. It uses lists, dictionaries, functions, files, and JSON, everything from the first half of this course.

Project: A To-Do App
From the video: Project: A To-Do App
  • The goal: a to-do list that remembers
  • Plan first, then build in small steps
  • Uses functions, dicts, lists and JSON

Plan the program

Before any code, plan. Each task is a dictionary with a title, and a done flag. All tasks live in a list. We need four functions, add, complete, show, and save and load, with JSON. The main part ties them together. Small functions, each doing one job, are easy to test, and easy to change.

Task = dict{'title': ..., 'done': False}
add(tasks, title)append a new task
complete(tasks, n)mark task n as done
show(tasks)print the list nicely
save / loadJSON file between runs

The core functions

Here are the core functions. Add appends a new task. Complete marks one as done, and refuses a bad number, with a clear error. Show prints a checkbox, and the title, for every task, numbered from one. We build a small list, complete one item, and show the result.

todo_core.pypython
def add(tasks, title):
    tasks.append({"title": title, "done": False})

def complete(tasks, n):
    if not 1 <= n <= len(tasks):
        raise ValueError(f"no task {n}")
    tasks[n - 1]["done"] = True

def show(tasks):
    for i, t in enumerate(tasks, 1):
        print(i, "[x]" if t["done"] else "[ ]", t["title"])

tasks = []
add(tasks, "Water the ferns")
add(tasks, "Pack Maya's order")
complete(tasks, 1)
show(tasks)
Output
1 [x] Water the ferns
2 [ ] Pack Maya's order

Saving and loading

Now persistence. Save writes the list as JSON. Load reads it back, and returns an empty list, when the file does not exist yet, which is the first run. Using pathlib keeps it tidy. We save, then load into a new variable, to prove the data survived.

todo_save.pypython
import json
from pathlib import Path

FILE = Path("tasks.json")

def save(tasks):
    FILE.write_text(json.dumps(tasks, indent=2))

def load():
    return json.loads(FILE.read_text()) if FILE.exists() else []

print(load())
save([{"title": "Water the ferns", "done": True}])
print(load())
Output
[]
[{'title': 'Water the ferns', 'done': True}]

The interactive loop

Finally, the main loop. It shows a menu, reads a command, and calls the right function. Errors are caught, so a typing mistake never crashes the program. Here, we feed it a few commands, add, done, list, and quit, and watch the whole app work. You just built a real program.

todo_app.pypython
tasks = []
while True:
    cmd = input("> ").strip()
    if cmd == "quit":
        break
    elif cmd.startswith("add "):
        tasks.append({"title": cmd[4:], "done": False})
    elif cmd.startswith("done "):
        try:
            tasks[int(cmd[5:]) - 1]["done"] = True
        except (ValueError, IndexError):
            print("no such task")
    elif cmd == "list":
        for i, t in enumerate(tasks, 1):
            print(i, "[x]" if t["done"] else "[ ]", t["title"])
    else:
        print("commands: add, done, list, quit")
Input typed: add Water ferns · add Pack order · done 1 · done 9 · list · help · quit
Output
> add Water ferns
> add Pack order
> done 1
> done 9
no such task
> list
1 [x] Water ferns
2 [ ] Pack order
> help
commands: add, done, list, quit
> quit

Add a remove command

Your turn. Extend the idea, with a function that removes a task by its number, and raises a ValueError for a bad number. Then test it on a small list. Ready? Here is one solution. Check the range, and delete with pop, using a zero based index.

✍️ Exercise: Add a remove command

Write remove(tasks, n) that deletes task number n (1-based), raising ValueError if invalid.

Show hint

tasks.pop(n - 1)

Show solution
remove.pypython
def remove(tasks, n):
    if not 1 <= n <= len(tasks):
        raise ValueError(f"no task {n}")
    return tasks.pop(n - 1)

tasks = ["a", "b", "c"]
print(remove(tasks, 2))
print(tasks)
try:
    remove(tasks, 9)
except ValueError as e:
    print(e)
Output
b
['a', 'c']
no task 9

Summary

Congratulations. You planned a program, built small functions, saved data with JSON, and wrapped everything in an interactive loop. Next, a second project, an expense tracker, with files, dictionaries and reports.

Key takeaways
  • Plan, then small functions
  • JSON for persistence
  • Catch errors in the main loop
Next up: Project: Expense Tracker — CSV, grouping and reports