-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
292: Practice Exercise: Building a Simple SQLite-Backed To-Do App
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
taskswith the following columns:id(Integer PK),task_text(Text),priority(Integer, where 1 is high and 3 is low), andis_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 whereis_completedis 0, sorted by priority (1s first). - Complete Task: Write a function
mark_completed(task_id)that updates theis_completedstatus 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.
There are no comments for now.