← Back to Python series
🌱
Basic
Review · Integration · File persistence

Week 10 — Basic Capstone: CLI To-Do App

Combine everything from weeks 1–9 into a full command-line to-do application with add, list, complete, and delete commands. Optionally persist tasks to a JSON file.

capstoneprojectCLIJSONintegration
Duration
3 hours
Level
📊 Beginner
Prerequisite
🎯 Weeks 1–9
OUTCOME
A working CLI to-do app you can run from the terminal

What you'll learn

  • 1Integrate lists, dicts, functions, and loops in one project
  • 2Handle user input robustly with error messages
  • 3Save and load tasks from a JSON file
  • 4Structure code into well-named functions
  • 5Practice the full development loop: plan → code → test

1. Project Specification

  • Commands: add <title>, list, done <id>, delete <id>, quit
  • Each task: id (auto-increment), title, done (bool)
  • Persist tasks in tasks.json on disk
  • Show task count and completion stats on list

2. Architecture

  • load_tasks() / save_tasks() — JSON file I/O
  • add_task(tasks, title) — append new task dict
  • list_tasks(tasks) — print numbered table
  • mark_done(tasks, task_id) — find by id and set done=True
  • delete_task(tasks, task_id) — filter out by id
  • main() — REPL loop with command dispatch

3. Sample Session

text
Todo App — type 'help' for commands
> add Buy groceries
  [1] added.
> add Write report
  [2] added.
> list
  ID  Status  Title
  1   [ ]     Buy groceries
  2   [ ]     Write report
> done 1
  [1] marked as done.
> list
  ID  Status  Title
  1   [x]     Buy groceries
  2   [ ]     Write report
  Done: 1/2
> quit
  Bye!

4. Extension Challenges

  • Add a priority field (high/medium/low) and sort by priority on list
  • Add a due date field and highlight overdue tasks in red (colorama)
  • Add search <keyword> command to filter tasks

💻 Examples

Run these examples and check the output yourself.

todo.pyComplete to-do app implementation
CODE
import json, os

FILE = "tasks.json"

def load_tasks():
    if os.path.exists(FILE):
        with open(FILE) as f:
            return json.load(f)
    return []

def save_tasks(tasks):
    with open(FILE, "w") as f:
        json.dump(tasks, f, indent=2)

def add_task(tasks, title):
    task_id = max((t["id"] for t in tasks), default=0) + 1
    tasks.append({"id": task_id, "title": title, "done": False})
    print(f"  [{task_id}] added.")

def list_tasks(tasks):
    if not tasks:
        print("  No tasks.")
        return
    print(f"  {'ID':<4} {'Status':<8} Title")
    for t in tasks:
        status = "[x]" if t["done"] else "[ ]"
        print(f"  {t['id']:<4} {status:<8} {t['title']}")
    done = sum(1 for t in tasks if t["done"])
    print(f"  Done: {done}/{len(tasks)}")

def mark_done(tasks, task_id):
    for t in tasks:
        if t["id"] == task_id:
            t["done"] = True
            print(f"  [{task_id}] marked as done.")
            return
    print(f"  Task {task_id} not found.")

def delete_task(tasks, task_id):
    orig = len(tasks)
    tasks[:] = [t for t in tasks if t["id"] != task_id]
    if len(tasks) < orig:
        print(f"  [{task_id}] deleted.")
    else:
        print(f"  Task {task_id} not found.")

def main():
    tasks = load_tasks()
    print("Todo App — type 'help' for commands")
    while True:
        line = input("> ").strip()
        if not line:
            continue
        parts = line.split(maxsplit=1)
        cmd = parts[0].lower()
        arg = parts[1] if len(parts) > 1 else ""
        if cmd == "add":
            if arg:
                add_task(tasks, arg)
                save_tasks(tasks)
            else:
                print("  Usage: add <title>")
        elif cmd == "list":
            list_tasks(tasks)
        elif cmd == "done":
            try:
                mark_done(tasks, int(arg))
                save_tasks(tasks)
            except ValueError:
                print("  Usage: done <id>")
        elif cmd == "delete":
            try:
                delete_task(tasks, int(arg))
                save_tasks(tasks)
            except ValueError:
                print("  Usage: delete <id>")
        elif cmd in ("quit", "exit"):
            print("  Bye!")
            break
        elif cmd == "help":
            print("  Commands: add <title>, list, done <id>, delete <id>, quit")
        else:
            print(f"  Unknown command: {cmd}")

if __name__ == "__main__":
    main()

📝 Exercises

Try them yourself first, then open the solution to compare.

Exercise 1

Implement the base app

Goal: Implement the to-do app as described with add, list, done, delete, and quit.

Requirements
  • All 5 commands work correctly
  • Tasks persist across runs (JSON file)
  • Invalid input shows helpful error messages
Grading
  • · add/list work — 30%
  • · done/delete work — 30%
  • · JSON persistence — 30%
  • · Error handling — 10%
Exercise 2

Add priority support

Goal: Extend the app with a priority field (high/medium/low).

Requirements
  • add command accepts optional priority: add <title> [--priority high]
  • list sorts by priority: high → medium → low
  • Priority shown in list output
Example code / lecture materials

All lecture materials and example code are openly available on GitHub.

View on GitHub ↗