Lab 08: SQLite & Database

Objective

Work with SQLite using Python's sqlite3 module: schema creation, CRUD operations, transactions, parameterized queries, connection pooling pattern, and repository design.

Time

35 minutes

Prerequisites

  • Lab 01 (Advanced OOP), Lab 07 (Type Hints)

Tools

  • Docker image: zchencow/innozverse-python:latest


Lab Instructions

Step 1: sqlite3 Basics

docker run --rm zchencow/innozverse-python:latest python3 -c "
import sqlite3

# Connect (creates file or uses :memory: for temp DB)
conn = sqlite3.connect(':memory:')
conn.row_factory = sqlite3.Row  # access columns by name

cur = conn.cursor()

# Create table
cur.execute('''
    CREATE TABLE products (
        id       INTEGER PRIMARY KEY AUTOINCREMENT,
        name     TEXT NOT NULL,
        price    REAL NOT NULL CHECK(price > 0),
        stock    INTEGER NOT NULL DEFAULT 0,
        category TEXT NOT NULL,
        status   TEXT NOT NULL DEFAULT \"active\",
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
''')

# Insert (always use ? placeholders — never f-strings!)
cur.executemany('''
    INSERT INTO products (name, price, stock, category)
    VALUES (?, ?, ?, ?)
''', [
    ('Surface Pro 12\"', 864.00, 15, 'Laptop'),
    ('Surface Pen',      49.99,  80, 'Accessory'),
    ('Office 365',       99.99,  999,'Software'),
    ('USB-C Hub',        29.99,  0,  'Accessory'),
    ('Surface Book 3',   1299.00,5,  'Laptop'),
])

conn.commit()

# Query all
rows = cur.execute('SELECT * FROM products ORDER BY price DESC').fetchall()
print(f'Total products: {len(rows)}')
for row in rows:
    print(f'  [{row[\"id\"]}] {row[\"name\"]:20s} \${row[\"price\"]:8.2f}  stock={row[\"stock\"]}')

# WHERE + aggregation
avg = cur.execute('SELECT AVG(price) FROM products').fetchone()[0]
print(f'Average price: \${avg:.2f}')

by_cat = cur.execute('''
    SELECT category, COUNT(*) as count, AVG(price) as avg_price
    FROM products GROUP BY category ORDER BY category
''').fetchall()
for row in by_cat:
    print(f'  {row[\"category\"]:12s}: {row[\"count\"]} items, avg \${row[\"avg_price\"]:.2f}')

conn.close()
"

💡 Always use ? placeholders for parameterized queries — never format values directly into SQL strings. cur.execute('SELECT * FROM users WHERE name = ?', (name,)) is safe; f'SELECT * FROM users WHERE name = \"{name}\"' is vulnerable to SQL injection. The trailing comma in (name,) makes it a tuple.

📸 Verified Output:


Step 2: Transactions & Error Handling

📸 Verified Output:


Steps 3–8: Repository Pattern, Migrations, Full-Text Search, Pagination, Aggregation, Capstone

📸 Verified Output:


Summary

Pattern
Code
Notes

Connect

sqlite3.connect(':memory:')

:memory: for temp

Named columns

conn.row_factory = sqlite3.Row

Access by name

Safe query

cur.execute('... WHERE id=?', (id,))

Always use ?

Batch insert

cur.executemany(sql, list_of_tuples)

Faster than loop

Transaction

conn.commit() / conn.rollback()

Explicit control

Context manager

@contextmanager + try/commit/rollback

Auto-rollback on error

Repository

Class wrapping all SQL operations

Clean separation

Further Reading

Last updated