Todo List TUI in Python
This one is short, useful, and very beginner-friendly.
Quick Run (from repo root)
1cd Site/static/code/examples/python
2python todo_cli.pyYou get a complete todo CLI with JSON persistence.
Features
- add/edit/delete tasks
- mark tasks complete
- filter done vs pending
- persist to JSON file
Code (Pythonic + Short)
1from dataclasses import asdict, dataclass
2from pathlib import Path
3import json
4
5
6SAVE_FILE = Path("todo-python.json")
7
8
9@dataclass
10class Task:
11 id: int
12 text: str
13 done: bool = False
14
15
16def load_tasks() -> list[Task]:
17 if not SAVE_FILE.exists():
18 return []
19 raw = json.loads(SAVE_FILE.read_text())
20 return [Task(**item) for item in raw]
21
22
23def save_tasks(tasks: list[Task]) -> None:
24 SAVE_FILE.write_text(json.dumps([asdict(t) for t in tasks], indent=2))
25
26
27def next_id(tasks: list[Task]) -> int:
28 return max((task.id for task in tasks), default=0) + 1
29
30
31def print_tasks(tasks: list[Task], mode: str = "all") -> None:
32 if mode == "done":
33 view = [t for t in tasks if t.done]
34 elif mode == "open":
35 view = [t for t in tasks if not t.done]
36 else:
37 view = tasks
38
39 if not view:
40 print("No matching tasks.")
41 return
42
43 for task in view:
44 box = "[x]" if task.done else "[ ]"
45 print(f"{task.id}. {box} {task.text}")
46
47
48def find_task(tasks: list[Task], task_id: int) -> Task | None:
49 return next((task for task in tasks if task.id == task_id), None)
50
51
52def read_task_id(prompt: str) -> int:
53 raw = input(prompt).strip()
54 return int(raw) if raw.isdigit() else -1
55
56
57def main():
58 tasks = load_tasks()
59 print("Commands: add, list, done, edit, delete, filter, quit")
60
61 while True:
62 command = input("> ").strip().lower()
63
64 if command == "add":
65 text = input("Task: ").strip()
66 if text:
67 tasks.append(Task(id=next_id(tasks), text=text))
68 save_tasks(tasks)
69 print("Added.")
70
71 elif command == "list":
72 print_tasks(tasks)
73
74 elif command == "done":
75 task_id = read_task_id("Task id: ")
76 task = find_task(tasks, task_id)
77 if task:
78 task.done = True
79 save_tasks(tasks)
80 print("Marked done.")
81 else:
82 print("Task not found.")
83
84 elif command == "edit":
85 task_id = read_task_id("Task id: ")
86 task = find_task(tasks, task_id)
87 if task:
88 new_text = input("New text: ").strip()
89 if new_text:
90 task.text = new_text
91 save_tasks(tasks)
92 print("Updated.")
93 else:
94 print("Task not found.")
95
96 elif command == "delete":
97 task_id = read_task_id("Task id: ")
98 before = len(tasks)
99 tasks = [task for task in tasks if task.id != task_id]
100 if len(tasks) != before:
101 save_tasks(tasks)
102 print("Deleted.")
103 else:
104 print("Task not found.")
105
106 elif command == "filter":
107 mode = input("Mode (all/open/done): ").strip().lower()
108 print_tasks(tasks, mode if mode in {"all", "open", "done"} else "all")
109
110 elif command == "quit":
111 save_tasks(tasks)
112 print(f"Saved to {SAVE_FILE}")
113 break
114
115 else:
116 print("Use: add, list, done, edit, delete, filter, quit")
117
118
119if __name__ == "__main__":
120 main()Run It
1python todo_cli.pyWhy this is pythonic
dataclassmodels each task clearly.Pathandjsonkeep persistence simple and standard-library only.- List comprehensions and generator expressions keep code compact and readable.