On this page
article
Working with Databases
Connect Python to SQLite and PostgreSQL, use SQLAlchemy ORM, write queries, manage transactions, and follow database best practices.
Most applications store data in databases. Python connects to SQL and NoSQL databases through built-in modules and third-party libraries.
SQLite — Built-In, Zero Config
Perfect for development, prototypes, and embedded apps:
import sqlite3
conn = sqlite3.connect("app.db")
conn.row_factory = sqlite3.Row # dict-like access
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
email TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
cursor.execute(
"INSERT INTO users (username, email) VALUES (?, ?)",
("alice", "[email protected]"),
)
conn.commit()
cursor.execute("SELECT * FROM users WHERE username = ?", ("alice",))
user = cursor.fetchone()
print(dict(user))
conn.close()
Always use ? placeholders — never f-strings for SQL values.
Context Manager Pattern
with sqlite3.connect("app.db") as conn:
cursor = conn.cursor()
cursor.execute("SELECT COUNT(*) FROM users")
count = cursor.fetchone()[0]
print(f"Total users: {count}")
# Connection auto-committed and closed
SQLAlchemy ORM
The standard ORM for Python — database-agnostic:
pip install sqlalchemy
from sqlalchemy import create_engine, Column, Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import DeclarativeBase, sessionmaker, relationship
from datetime import datetime
engine = create_engine("sqlite:///app.db")
Session = sessionmaker(bind=engine)
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
email = Column(String(100), nullable=False)
posts = relationship("Post", back_populates="author")
class Post(Base):
__tablename__ = "posts"
id = Column(Integer, primary_key=True)
title = Column(String(200), nullable=False)
body = Column(String, nullable=False)
author_id = Column(Integer, ForeignKey("users.id"))
author = relationship("User", back_populates="posts")
Base.metadata.create_all(engine)
CRUD with SQLAlchemy
with Session() as session:
# Create
user = User(username="bob", email="[email protected]")
session.add(user)
session.commit()
# Read
users = session.query(User).filter(User.username.like("b%")).all()
user = session.query(User).filter_by(username="bob").first()
# Update
user.email = "[email protected]"
session.commit()
# Delete
session.delete(user)
session.commit()
PostgreSQL
pip install psycopg2-binary sqlalchemy
engine = create_engine("postgresql://user:pass@localhost:5432/mydb")
SQLAlchemy code stays the same — only the connection URL changes.
Raw SQL with SQLAlchemy
When you need full SQL control:
from sqlalchemy import text
with Session() as session:
result = session.execute(
text("SELECT username, COUNT(*) as post_count FROM users u "
"JOIN posts p ON u.id = p.author_id GROUP BY username")
)
for row in result:
print(row.username, row.post_count)
Migrations with Alembic
Track schema changes over time:
pip install alembic
alembic init migrations
alembic revision --autogenerate -m "create users table"
alembic upgrade head
Transactions
with Session() as session:
try:
session.add(User(username="charlie", email="[email protected]"))
session.add(Post(title="First", body="Hello", author_id=1))
session.commit()
except Exception:
session.rollback()
raise
Database Best Practices
- Use parameterized queries — prevent SQL injection
- Use connection pooling in production (SQLAlchemy handles this)
- Index frequently queried columns
- Use migrations — never modify production schema manually
- Close connections — use context managers
- Separate config — database URL from environment variables
import os
DATABASE_URL = os.environ["DATABASE_URL"]
engine = create_engine(DATABASE_URL, pool_size=10, max_overflow=20)
Databases are the persistence layer of virtually every production Python application.