Python Course #10: Final Project - Task Manager
Final Project: Task Manager in the Terminal - Part 10 of 10

We made it to the last article of the Python from Scratch Course! This is article 10 of 10, and it is time to put into practice everything you have learned throughout the course.
We are going to build a task manager (to-do list) that runs in the terminal. This project uses variables, functions, lists, dictionaries, object-oriented programming (OOP), file handling, and error handling — basically everything we have covered.
By the end you will have a complete, working program that you can use in your daily life and that proves you already know how to program in Python.
Project Description
Our task manager will have the following features:
- Add tasks with title and description
- List all tasks showing their status (pending or completed)
- Mark tasks as completed
- Delete tasks
- Search tasks by keyword
- Save tasks to a JSON file so they are not lost when the program closes
- Interactive menu with numbered options
The project will have 3 files:
| File | What it contains |
|---|---|
models.py |
The Task class (data model) |
task_manager.py |
The TaskManager class (business logic) |
main.py |
The interactive menu (user interface) |
Step 1: Project Structure
First, create a folder for the project and inside it the three files:
1# Folder structure
2task_manager_app/
3 models.py
4 task_manager.py
5 main.py
You can create the folder and files from the terminal:
1# In the terminal (these are shell commands, not Python code)
2# Windows:
3mkdir task_manager_app
4cd task_manager_app
5type nul > models.py
6type nul > task_manager.py
7type nul > main.py
8
9# macOS / Linux:
10mkdir task_manager_app
11cd task_manager_app
12touch models.py task_manager.py main.py
Step 2: Data Model — The Task Class
Let us start with the data model. The Task class represents an individual task with its properties.
Open the models.py file and write:
1from datetime import datetime
2
3
4class Task:
5 """Represents an individual task in the manager."""
6
7 def __init__(self, task_id, title, description=""):
8 self.task_id = task_id
9 self.title = title
10 self.description = description
11 self.completed = False
12 self.created_at = datetime.now().strftime("%Y-%m-%d %H:%M")
13
14 def mark_completed(self):
15 """Marks the task as completed."""
16 self.completed = True
17
18 def to_dict(self):
19 """Converts the task to a dictionary (to save as JSON)."""
20 return {
21 "task_id": self.task_id,
22 "title": self.title,
23 "description": self.description,
24 "completed": self.completed,
25 "created_at": self.created_at
26 }
27
28 @classmethod
29 def from_dict(cls, data):
30 """Creates a task from a dictionary (to load from JSON)."""
31 task = cls(data["task_id"], data["title"], data.get("description", ""))
32 task.completed = data.get("completed", False)
33 task.created_at = data.get("created_at", "")
34 return task
35
36 def __str__(self):
37 """Text representation of the task."""
38 status = "✓" if self.completed else "○"
39 text = f"[{status}] #{self.task_id} - {self.title}"
40 if self.description:
41 text += f"
42 {self.description}"
43 text += f"
44 Created: {self.created_at}"
45 return text
Let us look at what each part does:
__init__: Initializes the task with an ID, title, description (optional), status (pending by default), and creation datemark_completed: Changes the status to completedto_dict: Converts the task to a dictionary so it can be saved to a JSON filefrom_dict: Class method that creates a task from a dictionary (to load data from JSON)__str__: Defines how the task is displayed when we print it withprint()
Step 3: Business Logic — The TaskManager Class
Now let us create the class that handles all operations: add, list, complete, delete, and search tasks. It also takes care of saving and loading tasks from a JSON file.
Open the task_manager.py file and write:
1import json
2import os
3from models import Task
4
5
6class TaskManager:
7 """Manages the task list with JSON file persistence."""
8
9 def __init__(self, filename="tasks.json"):
10 self.filename = filename
11 self.tasks = []
12 self.next_id = 1
13 self.load_tasks()
14
15 def load_tasks(self):
16 """Loads tasks from the JSON file."""
17 if not os.path.exists(self.filename):
18 return
19
20 try:
21 with open(self.filename, "r", encoding="utf-8") as file:
22 data = json.load(file)
23 self.tasks = [Task.from_dict(item) for item in data]
24 if self.tasks:
25 self.next_id = max(t.task_id for t in self.tasks) + 1
26 print(f"Loaded {len(self.tasks)} tasks from {self.filename}")
27 except (json.JSONDecodeError, KeyError) as e:
28 print(f"Error reading task file: {e}")
29 print("Starting with an empty list.")
30 self.tasks = []
31
32 def save_tasks(self):
33 """Saves tasks to the JSON file."""
34 try:
35 data = [task.to_dict() for task in self.tasks]
36 with open(self.filename, "w", encoding="utf-8") as file:
37 json.dump(data, file, indent=2, ensure_ascii=False)
38 except IOError as e:
39 print(f"Error saving tasks: {e}")
40
41 def add_task(self, title, description=""):
42 """Adds a new task to the list."""
43 if not title or not title.strip():
44 raise ValueError("Task title cannot be empty")
45
46 task = Task(self.next_id, title.strip(), description.strip())
47 self.tasks.append(task)
48 self.next_id += 1
49 self.save_tasks()
50 return task
51
52 def list_tasks(self, show_all=True):
53 """Returns tasks (all or only pending)."""
54 if show_all:
55 return self.tasks
56 return [t for t in self.tasks if not t.completed]
57
58 def complete_task(self, task_id):
59 """Marks a task as completed."""
60 task = self._find_task(task_id)
61 if task is None:
62 raise ValueError(f"No task found with ID #{task_id}")
63 if task.completed:
64 raise ValueError(f"Task #{task_id} is already completed")
65
66 task.mark_completed()
67 self.save_tasks()
68 return task
69
70 def delete_task(self, task_id):
71 """Deletes a task from the list."""
72 task = self._find_task(task_id)
73 if task is None:
74 raise ValueError(f"No task found with ID #{task_id}")
75
76 self.tasks.remove(task)
77 self.save_tasks()
78 return task
79
80 def search_tasks(self, keyword):
81 """Searches tasks containing the keyword in title or description."""
82 if not keyword or not keyword.strip():
83 raise ValueError("Search keyword cannot be empty")
84
85 keyword = keyword.lower().strip()
86 results = [
87 t for t in self.tasks
88 if keyword in t.title.lower() or keyword in t.description.lower()
89 ]
90 return results
91
92 def get_stats(self):
93 """Returns task statistics."""
94 total = len(self.tasks)
95 completed = sum(1 for t in self.tasks if t.completed)
96 pending = total - completed
97 return {
98 "total": total,
99 "completed": completed,
100 "pending": pending
101 }
102
103 def _find_task(self, task_id):
104 """Finds a task by its ID (private method)."""
105 for task in self.tasks:
106 if task.task_id == task_id:
107 return task
108 return None
This class contains all the task manager logic:
load_tasksandsave_tasks: Handle JSON persistence (Part 8 of the course)add_task: Validates and adds a new tasklist_tasks: Can show all or only pending tasks (list comprehensions, Part 4)complete_taskanddelete_task: Find the task and raise errors if it does not exist (Part 9)search_tasks: Searches by keyword in title and descriptionget_stats: Returns statistics using dictionaries (Part 5)_find_task: Private helper method to search by ID
_find_task starts with an underscore (_). In Python, this indicates it is a "private" method — it should only be used inside the class, not from outside.
Step 4: Saving and Loading Tasks with JSON
Persistence is what ensures tasks are not lost when you close the program. Every time you add, complete, or delete a task, the program automatically saves everything to a tasks.json file.
This is what the JSON file generated by the program looks like:
1# Contents of tasks.json (generated automatically)
2[
3 {
4 "task_id": 1,
5 "title": "Buy milk",
6 "description": "From the corner store",
7 "completed": false,
8 "created_at": "2025-01-15 10:30"
9 },
10 {
11 "task_id": 2,
12 "title": "Study Python",
13 "description": "Finish the course final project",
14 "completed": true,
15 "created_at": "2025-01-15 10:35"
16 }
17]
The flow works like this:
- When the program starts,
load_tasks()reads the JSON file and createsTaskobjects - When you make any change,
save_tasks()converts all tasks to dictionaries and saves them to JSON - If the file does not exist (first time), the program starts with an empty list
- If the file is corrupted, the program shows an error and starts with an empty list instead of crashing
ensure_ascii=False in json.dump() so that special characters (like accents) are saved correctly in the file.
Step 5: Interactive Menu — The main.py File
Finally, let us create the user interface: an interactive menu that lets the user choose what they want to do.
Open the main.py file and write:
1from task_manager import TaskManager
2
3
4def show_menu():
5 """Displays the main menu."""
6 print()
7 print("=" * 45)
8 print(" TASK MANAGER - Main Menu")
9 print("=" * 45)
10 print(" 1. Add task")
11 print(" 2. List all tasks")
12 print(" 3. List pending tasks")
13 print(" 4. Complete a task")
14 print(" 5. Delete a task")
15 print(" 6. Search tasks")
16 print(" 7. View statistics")
17 print(" 0. Exit")
18 print("-" * 45)
19
20
21def add_task_flow(manager):
22 """Flow for adding a task."""
23 print("
24--- Add New Task ---")
25 title = input("Title: ").strip()
26 if not title:
27 print("Error: title cannot be empty.")
28 return
29
30 description = input("Description (optional, press Enter to skip): ").strip()
31
32 try:
33 task = manager.add_task(title, description)
34 print(f"
35Task added successfully:")
36 print(task)
37 except ValueError as e:
38 print(f"Error: {e}")
39
40
41def list_tasks_flow(manager, show_all=True):
42 """Flow for listing tasks."""
43 label = "All Tasks" if show_all else "Pending Tasks"
44 print(f"
45--- {label} ---")
46
47 tasks = manager.list_tasks(show_all=show_all)
48 if not tasks:
49 print("No tasks to display.")
50 return
51
52 for task in tasks:
53 print()
54 print(task)
55 print(f"
56Total: {len(tasks)} task(s)")
57
58
59def complete_task_flow(manager):
60 """Flow for completing a task."""
61 print("
62--- Complete Task ---")
63 list_tasks_flow(manager, show_all=False)
64
65 try:
66 task_id = int(input("
67Enter the ID of the task to complete: "))
68 task = manager.complete_task(task_id)
69 print(f"
70Task completed:")
71 print(task)
72 except ValueError as e:
73 print(f"Error: {e}")
74
75
76def delete_task_flow(manager):
77 """Flow for deleting a task."""
78 print("
79--- Delete Task ---")
80 list_tasks_flow(manager)
81
82 try:
83 task_id = int(input("
84Enter the ID of the task to delete: "))
85 confirm = input(f"Are you sure you want to delete task #{task_id}? (y/n): ")
86 if confirm.lower() == "y":
87 task = manager.delete_task(task_id)
88 print(f"
89Task deleted: {task.title}")
90 else:
91 print("Operation cancelled.")
92 except ValueError as e:
93 print(f"Error: {e}")
94
95
96def search_tasks_flow(manager):
97 """Flow for searching tasks."""
98 print("
99--- Search Tasks ---")
100 keyword = input("Search keyword: ").strip()
101
102 try:
103 results = manager.search_tasks(keyword)
104 if not results:
105 print(f"No tasks found with '{keyword}'.")
106 return
107
108 print(f"
109Results for '{keyword}': {len(results)} task(s)")
110 for task in results:
111 print()
112 print(task)
113 except ValueError as e:
114 print(f"Error: {e}")
115
116
117def stats_flow(manager):
118 """Flow for displaying statistics."""
119 print("
120--- Statistics ---")
121 stats = manager.get_stats()
122
123 print(f" Total tasks: {stats['total']}")
124 print(f" Completed: {stats['completed']}")
125 print(f" Pending: {stats['pending']}")
126
127 if stats["total"] > 0:
128 percentage = (stats["completed"] / stats["total"]) * 100
129 bar_length = 20
130 filled = int(bar_length * stats["completed"] / stats["total"])
131 bar = "█" * filled + "░" * (bar_length - filled)
132 print(f" Progress: [{bar}] {percentage:.0f}%")
133
134
135def main():
136 """Main function of the program."""
137 print("
138Welcome to the Task Manager")
139 manager = TaskManager()
140
141 while True:
142 show_menu()
143 option = input("Choose an option: ").strip()
144
145 if option == "1":
146 add_task_flow(manager)
147 elif option == "2":
148 list_tasks_flow(manager, show_all=True)
149 elif option == "3":
150 list_tasks_flow(manager, show_all=False)
151 elif option == "4":
152 complete_task_flow(manager)
153 elif option == "5":
154 delete_task_flow(manager)
155 elif option == "6":
156 search_tasks_flow(manager)
157 elif option == "7":
158 stats_flow(manager)
159 elif option == "0":
160 print("
161Goodbye! Your tasks have been saved.")
162 break
163 else:
164 print("Invalid option. Choose a number from 0 to 7.")
165
166
167if __name__ == "__main__":
168 main()
To run the program, open a terminal in the project folder and type:
1# Run the task manager
2python main.py
Step 6: Error Handling Throughout the Project
Notice how error handling is integrated throughout the entire project:
In the model (models.py)
- The
from_dictmethod uses.get()with default values to avoidKeyError
In the logic (task_manager.py)
load_tasks: Catchesjson.JSONDecodeErrorif the file is corruptedsave_tasks: CatchesIOErrorif the file cannot be writtenadd_task: Validates that the title is not emptycomplete_task: Verifies the task exists and is not already completeddelete_task: Verifies the task exists before deletingsearch_tasks: Validates that the search keyword is not empty
In the interface (main.py)
- Each flow uses
try/exceptto catch validation errors and show friendly messages - The
delete_task_flowfunction asks for confirmation before deleting - The main menu handles invalid options with a clear message
1# Example of how errors look to the user:
2
3# If they try to complete a task that does not exist:
4# "Error: No task found with ID #99"
5
6# If they try to add a task without a title:
7# "Error: title cannot be empty."
8
9# If they type letters where a number is expected:
10# "Error: invalid literal for int() with base 10: 'abc'"
Complete Project Code
Here is the complete code for each file so you can copy and run it directly:
File: models.py
1from datetime import datetime
2
3
4class Task:
5 """Represents an individual task in the manager."""
6
7 def __init__(self, task_id, title, description=""):
8 self.task_id = task_id
9 self.title = title
10 self.description = description
11 self.completed = False
12 self.created_at = datetime.now().strftime("%Y-%m-%d %H:%M")
13
14 def mark_completed(self):
15 """Marks the task as completed."""
16 self.completed = True
17
18 def to_dict(self):
19 """Converts the task to a dictionary (to save as JSON)."""
20 return {
21 "task_id": self.task_id,
22 "title": self.title,
23 "description": self.description,
24 "completed": self.completed,
25 "created_at": self.created_at
26 }
27
28 @classmethod
29 def from_dict(cls, data):
30 """Creates a task from a dictionary (to load from JSON)."""
31 task = cls(data["task_id"], data["title"], data.get("description", ""))
32 task.completed = data.get("completed", False)
33 task.created_at = data.get("created_at", "")
34 return task
35
36 def __str__(self):
37 """Text representation of the task."""
38 status = "✓" if self.completed else "○"
39 text = f"[{status}] #{self.task_id} - {self.title}"
40 if self.description:
41 text += f"\n {self.description}"
42 text += f"\n Created: {self.created_at}"
43 return text
File: task_manager.py
1import json
2import os
3from models import Task
4
5
6class TaskManager:
7 """Manages the task list with JSON file persistence."""
8
9 def __init__(self, filename="tasks.json"):
10 self.filename = filename
11 self.tasks = []
12 self.next_id = 1
13 self.load_tasks()
14
15 def load_tasks(self):
16 """Loads tasks from the JSON file."""
17 if not os.path.exists(self.filename):
18 return
19
20 try:
21 with open(self.filename, "r", encoding="utf-8") as file:
22 data = json.load(file)
23 self.tasks = [Task.from_dict(item) for item in data]
24 if self.tasks:
25 self.next_id = max(t.task_id for t in self.tasks) + 1
26 print(f"Loaded {len(self.tasks)} tasks from {self.filename}")
27 except (json.JSONDecodeError, KeyError) as e:
28 print(f"Error reading task file: {e}")
29 print("Starting with an empty list.")
30 self.tasks = []
31
32 def save_tasks(self):
33 """Saves tasks to the JSON file."""
34 try:
35 data = [task.to_dict() for task in self.tasks]
36 with open(self.filename, "w", encoding="utf-8") as file:
37 json.dump(data, file, indent=2, ensure_ascii=False)
38 except IOError as e:
39 print(f"Error saving tasks: {e}")
40
41 def add_task(self, title, description=""):
42 """Adds a new task to the list."""
43 if not title or not title.strip():
44 raise ValueError("Task title cannot be empty")
45
46 task = Task(self.next_id, title.strip(), description.strip())
47 self.tasks.append(task)
48 self.next_id += 1
49 self.save_tasks()
50 return task
51
52 def list_tasks(self, show_all=True):
53 """Returns tasks (all or only pending)."""
54 if show_all:
55 return self.tasks
56 return [t for t in self.tasks if not t.completed]
57
58 def complete_task(self, task_id):
59 """Marks a task as completed."""
60 task = self._find_task(task_id)
61 if task is None:
62 raise ValueError(f"No task found with ID #{task_id}")
63 if task.completed:
64 raise ValueError(f"Task #{task_id} is already completed")
65
66 task.mark_completed()
67 self.save_tasks()
68 return task
69
70 def delete_task(self, task_id):
71 """Deletes a task from the list."""
72 task = self._find_task(task_id)
73 if task is None:
74 raise ValueError(f"No task found with ID #{task_id}")
75
76 self.tasks.remove(task)
77 self.save_tasks()
78 return task
79
80 def search_tasks(self, keyword):
81 """Searches tasks containing the keyword."""
82 if not keyword or not keyword.strip():
83 raise ValueError("Search keyword cannot be empty")
84
85 keyword = keyword.lower().strip()
86 results = [
87 t for t in self.tasks
88 if keyword in t.title.lower() or keyword in t.description.lower()
89 ]
90 return results
91
92 def get_stats(self):
93 """Returns task statistics."""
94 total = len(self.tasks)
95 completed = sum(1 for t in self.tasks if t.completed)
96 pending = total - completed
97 return {"total": total, "completed": completed, "pending": pending}
98
99 def _find_task(self, task_id):
100 """Finds a task by its ID (private method)."""
101 for task in self.tasks:
102 if task.task_id == task_id:
103 return task
104 return None
File: main.py
1from task_manager import TaskManager
2
3
4def show_menu():
5 """Displays the main menu."""
6 print()
7 print("=" * 45)
8 print(" TASK MANAGER - Main Menu")
9 print("=" * 45)
10 print(" 1. Add task")
11 print(" 2. List all tasks")
12 print(" 3. List pending tasks")
13 print(" 4. Complete a task")
14 print(" 5. Delete a task")
15 print(" 6. Search tasks")
16 print(" 7. View statistics")
17 print(" 0. Exit")
18 print("-" * 45)
19
20
21def add_task_flow(manager):
22 """Flow for adding a task."""
23 print("\n--- Add New Task ---")
24 title = input("Title: ").strip()
25 if not title:
26 print("Error: title cannot be empty.")
27 return
28
29 description = input("Description (optional, press Enter to skip): ").strip()
30
31 try:
32 task = manager.add_task(title, description)
33 print(f"\nTask added successfully:")
34 print(task)
35 except ValueError as e:
36 print(f"Error: {e}")
37
38
39def list_tasks_flow(manager, show_all=True):
40 """Flow for listing tasks."""
41 label = "All Tasks" if show_all else "Pending Tasks"
42 print(f"\n--- {label} ---")
43
44 tasks = manager.list_tasks(show_all=show_all)
45 if not tasks:
46 print("No tasks to display.")
47 return
48
49 for task in tasks:
50 print()
51 print(task)
52 print(f"\nTotal: {len(tasks)} task(s)")
53
54
55def complete_task_flow(manager):
56 """Flow for completing a task."""
57 print("\n--- Complete Task ---")
58 list_tasks_flow(manager, show_all=False)
59
60 try:
61 task_id = int(input("\nEnter the ID of the task to complete: "))
62 task = manager.complete_task(task_id)
63 print(f"\nTask completed:")
64 print(task)
65 except ValueError as e:
66 print(f"Error: {e}")
67
68
69def delete_task_flow(manager):
70 """Flow for deleting a task."""
71 print("\n--- Delete Task ---")
72 list_tasks_flow(manager)
73
74 try:
75 task_id = int(input("\nEnter the ID of the task to delete: "))
76 confirm = input(f"Are you sure you want to delete task #{task_id}? (y/n): ")
77 if confirm.lower() == "y":
78 task = manager.delete_task(task_id)
79 print(f"\nTask deleted: {task.title}")
80 else:
81 print("Operation cancelled.")
82 except ValueError as e:
83 print(f"Error: {e}")
84
85
86def search_tasks_flow(manager):
87 """Flow for searching tasks."""
88 print("\n--- Search Tasks ---")
89 keyword = input("Search keyword: ").strip()
90
91 try:
92 results = manager.search_tasks(keyword)
93 if not results:
94 print(f"No tasks found with '{keyword}'.")
95 return
96
97 print(f"\nResults for '{keyword}': {len(results)} task(s)")
98 for task in results:
99 print()
100 print(task)
101 except ValueError as e:
102 print(f"Error: {e}")
103
104
105def stats_flow(manager):
106 """Flow for displaying statistics."""
107 print("\n--- Statistics ---")
108 stats = manager.get_stats()
109
110 print(f" Total tasks: {stats['total']}")
111 print(f" Completed: {stats['completed']}")
112 print(f" Pending: {stats['pending']}")
113
114 if stats["total"] > 0:
115 percentage = (stats["completed"] / stats["total"]) * 100
116 bar_length = 20
117 filled = int(bar_length * stats["completed"] / stats["total"])
118 bar = "█" * filled + "░" * (bar_length - filled)
119 print(f" Progress: [{bar}] {percentage:.0f}%")
120
121
122def main():
123 """Main function of the program."""
124 print("\nWelcome to the Task Manager")
125 manager = TaskManager()
126
127 while True:
128 show_menu()
129 option = input("Choose an option: ").strip()
130
131 if option == "1":
132 add_task_flow(manager)
133 elif option == "2":
134 list_tasks_flow(manager, show_all=True)
135 elif option == "3":
136 list_tasks_flow(manager, show_all=False)
137 elif option == "4":
138 complete_task_flow(manager)
139 elif option == "5":
140 delete_task_flow(manager)
141 elif option == "6":
142 search_tasks_flow(manager)
143 elif option == "7":
144 stats_flow(manager)
145 elif option == "0":
146 print("\nGoodbye! Your tasks have been saved.")
147 break
148 else:
149 print("Invalid option. Choose a number from 0 to 7.")
150
151
152if __name__ == "__main__":
153 main()
Ideas to Improve the Project
You already have a working task manager. Here are some ideas to keep practicing and improve the project:
Easy level
- Terminal colors: Install the
coloramalibrary (pip install colorama) and add colors to the menu and messages. For example, completed tasks in green and pending ones in yellow. - Sort tasks: Add an option to sort tasks by creation date, by title (alphabetically), or by status.
- Edit tasks: Add the ability to change the title or description of an existing task.
Intermediate level
- Priorities: Add a priority field (high, medium, low) to tasks and allow filtering by priority.
- Due dates: Add a due date to tasks and show alerts when a task is about to expire.
- Categories: Allow assigning categories to tasks (work, personal, study) and filtering by category.
Advanced level
- SQLite database: Instead of JSON, use SQLite to store tasks. Python includes the
sqlite3module without needing to install anything. - Graphical interface: Create a version with a graphical interface using
tkinter(comes included with Python). - REST API: Convert the manager into a web API using Flask or FastAPI — so you can access your tasks from a browser.
Course Conclusion
Congratulations! You have completed the Python from Scratch Course. Throughout 10 articles, you have gone from knowing nothing about programming to building a complete project with multiple files, classes, data persistence, and error handling.
Let us review everything you learned:
| Article | Topic |
|---|---|
| Part 1 | Installing Python and your first program |
| Part 2 | Variables and data types |
| Part 3 | Conditionals (if, elif, else) |
| Part 4 | Loops (for, while) and lists |
| Part 5 | Functions and dictionaries |
| Part 6 | Strings and text methods |
| Part 7 | Object-oriented programming (OOP) |
| Part 8 | Files and modules |
| Part 9 | Error handling and exceptions |
| Part 10 | Final project: task manager |
What comes next after this course?
Now that you have mastered the fundamentals of Python, you have many options to keep growing:
- Web Development: Learn Flask or Django to create web applications with Python. Flask is simpler to start with, Django is more complete for large projects.
- Data Science: Learn pandas, NumPy, and Matplotlib to analyze and visualize data. It is one of the best-paid areas in tech.
- Automation: Use Python to automate repetitive tasks: rename files, extract data from web pages (web scraping with BeautifulSoup), send automatic emails, etc.
- REST APIs: Learn FastAPI to create modern, fast APIs. It is the most popular Python framework for APIs.
- Machine Learning: Learn scikit-learn and TensorFlow to create artificial intelligence models.
- Databases: Learn SQL and how to connect Python with databases like PostgreSQL or MySQL.
Comments
Sign in to leave a comment
No comments yet. Be the first!