Command-Line To-Do App
Intermediate Project — Learn File Handling, Data Persistence, and OOP
🧠 Project Overview
A To-Do List app is one of the most essential beginner–intermediate Python projects. It teaches file management, user input handling, and object-oriented programming (OOP).
In real life, the same logic is used in productivity software like Todoist or Microsoft To Do — just with a graphical interface instead of command line.
🎯 Goal
Build a command-line To-Do app that can:
- • Add, delete, and update tasks
- • Mark tasks as complete
- • Save and load data from a file
- • Organize tasks by category, due date, or priority
- • Provide clean user experience through menus
🧩 Core Concepts
- • File I/O (reading and writing text or JSON files)
- • Classes & Objects (OOP design)
- • Input validation and error handling
- • String and date manipulation
- • Loops and conditional logic
- • Data persistence
💡 Technologies & Concepts Used
| Feature | Description |
|---|---|
| open() | File reading/writing |
| json module | Structured storage |
| datetime | For due dates & reminders |
| colorama | CLI color highlighting |
| os | Check and manage file existence |
⚙️ Starter Code
💡 Note: Interactive Code
This is a command-line app that uses input() for user interaction. The browser demo below shows a simplified version. For the full interactive experience, copy the code below and run it in your local Python environment (IDLE, VS Code, or terminal).Jump to the Browser Demo.
Here's a functional starting version of your app. Copy it to your local Python editor and run it.
import json
from datetime import datetime
class Task:
def __init__(self, title, priority="Medium", category="General", completed=False, created=None, due=None):
self.title = title
self.priority = priority
self.category = category
self.completed = completed
self.created = created or datetime.now().strftime("%Y-%m-%d %H:%M")
self.due = due
def mark_done(self):
self.completed = True
def to_dict(self):
return self.__dict__
class TodoApp:
def __init__(self, filename="tasks.json"):
self.filename = filename
self.tasks = self.load_tasks()
def load_tasks(self):
try:
with open(self.filename, "r") as f:
data = json.load(f)
return [Task(**t) for t in data]
except (FileNotFoundError, json.JSONDecodeError):
return []
def save_tasks(self):
with open(self.filename, "w") as f:
json.dump([t.to_dict() for t in self.tasks], f, indent=4)
def add_task(self):
title = input("Task title: ")
if not title.strip():
print("❌ Title cannot be empty.")
return
priority = input("Priority (High/Medium/Low): ") or "Medium"
category = input("Category: ") or "General"
due = input("Due date (YYYY-MM-DD, optional): ") or None
task = Task(title, priority, category, False, None, due)
self.tasks.append(task)
self.save_tasks()
print(f"✅ Task '{title}' added.")
def list_tasks(self):
if not self.tasks:
print("📭 No tasks found.")
return
for i, t in enumerate(self.tasks, 1):
status = "✔️" if t.completed else "❌"
print(f"{i}. {t.title} [{t.priority}] ({t.category}) - {status}")
def mark_complete(self):
self.list_tasks()
try:
index = int(input("Enter task number to mark complete: "))
self.tasks[index-1].mark_done()
self.save_tasks()
print("✅ Task marked complete!")
except (ValueError, IndexError):
print("❌ Invalid selection.")
def delete_task(self):
self.list_tasks()
try:
index = int(input("Enter task number to delete: "))
task = self.tasks.pop(index-1)
self.save_tasks()
print(f"🗑️ Deleted '{task.title}'.")
except (ValueError, IndexError):
print("❌ Invalid number.")
def search_tasks(self):
term = input("Search keyword: ").lower()
results = [t for t in self.tasks if term in t.title.lower() or term in t.category.lower()]
if results:
print(f"🔍 Found {len(results)} result(s):")
for t in results:
print(f"- {t.title} ({t.category}) [{t.priority}]")
else:
print("No matches found.")
def menu(self):
while True:
print("
--- TO-DO MENU ---")
print("1. Add Task")
print("2. List Tasks")
print("3. Mark Complete")
print("4. Delete Task")
print("5. Search")
print("6. Exit")
choice = input("Choose option: ")
if choice == "1": self.add_task()
elif choice == "2": self.list_tasks()
elif choice == "3": self.mark_complete()
elif choice == "4": self.delete_task()
elif choice == "5": self.search_tasks()
elif choice == "6":
print("👋 Goodbye!")
break
else:
print("❌ Invalid choice.")
if __name__ == "__main__":
TodoApp().menu()🚀 How to Run Locally:
- 1. Copy the code above
- 2. Save it as
todo_app.py - 3. Open terminal and run:
python todo_app.py - 4. Follow the interactive menu to add, list, and manage tasks
🎮 Browser Demo (Simplified Version)
This demo version shows the core functionality without user input (browser limitation):
To-Do App Demo
Try the simplified browser-compatible version of the To-Do app
import json
from datetime import datetime
class Task:
def __init__(self, title, priority="Medium", category="General", completed=False):
self.title = title
self.priority = priority
self.category = category
self.completed = completed
self.created = datetime.now().strftime("%Y-%m-%d %H:%M")
def mark_done(self):
self.completed = True
def __str__(self):
status = "✔️" if self.completed else "❌"
return f"{self.title} [{self
...💬 Expected Output (Full Version)
--- TO-DO MENU --- 1. Add Task 2. List Tasks 3. Mark Complete 4. Delete Task 5. Search 6. Exit Choose option: 1 Task title: Study Python Priority: High Category: Study ✅ Task 'Study Python' added.
🚀 Enhancement Ideas
1️⃣ Add Colors (User Interface)
Install colorama to make CLI text colorful:
pip install colorama
Then highlight statuses:
from colorama import Fore, Style print(Fore.GREEN + "✔ Completed" + Style.RESET_ALL) print(Fore.RED + "❌ Pending" + Style.RESET_ALL)
This simple change boosts readability — important for apps used daily.
2️⃣ JSON Storage (Structured & Expandable)
Instead of plain text lines, use JSON to save metadata:
{
"title": "Study Python",
"priority": "High",
"category": "Education",
"completed": false,
"created": "2025-11-09 20:14",
"due": "2025-11-15"
}You can then sort, filter, and search efficiently — similar to a small database.
3️⃣ Due Dates & Notifications
Use datetime and timedelta to detect overdue tasks:
from datetime import datetime
due_date = datetime.strptime(task.due, "%Y-%m-%d")
if due_date < datetime.now():
print("⚠️ Overdue!")Later, integrate plyer.notification for desktop popups or even email reminders with smtplib.
4️⃣ Priority Levels & Sorting
Store priority as integers (1=High, 2=Medium, 3=Low), then sort automatically:
sorted_tasks = sorted(self.tasks, key=lambda t: {"High":1,"Medium":2,"Low":3}[t.priority])5️⃣ Categories & Filters
Let users list tasks by category:
cat = input("Enter category: ")
for t in self.tasks:
if t.category.lower() == cat.lower():
print(t.title)Organizing by category is essential for productivity — exactly how project management tools work internally.
6️⃣ Validation and Error Recovery
Wrap critical operations with try/except and confirmations:
try:
index = int(input("Enter index: "))
if index not in range(1, len(self.tasks)+1):
raise ValueError
except ValueError:
print("❌ Please enter a valid task number.")7️⃣ CLI Menus and Keyboard Shortcuts
Add quick commands like: a → Add task, l → List all, m → Mark complete. This improves speed for experienced users.
8️⃣ Auto Backup
Each time the app saves, also write a backup:
import shutil
shutil.copy("tasks.json", "tasks_backup.json")9️⃣ Analytics / Statistics
Calculate: Completed vs pending tasks, Average task age, Most common category
done = sum(t.completed for t in self.tasks)
print(f"Progress: {done}/{len(self.tasks)} tasks done!")🔟 Simple Encryption
To keep personal tasks safe:
import base64 def encrypt(data): return base64.b64encode(data.encode()).decode() def decrypt(data): return base64.b64decode(data).decode()
Use this before saving JSON — basic, but effective for privacy.
💡 NEXT STEPS — From Intermediate to Advanced
To evolve this into a professional-level tool, start introducing modular architecture and optional GUI.
🧱 1. Modularize Code
Split into separate files:
todo/ ┣ main.py ┣ models.py (Task class) ┣ storage.py (save/load) ┣ utils.py (helpers) ┗ menu.py
This teaches maintainable structure used in all professional codebases.
🧠 2. Add CLI Arguments (sys / argparse)
Let users run commands directly:
python todo.py --add "Buy groceries" python todo.py --list
Use Python's argparse library:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument("--add")
args = parser.parse_args()📦 3. Add SQLite Database
Instead of JSON, use SQLite for instant data retrieval:
import sqlite3
conn = sqlite3.connect("tasks.db")Then map each task into a table with SQL CRUD operations — identical to backend app logic.
💬 4. Add Natural Language Input
Use simple parsing: "Add Buy milk tomorrow at 5pm"
Then interpret "tomorrow" using libraries like dateparser to auto-create due dates.
🪄 5. Add Notifications or Email Reminders
Use:
- •
plyer→ desktop notifications - •
smtplib→ email reminders - •
schedule→ automated checks
This makes your CLI feel like a background productivity daemon.
🌐 6. Connect to Cloud Storage (Advanced)
Save tasks online using Google Drive API, Firebase, or Supabase so you can access them anywhere. This brings your Python CLI closer to a SaaS-level product.
🧑💻 7. Build GUI Version
Turn this into a desktop app:
- • Use
tkinterfor offline version - • Use
customtkinterorPyQt5for modern design - • Eventually connect to backend via Flask/FastAPI
This is how you move from console → app development.
📱 8. Export to Mobile
Convert the same logic into a React Native or Flutter app with shared logic from your Python backend.
🧩 9. Integrate AI Suggestions
Let an AI assistant prioritize or label tasks automatically by reading their text:
"Study Python OOP" → category = Education, priority = Medium
This can use OpenAI or your future FlickAI when ready.
🧾 10. Monetize It
Add:
- • Rewarded ads or donation links
- • Premium features (cloud sync, reminders)
- • Affiliate banners ("Buy productivity planner")
Even a CLI can be monetized through GitHub Sponsors or Gumroad templates.
⚙️ Super Advanced Tips for Expert Coders
- • Use Async I/O: Replace normal file I/O with asyncio + aiofiles for instant responsiveness.
- • Add Unit Tests: Write automated pytest cases for each method (add_task, delete_task, etc.).
- • Implement Logging System: Use logging module for detailed app logs instead of prints.
- • Track Time Spent: Add start/stop timestamps for time management reports.
- • Command History: Store user actions in a log file to undo/redo actions.
- • Versioning: Auto-save task versions every edit like a lightweight Git system.
- • REST API: Convert into Flask REST API (backend) so frontends or other devices can sync tasks.
- • Voice Commands: Integrate speech_recognition to add tasks using microphone.
- • Cross-Platform Installer: Bundle into an .exe or .app using PyInstaller or cx_Freeze.
- • Cloud-Based Dashboard: Deploy to web (Render / Railway / Supabase) and manage via browser.
- • Machine Learning Add-On: Predict overdue likelihood or categorize automatically using simple models (e.g., scikit-learn).
✅ Next Steps
- Start with the base version above and test all functions.
- Add enhancements one by one (don't jump straight to everything).
- Learn to debug using print/logging and handle exceptions gracefully.
- Refactor into modular files for easier maintenance.
- Add small features like reminders and category filters first.
- Document your app with a README, screenshots, and usage examples.
- Push your finished project to GitHub or Replit and include in your portfolio or CV.
Each new feature is a learning milestone — by finishing this project, you'll understand how real developers structure, persist, and maintain applications.