✦ Python Intermediate & OOP Level 2 final project

L2 · Concept 01 · Final Project: Task Manager

Build One Project.
Grow It Step by Step.

Start a small local Task Manager that will grow through the final four Level 2 concepts.

Learning goalExplore and extend the starter Task and TaskManager classes, organize them into packages, and understand why each TaskManager instance owns its own task list.
01 Learn02 Work locally03 Check your understanding

Learning as a guest

Learning as a guest? Save your progress!

Create a free account to learn with Sloby AI, check your understanding, and track your progress.

The entire platform is free throughout 2026. Foundations stays free forever.

A final project is built in small, useful steps.

Until now, you practiced individual Python and OOP ideas. In the final project, you will combine them in one local application: a Task Manager for keeping small tasks organized.

task_manager/
├── main.py
├── models/
│   ├── __init__.py
│   └── task.py
└── services/
    ├── __init__.py
    └── task_manager.py

models contains data objects. services contains code that coordinates those objects. main.py starts a small demonstration. Later concepts will add task operations, JSON storage, and tests without moving these responsibilities around.

A Task represents one piece of data. A TaskManager owns a collection of Task objects and will later offer operations for that collection. This separation keeps one class from trying to do every job.

↳

Stable project agreement: Each Task will have a string task_id, a title, and a completion state. Each TaskManager will own its own tasks list. Concept 29 adds management operations, concept 30 saves data as JSON, and concept 31 tests the rules.

The first two classes
class Task:
    """Represent one task in the local project."""

    def __init__(self, task_id: str, title: str) -> None:
        self.task_id = task_id
        self.title = title
        self.completed = False


class TaskManager:
    """Keep this manager instance's tasks together."""

    def __init__(self) -> None:
        self.tasks: list[Task] = []

    def add_task(self, task: Task) -> None:
        self.tasks.append(task)

The constructor gives every TaskManager a new empty list. The list is an instance attribute, so adding a task to one manager does not add it to another manager. Type hints and short docstrings make the intended roles easier to read; they do not add automatic validation.

Import classes from their packages
from models.task import Task
from services.task_manager import TaskManager

The dot in an import follows the project folders. Empty __init__.py files make these folders clear classic Python packages for this beginner-friendly project.

Run the first demonstration
from models.task import Task
from services.task_manager import TaskManager

first_manager = TaskManager()
second_manager = TaskManager()

first_manager.add_task(Task("task-1", "Read the project plan"))

print(len(first_manager.tasks))
print(len(second_manager.tasks))

The output is 1 and then 0. The first manager received one Task; the second manager starts independently with no tasks.

How this project will grow

  1. Concept 28 — Build the foundationCreate the folders, Task, TaskManager, imports, and a small runnable demonstration.
  2. Concept 29 — Manage tasksBuild on add_task() with focused operations for viewing tasks, finding a task, changing its status, and removing it.
  3. Concept 30 — Save locallyAdd a small JSON storage module so task data can be saved and loaded from a local file.
  4. Concept 31 — Protect the rulesUse unittest to check the classes, manager behavior, and local storage.

Common project-structure mistakes

  • One shared class listDo not write tasks = [] directly on TaskManager for this project. Put self.tasks = [] in __init__ so every manager starts separately.
  • Putting every responsibility in main.pyKeep data in Task and collection behavior in TaskManager. main.py should demonstrate how the pieces work together.
  • Building future features too earlyDo not add JSON files or a full command menu yet. Build and run this small foundation first.

Start Your Task Manager

Do this in VS Code

Download one small project folder. Keep and improve this same folder through the next three final-project concepts.

Download starter project
Task Manager starter
task_manager/
├── main.py
├── models/
│   ├── __init__.py
│   └── task.py
├── services/
│   ├── __init__.py
│   └── task_manager.py
├── data/
│   └── .gitkeep
├── .gitignore
├── README_SR.md
└── README_EN.md

This starter contains only the foundation: Task, TaskManager, imports, and a demonstration. It deliberately does not contain a command menu, JSON persistence, or finished tests.

Open and run the starter

  1. Extract and open the folderExtract task_manager_starter.zip, then use File → Open Folder in VS Code and select the extracted task_manager_starter folder. In the lesson diagrams, task_manager is the project name; your extracted folder keeps the starter name.
  2. Read the responsibilitiesOpen models/task.py and services/task_manager.py. Identify which class represents one task and which class owns the collection.
  3. Run the demonstrationOpen the VS Code terminal in the project folder and run main.py. Confirm that the two managers have separate task lists.
    python3 main.py
    # or: python main.py
  4. Make one small changeAdd one simple Task attribute, such as a category, then display it in main.py. Do not add task management or JSON features yet.
↳

Expected result: First manager: 1 task(s) Second manager: 0 task(s) The output demonstrates separate TaskManager instance state.

Before asking Sloby for help

  • Run from the project folderThe terminal should be inside task_manager before you run python main.py or python3 main.py.
  • Check package file namesKeep models/__init__.py and services/__init__.py. Check spelling in the imports before changing the code.

What will this program print?

Focus on where each manager stores its task list.

from models.task import Task
from services.task_manager import TaskManager

first_manager = TaskManager()
second_manager = TaskManager()

first_manager.add_task(Task("task-1", "Read the project plan"))

print(len(first_manager.tasks))
print(len(second_manager.tasks))
Choose the output

Build confidence independently

Extend the foundation carefully

Work in your local folder. Keep the demonstration running while you make one small structural improvement.

Your goalAdd one simple Task attribute, create a Task in main.py, add it to one TaskManager, and show that a second TaskManager still has an empty tasks list.
task_manager/
├── main.py
├── models/task.py
└── services/task_manager.py

Work in your local VS Code folder

Progressive hints
  • A new constructor parameter can have a default value, so existing Task calls can keep working while you add a small attribute.
  • Create two separate TaskManager() objects and add the Task to only one of them.
  • Use len(first_manager.tasks) and len(second_manager.tasks) to compare their state.

Ask Sloby for a guiding question about class responsibilities or imports. It can review the active file, but it cannot run or change your local project. You are ready for Concept 29 when you can run main.py, explain the difference between Task and TaskManager, make one small class change, and show that two TaskManager instances do not share the same task list.

Sloby, the Python AI Tutor mascot

Your learning mentor

Sign in to ask Sloby

Create a free account or sign in to get AI guidance while you learn.

Sign in to check your understanding

Create a free account or sign in to receive AI feedback and save your learning progress.

Python AI Tutor · Learn by understanding, not just by running code.