Skip to Content
Course content

292: Practice Exercise: Building a Simple SQLite-Backed To-Do App

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Whenever I see developers starting with SQLite, they usually bring a "server mindset" with them. They spend twenty minutes hunting for a "Start Server" button or trying to figure out which port the database is listening on, assuming that since it's a SQL database, there must be a background process running—similar to how PostgreSQL or MySQL works.

The "Database Server" Myth

If you've been trying to find the SQLite configuration file to "turn on" the database, stop right there. You're looking for something that doesn't exist. In those other databases, your Python code talks to a separate piece of software (the server) via a network socket. If that server isn't running, your app crashes.

# This is what people expect: 
# connect(host="localhost", port=5432, user="admin", password="123")
# But in SQLite, it's just...
import sqlite3
connection = sqlite3.connect('todo_list.db') 

SQLite is Just a File on Your Hard Drive

Here is the reality: SQLite is a library, not a server. When you call sqlite3.connect('todo_list.db'), Python isn't reaching out across a network; it's simply opening a file on your disk. The "database engine" is actually built directly into the Python process. This makes it incredibly fast for small apps and means you can move your entire database just by copying a single file to a USB drive.

Connecting Your Logic to the Disk

To build a To-Do app, we need more than just a connection; we need a way to execute commands. This is where the cursor comes in. I like to think of the connection as the "pipe" to the file and the cursor as the "worker" who actually goes in and fetches or changes the data.

For our app, we need a table to store the task description and a boolean to track if it's finished. Here is how I typically structure the initialization:

import sqlite3

def init_db():
    conn = sqlite3.connect('todo_list.db')
    cursor = conn.cursor()
    # We use IF NOT EXISTS so we don't crash the app on the second launch
    cursor.execute('''
        CREATE TABLE IF NOT EXISTS tasks (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            task_text TEXT NOT NULL,
            is_completed INTEGER DEFAULT 0
        )
    ''')
    conn.commit()
    conn.close()

init_db()

The Danger of Forgetting to Commit

This is the most common "Why isn't my data saving?" bug I see. In SQLite, changes (like INSERT, UPDATE, or DELETE) happen inside a transaction. They are held in a temporary state. If you close the connection without calling conn.commit(), SQLite assumes something went wrong and rolls back those changes to protect the integrity of your file.

I've spent hours debugging "phantom data" only to realize I forgot one line of code. If you're adding a task to your list, you must commit that change, or it will vanish the moment your script ends.

def add_task(text):
    conn = sqlite3.connect('todo_list.db')
    cursor = conn.cursor()
    cursor.execute('INSERT INTO tasks (task_text) VALUES (?)', (text,))
    conn.commit() # <--- Without this, the task never actually hits the disk
    conn.close()



📋 Practical Task

Build a Persistent To-Do Manager with Priority Levels

Now it's your turn to put this into practice. You are going to build a small command-line utility that manages a task list. However, to make it more realistic, we're adding a "Priority" feature.

Requirements:

  • Database Schema: Create a table named tasks with the following columns: id (Integer PK), task_text (Text), priority (Integer, where 1 is high and 3 is low), and is_completed (Integer).
  • Add Task: Write a function add_task(text, priority) that inserts a new task into the database.
  • View Tasks: Write a function get_pending_tasks() that returns all tasks where is_completed is 0, sorted by priority (1s first).
  • Complete Task: Write a function mark_completed(task_id) that updates the is_completed status to 1 for a specific ID.

Validation: To test your work, run your script, add three tasks with different priorities, restart your Python interpreter, and call get_pending_tasks(). If the tasks are still there and sorted correctly, you've successfully implemented persistence.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.