Project 1: Task Manager CLI
Welcome to your first complete Python application! Weβre building a command-line task management system that helps users organize their tasks, set priorities, track due dates, and stay productive.
Project Overview
TaskMaster CLI is a command-line application that allows users to:
- β Create, read, update, and delete tasks
- β Organize tasks by categories and priorities
- β Set due dates and get reminders
- β Search and filter tasks
- β Persist data between sessions
- β Export tasks to different formats
Learning Objectives
By the end of this project, youβll be able to:
- Design and implement object-oriented applications
- Handle file I/O for data persistence
- Create professional command-line interfaces
- Implement CRUD operations
- Work with dates and times
- Write modular, maintainable code
Project Requirements
Core Features
-
Task Management
- Add new tasks with title, description, category, priority, due date
- View all tasks or filter by criteria
- Edit existing tasks
- Mark tasks as complete/incomplete
- Delete tasks
-
Organization
- Categorize tasks (Work, Personal, Shopping, Health, etc.)
- Set priorities (High, Medium, Low)
- Sort tasks by due date, priority, or creation date
-
Data Persistence
- Save tasks to JSON file
- Load tasks on application startup
- Automatic saving after changes
-
Search & Filter
- Search tasks by title or description
- Filter by category, priority, completion status
- Show overdue tasks
Advanced Features
-
Due Date Management
- Set and display due dates
- Show days remaining until due
- Highlight overdue tasks
-
Export Functionality
- Export tasks to CSV format
- Export completed tasks summary
-
Statistics
- Show task completion statistics
- Display productivity metrics
Project Structure
taskmaster/
βββ taskmaster/
β βββ __init__.py
β βββ cli.py # Command-line interface
β βββ task.py # Task model class
β βββ task_manager.py # Business logic
β βββ storage.py # Data persistence
β βββ utils.py # Helper functions
βββ tests/
β βββ test_task.py
β βββ test_task_manager.py
β βββ test_storage.py
βββ data/
β βββ tasks.json # Task storage
βββ docs/
β βββ README.md
βββ requirements.txt
βββ setup.py
βββ main.py # Application entry point
Step 1: Design the Task Model
Letβs start by creating the core Task class that represents individual tasks.
# taskmaster/task.py
from datetime import datetime, date
from typing import Optional
import json
class Task:
"""Represents a single task with all its properties."""
PRIORITIES = ['Low', 'Medium', 'High']
CATEGORIES = ['Work', 'Personal', 'Shopping', 'Health', 'Education', 'Other']
def __init__(self, title: str, description: str = "",
category: str = "Other", priority: str = "Medium",
due_date: Optional[date] = None, task_id: Optional[int] = None):
self.task_id = task_id or self._generate_id()
self.title = title
self.description = description
self.category = category
self.priority = priority
self.due_date = due_date
self.completed = False
self.created_at = datetime.now()
self.updated_at = datetime.now()
# Validate inputs
self._validate_category()
self._validate_priority()
def _generate_id(self) -> int:
"""Generate a unique task ID."""
return int(datetime.now().timestamp() * 1000000) % 1000000
def _validate_category(self):
"""Ensure category is valid."""
if self.category not in self.CATEGORIES:
raise ValueError(f"Category must be one of: {', '.join(self.CATEGORIES)}")
def _validate_priority(self):
"""Ensure priority is valid."""
if self.priority not in self.PRIORITIES:
raise ValueError(f"Priority must be one of: {', '.join(self.PRIORITIES)}")
def mark_complete(self):
"""Mark the task as completed."""
self.completed = True
self.updated_at = datetime.now()
def mark_incomplete(self):
"""Mark the task as incomplete."""
self.completed = False
self.updated_at = datetime.now()
def update(self, **kwargs):
"""Update task properties."""
allowed_fields = ['title', 'description', 'category', 'priority', 'due_date']
for field, value in kwargs.items():
if field in allowed_fields:
if field == 'category':
self._validate_category_value(value)
elif field == 'priority':
self._validate_priority_value(value)
setattr(self, field, value)
self.updated_at = datetime.now()
def _validate_category_value(self, category: str):
"""Validate category value."""
if category not in self.CATEGORIES:
raise ValueError(f"Category must be one of: {', '.join(self.CATEGORIES)}")
def _validate_priority_value(self, priority: str):
"""Validate priority value."""
if priority not in self.PRIORITIES:
raise ValueError(f"Priority must be one of: {', '.join(self.PRIORITIES)}")
def is_overdue(self) -> bool:
"""Check if task is overdue."""
if self.due_date and not self.completed:
return self.due_date < date.today()
return False
def days_until_due(self) -> Optional[int]:
"""Calculate days until due date."""
if self.due_date and not self.completed:
return (self.due_date - date.today()).days
return None
def to_dict(self) -> dict:
"""Convert task to dictionary for JSON serialization."""
return {
'task_id': self.task_id,
'title': self.title,
'description': self.description,
'category': self.category,
'priority': self.priority,
'due_date': self.due_date.isoformat() if self.due_date else None,
'completed': self.completed,
'created_at': self.created_at.isoformat(),
'updated_at': self.updated_at.isoformat()
}
@classmethod
def from_dict(cls, data: dict) -> 'Task':
"""Create task from dictionary."""
# Parse dates
due_date = None
if data.get('due_date'):
due_date = date.fromisoformat(data['due_date'])
created_at = datetime.fromisoformat(data['created_at'])
updated_at = datetime.fromisoformat(data['updated_at'])
# Create task
task = cls(
title=data['title'],
description=data.get('description', ''),
category=data.get('category', 'Other'),
priority=data.get('priority', 'Medium'),
due_date=due_date,
task_id=data['task_id']
)
# Set additional properties
task.completed = data.get('completed', False)
task.created_at = created_at
task.updated_at = updated_at
return task
def __str__(self) -> str:
"""String representation of the task."""
status = "β" if self.completed else "β"
due_info = ""
if self.due_date:
days = self.days_until_due()
if days is not None:
if days < 0:
due_info = f" (OVERDUE: {abs(days)} days)"
elif days == 0:
due_info = " (DUE TODAY)"
else:
due_info = f" ({days} days left)"
return f"{status} [{self.priority}] {self.title} - {self.category}{due_info}"
def __repr__(self) -> str:
return f"Task(id={self.task_id}, title='{self.title}', completed={self.completed})"
Step 2: Implement Data Storage
Create the storage layer for persisting tasks to JSON files.
# taskmaster/storage.py
import json
import os
from typing import List, Dict, Any
from .task import Task
class TaskStorage:
"""Handles data persistence for tasks."""
def __init__(self, data_dir: str = "data", filename: str = "tasks.json"):
self.data_dir = data_dir
self.filename = os.path.join(data_dir, filename)
self._ensure_data_dir()
def _ensure_data_dir(self):
"""Ensure data directory exists."""
if not os.path.exists(self.data_dir):
os.makedirs(self.data_dir)
def save_tasks(self, tasks: List[Task]) -> bool:
"""Save tasks to JSON file."""
try:
task_data = [task.to_dict() for task in tasks]
with open(self.filename, 'w', encoding='utf-8') as f:
json.dump(task_data, f, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"Error saving tasks: {e}")
return False
def load_tasks(self) -> List[Task]:
"""Load tasks from JSON file."""
if not os.path.exists(self.filename):
return []
try:
with open(self.filename, 'r', encoding='utf-8') as f:
task_data = json.load(f)
tasks = []
for data in task_data:
try:
task = Task.from_dict(data)
tasks.append(task)
except Exception as e:
print(f"Error loading task {data.get('task_id', 'unknown')}: {e}")
return tasks
except Exception as e:
print(f"Error loading tasks file: {e}")
return []
def export_to_csv(self, tasks: List[Task], filename: str) -> bool:
"""Export tasks to CSV format."""
try:
import csv
csv_filename = os.path.join(self.data_dir, filename)
with open(csv_filename, 'w', newline='', encoding='utf-8') as f:
writer = csv.writer(f)
# Write header
writer.writerow(['ID', 'Title', 'Description', 'Category', 'Priority',
'Due Date', 'Completed', 'Created At', 'Updated At'])
# Write tasks
for task in tasks:
writer.writerow([
task.task_id,
task.title,
task.description,
task.category,
task.priority,
task.due_date.isoformat() if task.due_date else '',
'Yes' if task.completed else 'No',
task.created_at.strftime('%Y-%m-%d %H:%M:%S'),
task.updated_at.strftime('%Y-%m-%d %H:%M:%S')
])
print(f"Tasks exported to {csv_filename}")
return True
except Exception as e:
print(f"Error exporting to CSV: {e}")
return False
def get_statistics(self, tasks: List[Task]) -> Dict[str, Any]:
"""Get statistics about tasks."""
if not tasks:
return {'total_tasks': 0}
total_tasks = len(tasks)
completed_tasks = sum(1 for task in tasks if task.completed)
overdue_tasks = sum(1 for task in tasks if task.is_overdue())
# Category breakdown
categories = {}
for task in tasks:
categories[task.category] = categories.get(task.category, 0) + 1
# Priority breakdown
priorities = {}
for task in tasks:
priorities[task.priority] = priorities.get(task.priority, 0) + 1
return {
'total_tasks': total_tasks,
'completed_tasks': completed_tasks,
'pending_tasks': total_tasks - completed_tasks,
'overdue_tasks': overdue_tasks,
'completion_rate': completed_tasks / total_tasks * 100 if total_tasks > 0 else 0,
'categories': categories,
'priorities': priorities
}
Step 3: Create Task Manager
Implement the business logic layer that manages tasks.
# taskmaster/task_manager.py
from typing import List, Optional, Dict, Any
from datetime import date
from .task import Task
from .storage import TaskStorage
class TaskManager:
"""Manages task operations and business logic."""
def __init__(self, storage: TaskStorage):
self.storage = storage
self.tasks = self.storage.load_tasks()
def add_task(self, title: str, description: str = "", category: str = "Other",
priority: str = "Medium", due_date: Optional[date] = None) -> Task:
"""Add a new task."""
task = Task(title, description, category, priority, due_date)
self.tasks.append(task)
self._save()
return task
def get_task(self, task_id: int) -> Optional[Task]:
"""Get a task by ID."""
for task in self.tasks:
if task.task_id == task_id:
return task
return None
def update_task(self, task_id: int, **kwargs) -> bool:
"""Update a task."""
task = self.get_task(task_id)
if task:
task.update(**kwargs)
self._save()
return True
return False
def delete_task(self, task_id: int) -> bool:
"""Delete a task."""
task = self.get_task(task_id)
if task:
self.tasks.remove(task)
self._save()
return True
return False
def mark_complete(self, task_id: int) -> bool:
"""Mark a task as complete."""
task = self.get_task(task_id)
if task:
task.mark_complete()
self._save()
return True
return False
def mark_incomplete(self, task_id: int) -> bool:
"""Mark a task as incomplete."""
task = self.get_task(task_id)
if task:
task.mark_incomplete()
self._save()
return True
return False
def get_all_tasks(self, sort_by: str = "created_at", reverse: bool = False) -> List[Task]:
"""Get all tasks, optionally sorted."""
tasks = self.tasks.copy()
if sort_by == "due_date":
tasks.sort(key=lambda t: (t.due_date is None, t.due_date), reverse=reverse)
elif sort_by == "priority":
priority_order = {'High': 3, 'Medium': 2, 'Low': 1}
tasks.sort(key=lambda t: priority_order.get(t.priority, 0), reverse=reverse)
elif sort_by == "title":
tasks.sort(key=lambda t: t.title.lower(), reverse=reverse)
else: # created_at
tasks.sort(key=lambda t: t.created_at, reverse=reverse)
return tasks
def search_tasks(self, query: str) -> List[Task]:
"""Search tasks by title or description."""
query = query.lower()
return [task for task in self.tasks
if query in task.title.lower() or query in task.description.lower()]
def filter_tasks(self, category: Optional[str] = None,
priority: Optional[str] = None,
completed: Optional[bool] = None,
overdue: bool = False) -> List[Task]:
"""Filter tasks by various criteria."""
filtered = self.tasks.copy()
if category:
filtered = [t for t in filtered if t.category == category]
if priority:
filtered = [t for t in filtered if t.priority == priority]
if completed is not None:
filtered = [t for t in filtered if t.completed == completed]
if overdue:
filtered = [t for t in filtered if t.is_overdue()]
return filtered
def get_overdue_tasks(self) -> List[Task]:
"""Get all overdue tasks."""
return [task for task in self.tasks if task.is_overdue()]
def get_upcoming_tasks(self, days: int = 7) -> List[Task]:
"""Get tasks due within specified days."""
today = date.today()
return [task for task in self.tasks
if task.due_date and not task.completed
and (task.due_date - today).days <= days
and (task.due_date - today).days >= 0]
def export_tasks(self, filename: str, tasks: Optional[List[Task]] = None) -> bool:
"""Export tasks to CSV."""
if tasks is None:
tasks = self.tasks
return self.storage.export_to_csv(tasks, filename)
def get_statistics(self) -> Dict[str, Any]:
"""Get task statistics."""
return self.storage.get_statistics(self.tasks)
def _save(self):
"""Save tasks to storage."""
self.storage.save_tasks(self.tasks)
def __len__(self) -> int:
"""Return number of tasks."""
return len(self.tasks)
Step 4: Build Command-Line Interface
Create the CLI that users will interact with.
# taskmaster/cli.py
import argparse
import sys
from datetime import date, datetime
from typing import List
from .task import Task
from .task_manager import TaskManager
from .storage import TaskStorage
class TaskCLI:
"""Command-line interface for TaskMaster."""
def __init__(self):
self.storage = TaskStorage()
self.manager = TaskManager(self.storage)
def run(self):
"""Main CLI entry point."""
parser = self._create_parser()
args = parser.parse_args()
if not hasattr(args, 'command') or args.command is None:
parser.print_help()
return
# Execute the appropriate command
command_method = getattr(self, f"do_{args.command}", None)
if command_method:
try:
command_method(args)
except Exception as e:
print(f"Error: {e}")
sys.exit(1)
else:
print(f"Unknown command: {args.command}")
sys.exit(1)
def _create_parser(self) -> argparse.ArgumentParser:
"""Create the argument parser."""
parser = argparse.ArgumentParser(
prog='taskmaster',
description='Task Manager CLI - Organize your tasks efficiently'
)
subparsers = parser.add_subparsers(dest='command', help='Available commands')
# Add task command
add_parser = subparsers.add_parser('add', help='Add a new task')
add_parser.add_argument('title', help='Task title')
add_parser.add_argument('-d', '--description', default='', help='Task description')
add_parser.add_argument('-c', '--category', default='Other',
choices=Task.CATEGORIES, help='Task category')
add_parser.add_argument('-p', '--priority', default='Medium',
choices=Task.PRIORITIES, help='Task priority')
add_parser.add_argument('--due-date', type=self._parse_date,
help='Due date (YYYY-MM-DD)')
# List tasks command
list_parser = subparsers.add_parser('list', help='List tasks')
list_parser.add_argument('--category', choices=Task.CATEGORIES, help='Filter by category')
list_parser.add_argument('--priority', choices=Task.PRIORITIES, help='Filter by priority')
list_parser.add_argument('--completed', action='store_true', help='Show only completed tasks')
list_parser.add_argument('--pending', action='store_true', help='Show only pending tasks')
list_parser.add_argument('--overdue', action='store_true', help='Show only overdue tasks')
list_parser.add_argument('--sort', choices=['created_at', 'due_date', 'priority', 'title'],
default='created_at', help='Sort tasks by')
list_parser.add_argument('--reverse', action='store_true', help='Reverse sort order')
# Show task command
show_parser = subparsers.add_parser('show', help='Show task details')
show_parser.add_argument('task_id', type=int, help='Task ID')
# Update task command
update_parser = subparsers.add_parser('update', help='Update a task')
update_parser.add_argument('task_id', type=int, help='Task ID')
update_parser.add_argument('-t', '--title', help='New title')
update_parser.add_argument('-d', '--description', help='New description')
update_parser.add_argument('-c', '--category', choices=Task.CATEGORIES, help='New category')
update_parser.add_argument('-p', '--priority', choices=Task.PRIORITIES, help='New priority')
update_parser.add_argument('--due-date', type=self._parse_date, help='New due date')
# Complete/Incomplete commands
complete_parser = subparsers.add_parser('complete', help='Mark task as complete')
complete_parser.add_argument('task_id', type=int, help='Task ID')
incomplete_parser = subparsers.add_parser('incomplete', help='Mark task as incomplete')
incomplete_parser.add_argument('task_id', type=int, help='Task ID')
# Delete command
delete_parser = subparsers.add_parser('delete', help='Delete a task')
delete_parser.add_argument('task_id', type=int, help='Task ID')
delete_parser.add_argument('--force', action='store_true', help='Skip confirmation')
# Search command
search_parser = subparsers.add_parser('search', help='Search tasks')
search_parser.add_argument('query', help='Search query')
# Statistics command
subparsers.add_parser('stats', help='Show task statistics')
# Export command
export_parser = subparsers.add_parser('export', help='Export tasks to CSV')
export_parser.add_argument('filename', help='Output filename')
export_parser.add_argument('--completed-only', action='store_true',
help='Export only completed tasks')
return parser
def _parse_date(self, date_str: str) -> date:
"""Parse date string into date object."""
try:
return date.fromisoformat(date_str)
except ValueError:
raise argparse.ArgumentTypeError(f"Invalid date format: {date_str}. Use YYYY-MM-DD")
def do_add(self, args):
"""Add a new task."""
task = self.manager.add_task(
title=args.title,
description=args.description,
category=args.category,
priority=args.priority,
due_date=args.due_date
)
print(f"β Task added successfully!")
print(f" ID: {task.task_id}")
print(f" Title: {task.title}")
if task.due_date:
print(f" Due: {task.due_date}")
def do_list(self, args):
"""List tasks with optional filtering."""
# Determine filter criteria
completed = None
if args.completed:
completed = True
elif args.pending:
completed = False
# Get filtered tasks
tasks = self.manager.filter_tasks(
category=args.category,
priority=args.priority,
completed=completed,
overdue=args.overdue
)
# Sort tasks
if args.sort == "due_date":
tasks.sort(key=lambda t: (t.due_date is None, t.due_date), reverse=args.reverse)
elif args.sort == "priority":
priority_order = {'High': 3, 'Medium': 2, 'Low': 1}
tasks.sort(key=lambda t: priority_order.get(t.priority, 0), reverse=args.reverse)
elif args.sort == "title":
tasks.sort(key=lambda t: t.title.lower(), reverse=args.reverse)
else: # created_at
tasks.sort(key=lambda t: t.created_at, reverse=args.reverse)
if not tasks:
print("No tasks found.")
return
print(f"π Tasks ({len(tasks)} found):")
print("-" * 80)
for task in tasks:
print(f"{task.task_id:6d} | {str(task)}")
print("-" * 80)
def do_show(self, args):
"""Show detailed task information."""
task = self.manager.get_task(args.task_id)
if not task:
print(f"Task {args.task_id} not found.")
return
print(f"π Task Details (ID: {task.task_id})")
print("=" * 40)
print(f"Title: {task.title}")
print(f"Description: {task.description or '(no description)'}")
print(f"Category: {task.category}")
print(f"Priority: {task.priority}")
print(f"Status: {'Completed' if task.completed else 'Pending'}")
print(f"Due Date: {task.due_date or 'Not set'}")
if task.due_date and not task.completed:
days = task.days_until_due()
if days == 0:
print("Due Status: DUE TODAY")
elif days > 0:
print(f"Due Status: {days} days remaining")
else:
print(f"Due Status: OVERDUE by {abs(days)} days")
print(f"Created: {task.created_at.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"Updated: {task.updated_at.strftime('%Y-%m-%d %H:%M:%S')}")
def do_update(self, args):
"""Update a task."""
# Check if any updates were provided
updates = {}
if args.title:
updates['title'] = args.title
if args.description:
updates['description'] = args.description
if args.category:
updates['category'] = args.category
if args.priority:
updates['priority'] = args.priority
if args.due_date:
updates['due_date'] = args.due_date
if not updates:
print("No updates specified. Use --help to see available options.")
return
if self.manager.update_task(args.task_id, **updates):
print(f"β Task {args.task_id} updated successfully!")
else:
print(f"Task {args.task_id} not found.")
def do_complete(self, args):
"""Mark task as complete."""
if self.manager.mark_complete(args.task_id):
print(f"β Task {args.task_id} marked as complete!")
else:
print(f"Task {args.task_id} not found.")
def do_incomplete(self, args):
"""Mark task as incomplete."""
if self.manager.mark_incomplete(args.task_id):
print(f"β Task {args.task_id} marked as incomplete!")
else:
print(f"Task {args.task_id} not found.")
def do_delete(self, args):
"""Delete a task."""
task = self.manager.get_task(args.task_id)
if not task:
print(f"Task {args.task_id} not found.")
return
# Show confirmation unless --force is used
if not args.force:
print(f"Are you sure you want to delete task '{task.title}'? (y/N): ", end='')
response = input().strip().lower()
if response not in ['y', 'yes']:
print("Deletion cancelled.")
return
if self.manager.delete_task(args.task_id):
print(f"β Task {args.task_id} deleted successfully!")
else:
print(f"Failed to delete task {args.task_id}.")
def do_search(self, args):
"""Search tasks."""
tasks = self.manager.search_tasks(args.query)
if not tasks:
print(f"No tasks found matching '{args.query}'.")
return
print(f"π Search Results for '{args.query}' ({len(tasks)} found):")
print("-" * 80)
for task in tasks:
print(f"{task.task_id:6d} | {str(task)}")
print("-" * 80)
def do_stats(self, args):
"""Show task statistics."""
stats = self.manager.get_statistics()
print("π Task Statistics")
print("=" * 30)
print(f"Total Tasks: {stats['total_tasks']}")
print(f"Completed: {stats['completed_tasks']}")
print(f"Pending: {stats['pending_tasks']}")
print(f"Overdue: {stats['overdue_tasks']}")
print(".1f")
if stats['categories']:
print("\nπ Tasks by Category:")
for category, count in stats['categories'].items():
print(f" {category:12s}: {count}")
if stats['priorities']:
print("\nπ¨ Tasks by Priority:")
for priority, count in stats['priorities'].items():
print(f" {priority:8s}: {count}")
def do_export(self, args):
"""Export tasks to CSV."""
tasks = self.manager.tasks
if args.completed_only:
tasks = [t for t in tasks if t.completed]
if self.manager.export_tasks(args.filename, tasks):
print(f"β Tasks exported to {args.filename}")
else:
print("Failed to export tasks.")
def main():
"""Main entry point."""
cli = TaskCLI()
cli.run()
if __name__ == "__main__":
main()
Step 5: Create Main Entry Point
# main.py
#!/usr/bin/env python3
"""
TaskMaster CLI - A command-line task management application.
Usage:
python main.py <command> [options]
Commands:
add Add a new task
list List tasks
show Show task details
update Update a task
complete Mark task as complete
delete Delete a task
search Search tasks
stats Show statistics
export Export tasks to CSV
Run 'python main.py <command> --help' for command-specific options.
"""
from taskmaster.cli import main
if __name__ == "__main__":
main()
Step 6: Add Package Configuration
# setup.py
from setuptools import setup, find_packages
with open("README.md", "r", encoding="utf-8") as fh:
long_description = fh.read()
setup(
name="taskmaster-cli",
version="1.0.0",
author="Your Name",
author_email="your.email@example.com",
description="A command-line task management application",
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/yourusername/taskmaster-cli",
packages=find_packages(),
classifiers=[
"Development Status :: 4 - Beta",
"Intended Audience :: End Users/Desktop",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
python_requires=">=3.8",
entry_points={
"console_scripts": [
"taskmaster=main:main",
],
},
include_package_data=True,
zip_safe=False,
)
# requirements.txt
# TaskMaster CLI Requirements
# taskmaster/__init__.py
"""
TaskMaster CLI - A command-line task management application.
"""
__version__ = "1.0.0"
__author__ = "Your Name"
__email__ = "your.email@example.com"
from .task import Task
from .task_manager import TaskManager
from .storage import TaskStorage
from .cli import TaskCLI
__all__ = ["Task", "TaskManager", "TaskStorage", "TaskCLI"]
Step 7: Write Tests
# tests/test_task.py
import unittest
from datetime import date
from taskmaster.task import Task
class TestTask(unittest.TestCase):
"""Test cases for Task class."""
def test_task_creation(self):
"""Test basic task creation."""
task = Task("Test Task", "Test description", "Work", "High")
self.assertEqual(task.title, "Test Task")
self.assertEqual(task.description, "Test description")
self.assertEqual(task.category, "Work")
self.assertEqual(task.priority, "High")
self.assertFalse(task.completed)
def test_task_with_due_date(self):
"""Test task with due date."""
due_date = date(2024, 12, 31)
task = Task("Future Task", due_date=due_date)
self.assertEqual(task.due_date, due_date)
def test_task_validation(self):
"""Test task validation."""
with self.assertRaises(ValueError):
Task("Invalid", category="InvalidCategory")
with self.assertRaises(ValueError):
Task("Invalid", priority="InvalidPriority")
def test_task_completion(self):
"""Test task completion methods."""
task = Task("Test Task")
self.assertFalse(task.completed)
task.mark_complete()
self.assertTrue(task.completed)
task.mark_incomplete()
self.assertFalse(task.completed)
def test_task_update(self):
"""Test task update functionality."""
task = Task("Original Title")
task.update(title="Updated Title", priority="High")
self.assertEqual(task.title, "Updated Title")
self.assertEqual(task.priority, "High")
def test_overdue_detection(self):
"""Test overdue task detection."""
past_date = date(2020, 1, 1)
task = Task("Overdue Task", due_date=past_date)
self.assertTrue(task.is_overdue())
future_date = date(2030, 1, 1)
task2 = Task("Future Task", due_date=future_date)
self.assertFalse(task2.is_overdue())
def test_serialization(self):
"""Test task serialization."""
task = Task("Test Task", "Description", "Work", "High", date(2024, 12, 31))
task_dict = task.to_dict()
# Test deserialization
restored_task = Task.from_dict(task_dict)
self.assertEqual(restored_task.title, task.title)
self.assertEqual(restored_task.due_date, task.due_date)
if __name__ == '__main__':
unittest.main()
Step 8: Create Documentation
# TaskMaster CLI
A powerful command-line task management application built with Python.
## Features
- β
Create, read, update, and delete tasks
- π Organize tasks by categories and priorities
- π
Set due dates with overdue detection
- π Search and filter tasks
- πΎ Persistent JSON storage
- π Export tasks to CSV
- π Task completion statistics
## Installation
### From Source
```bash
git clone https://github.com/yourusername/taskmaster-cli.git
cd taskmaster-cli
pip install -r requirements.txt
python setup.py install
As Package
pip install taskmaster-cli
Usage
Add a Task
# Basic task
taskmaster add "Buy groceries"
# Task with details
taskmaster add "Finish project report" -d "Complete the quarterly report" -c Work -p High --due-date 2024-12-31
List Tasks
# All tasks
taskmaster list
# Filter by category
taskmaster list --category Work
# Show only pending tasks
taskmaster list --pending
# Sort by due date
taskmaster list --sort due_date
Manage Tasks
# Show task details
taskmaster show 123456
# Update task
taskmaster update 123456 -t "New title" -p High
# Mark complete/incomplete
taskmaster complete 123456
taskmaster incomplete 123456
# Delete task
taskmaster delete 123456
Search and Export
# Search tasks
taskmaster search "project"
# Show statistics
taskmaster stats
# Export to CSV
taskmaster export tasks.csv
taskmaster export completed_tasks.csv --completed-only
Task Categories
- Work - Professional tasks
- Personal - Personal errands and activities
- Shopping - Shopping and purchases
- Health - Health and fitness related
- Education - Learning and courses
- Other - Miscellaneous tasks
Task Priorities
- High - Urgent and important
- Medium - Important but not urgent
- Low - Nice to have
Data Storage
Tasks are stored in JSON format in the data/tasks.json file. The application automatically creates this file and directory structure on first run.
Development
Running Tests
python -m pytest tests/
Code Quality
# Format code
black taskmaster/
# Lint code
flake8 taskmaster/
# Type checking
mypy taskmaster/
Contributing
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
License
MIT License - see LICENSE file for details.
Support
For issues and questions:
- GitHub Issues: https://github.com/yourusername/taskmaster-cli/issues
- Documentation: https://taskmaster-cli.readthedocs.io/
## Step 9: Test the Application
Let's create a simple test script to verify everything works:
```python
# test_app.py
#!/usr/bin/env python3
"""
Test script for TaskMaster CLI application.
"""
import os
import sys
import subprocess
from datetime import date, timedelta
def run_command(cmd):
"""Run a command and return the result."""
try:
result = subprocess.run(cmd, shell=True, capture_output=True, text=True)
return result.returncode, result.stdout, result.stderr
except Exception as e:
return 1, "", str(e)
def test_basic_functionality():
"""Test basic application functionality."""
print("π§ͺ Testing TaskMaster CLI...")
# Clean up any existing data
if os.path.exists("data/tasks.json"):
os.remove("data/tasks.json")
# Test adding a task
print("\n1. Testing task addition...")
returncode, stdout, stderr = run_command('python main.py add "Test Task" -d "Test description" -c Work -p High')
if returncode != 0:
print(f"β Failed to add task: {stderr}")
return False
print("β
Task added successfully")
# Test listing tasks
print("\n2. Testing task listing...")
returncode, stdout, stderr = run_command('python main.py list')
if returncode != 0 or "Test Task" not in stdout:
print(f"β Failed to list tasks: {stderr}")
return False
print("β
Task listing works")
# Test task completion
print("\n3. Testing task completion...")
# First get task ID from list output
lines = stdout.split('\n')
task_line = [line for line in lines if "Test Task" in line][0]
task_id = task_line.split('|')[0].strip()
returncode, stdout, stderr = run_command(f'python main.py complete {task_id}')
if returncode != 0:
print(f"β Failed to complete task: {stderr}")
return False
print("β
Task completion works")
# Test statistics
print("\n4. Testing statistics...")
returncode, stdout, stderr = run_command('python main.py stats')
if returncode != 0 or "Total Tasks:" not in stdout:
print(f"β Failed to show statistics: {stderr}")
return False
print("β
Statistics work")
# Test export
print("\n5. Testing export...")
returncode, stdout, stderr = run_command('python main.py export test_tasks.csv')
if returncode != 0 or not os.path.exists("data/test_tasks.csv"):
print(f"β Failed to export tasks: {stderr}")
return False
print("β
Export works")
# Clean up
if os.path.exists("data/test_tasks.csv"):
os.remove("data/test_tasks.csv")
print("\nπ All tests passed!")
return True
if __name__ == "__main__":
success = test_basic_functionality()
sys.exit(0 if success else 1)
Usage Examples
Daily Workflow
# Start your day by checking tasks
taskmaster list --pending --sort due_date
# Add new tasks as they come up
taskmaster add "Call dentist" -c Health -p High --due-date 2024-12-15
taskmaster add "Review code changes" -c Work -p Medium
# Complete tasks throughout the day
taskmaster complete 123456
# Check overdue tasks
taskmaster list --overdue
# End of day review
taskmaster stats
Project Management
# Add project tasks
taskmaster add "Design database schema" -c Work -p High --due-date 2024-12-20
taskmaster add "Implement user authentication" -c Work -p High --due-date 2024-12-25
taskmaster add "Write unit tests" -c Work -p Medium --due-date 2024-12-28
# Track progress
taskmaster list --category Work --pending
taskmaster complete 123456 # Mark design as complete
# Export for reporting
taskmaster export project_tasks.csv --completed-only
Personal Organization
# Weekly planning
taskmaster add "Grocery shopping" -c Shopping -p Medium --due-date 2024-12-16
taskmaster add "Gym workout" -c Health -p Medium --due-date 2024-12-14
taskmaster add "Read technical book" -c Education -p Low
# Daily review
taskmaster list --due-date --reverse # Show tasks due soon first
# Weekend cleanup
taskmaster search "book" # Find reading tasks
taskmaster stats # Check productivity
Advanced Features to Consider
Once you have the basic application working, consider adding:
- Recurring Tasks - Tasks that repeat daily/weekly/monthly
- Task Templates - Pre-defined task structures
- Time Tracking - Track time spent on tasks
- Notifications - Email/SMS reminders for due tasks
- Collaboration - Share tasks with team members
- Mobile App - Companion mobile application
- Web Interface - Browser-based task management
- Data Visualization - Charts and graphs for productivity
Summary
TaskMaster CLI demonstrates professional Python development:
Core Concepts:
- Object-oriented design with classes and inheritance
- Data persistence with JSON serialization
- Command-line interface with argparse
- Error handling and validation
- Modular code organization
Key Skills:
- Problem decomposition and planning
- Clean code principles
- Testing and debugging
- Documentation and user experience
- Version control and project structure
Next Steps:
- Implement the basic functionality
- Add comprehensive tests
- Create user documentation
- Package for distribution
- Deploy and share with others
Congratulations! Youβve built your first complete Python application! π
Ready for the next project? Letβs build a Weather Dashboard! π€οΈ