Beyond basic queries, Python's sqlite3 module supports transactions, custom types, and advanced SQLite features.
Context Manager
import sqlite3
# Auto-commit/rollback with context manager
with sqlite3.connect('app.db') as conn:
conn.execute('INSERT INTO users (name) VALUES (?)', ('Alice',))
# Auto-commits on success
# Auto-rolls back on exceptionRow Factories
import sqlite3
conn = sqlite3.connect('app.db')
# Dictionary rows
conn.row_factory = sqlite3.Row
cursor = conn.execute('SELECT * FROM users')
row = cursor.fetchone()
print(row['name']) # Access by column name
print(dict(row)) # Convert to dict
print(row.keys()) # Column names
# Named tuple rows
def namedtuple_factory(cursor, row):
from collections import namedtuple
fields = [col[0] for col in cursor.description]
Row = namedtuple('Row', fields)
return Row(*row)
conn.row_factory = namedtuple_factoryParameterized Queries
import sqlite3
conn = sqlite3.connect('app.db')
# Positional (?)
conn.execute('INSERT INTO users (name, age) VALUES (?, ?)', ('Alice', 30))
# Named (:name)
conn.execute(
'INSERT INTO users (name, age) VALUES (:name, :age)',
{'name': 'Bob', 'age': 25}
)
# executemany for batch inserts
users = [('Alice', 30), ('Bob', 25), ('Charlie', 35)]
conn.executemany('INSERT INTO users (name, age) VALUES (?, ?)', users)Transactions
import sqlite3
conn = sqlite3.connect('app.db')
# Explicit transaction
conn.execute('BEGIN')
try:
conn.execute('UPDATE accounts SET balance = balance - 100 WHERE id = 1')
conn.execute('UPDATE accounts SET balance = balance + 100 WHERE id = 2')
conn.execute('COMMIT')
except:
conn.execute('ROLLBACK')
raise
# With isolation level
conn = sqlite3.connect('app.db', isolation_level='DEFERRED')
# Options: None (autocommit), 'DEFERRED', 'IMMEDIATE', 'EXCLUSIVE'Custom Types
import sqlite3
from datetime import datetime
import json
# Register adapter (Python → SQLite)
def adapt_datetime(dt):
return dt.isoformat()
sqlite3.register_adapter(datetime, adapt_datetime)
# Register converter (SQLite → Python)
def convert_datetime(data):
return datetime.fromisoformat(data.decode())
sqlite3.register_converter('DATETIME', convert_datetime)
# Enable type detection
conn = sqlite3.connect('app.db', detect_types=sqlite3.PARSE_DECLTYPES)
# JSON column
sqlite3.register_adapter(dict, json.dumps)
sqlite3.register_converter('JSON', lambda x: json.loads(x.decode()))In-Memory Database
import sqlite3
# Ephemeral in-memory database
conn = sqlite3.connect(':memory:')
# Shared in-memory (multiple connections)
conn = sqlite3.connect('file::memory:?cache=shared', uri=True)Full-Text Search
import sqlite3
conn = sqlite3.connect('app.db')
# Create FTS5 table
conn.execute('''
CREATE VIRTUAL TABLE IF NOT EXISTS articles_fts
USING fts5(title, content)
''')
# Insert
conn.execute('INSERT INTO articles_fts VALUES (?, ?)',
('Python Guide', 'Learn Python programming...'))
# Search
results = conn.execute('''
SELECT * FROM articles_fts WHERE articles_fts MATCH 'python'
''').fetchall()
# Ranked search
results = conn.execute('''
SELECT *, rank FROM articles_fts
WHERE articles_fts MATCH 'python'
ORDER BY rank
''').fetchall()JSON Functions (SQLite 3.38+)
import sqlite3
conn = sqlite3.connect('app.db')
# Store and query JSON
conn.execute('''
CREATE TABLE IF NOT EXISTS events (
id INTEGER PRIMARY KEY,
data JSON
)
''')
conn.execute('INSERT INTO events (data) VALUES (?)',
['{"type": "click", "x": 100, "y": 200}'])
# Extract JSON values
result = conn.execute('''
SELECT json_extract(data, '$.type') as event_type
FROM events
''').fetchone()Connection Pool Pattern
import sqlite3
from queue import Queue
from contextlib import contextmanager
class SQLitePool:
def __init__(self, database, size=5):
self.database = database
self.pool = Queue(maxsize=size)
for _ in range(size):
conn = sqlite3.connect(database, check_same_thread=False)
conn.row_factory = sqlite3.Row
self.pool.put(conn)
@contextmanager
def connection(self):
conn = self.pool.get()
try:
yield conn
finally:
self.pool.put(conn)
pool = SQLitePool('app.db')
with pool.connection() as conn:
conn.execute('SELECT * FROM users')Backup
import sqlite3
source = sqlite3.connect('app.db')
backup = sqlite3.connect('backup.db')
source.backup(backup)
# Or with progress callback
def progress(status, remaining, total):
print(f'Copied {total-remaining}/{total} pages')
source.backup(backup, pages=10, progress=progress)Write-Ahead Logging (WAL)
import sqlite3
conn = sqlite3.connect('app.db')
conn.execute('PRAGMA journal_mode=WAL')
# Better concurrent read performance
# Reads don't block writes and vice versaPerformance Tips
import sqlite3
conn = sqlite3.connect('app.db')
# Use executemany for bulk inserts
data = [(i, f'user{i}') for i in range(10000)]
conn.executemany('INSERT INTO users VALUES (?, ?)', data)
# Indexes
conn.execute('CREATE INDEX IF NOT EXISTS idx_users_name ON users(name)')
# Analyze for query planner
conn.execute('ANALYZE')
# Vacuum to reclaim space
conn.execute('VACUUM')
# Cache size (pages)
conn.execute('PRAGMA cache_size = 10000')
# Synchronous mode (careful with data safety)
conn.execute('PRAGMA synchronous = NORMAL')Upsert (SQLite 3.24+)
import sqlite3
conn = sqlite3.connect('app.db')
conn.execute('''
INSERT INTO users (id, name, visits) VALUES (?, ?, 1)
ON CONFLICT(id) DO UPDATE SET visits = visits + 1
''', (1, 'Alice'))Thread Safety
import sqlite3
import threading
# Default: connections can't be shared between threads
conn = sqlite3.connect('app.db') # Only use in one thread
# Allow multi-thread access (not recommended)
conn = sqlite3.connect('app.db', check_same_thread=False)
# Better: one connection per thread
local = threading.local()
def get_conn():
if not hasattr(local, 'conn'):
local.conn = sqlite3.connect('app.db')
return local.connRepository Pattern
import sqlite3
from dataclasses import dataclass
from typing import Optional, List
@dataclass
class User:
id: Optional[int]
name: str
email: str
class UserRepository:
def __init__(self, conn: sqlite3.Connection):
self.conn = conn
self.conn.row_factory = sqlite3.Row
def get(self, user_id: int) -> Optional[User]:
row = self.conn.execute(
'SELECT * FROM users WHERE id = ?', (user_id,)
).fetchone()
return User(**dict(row)) if row else None
def save(self, user: User) -> User:
if user.id is None:
cursor = self.conn.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
(user.name, user.email)
)
user.id = cursor.lastrowid
else:
self.conn.execute(
'UPDATE users SET name=?, email=? WHERE id=?',
(user.name, user.email, user.id)
)
return userSummary
sqlite3 advanced patterns:
- Row factories: Dict/namedtuple access
- Custom types: datetime, JSON adapters
- FTS5: Full-text search
- WAL mode: Better concurrency
- Transactions: Explicit control
- Bulk operations: executemany
SQLite is powerful enough for most applications.
React to this post: