تخطَّ إلى المحتوى

SQLAlchemy Cheat Sheet

Overview

SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives application developers the full power and flexibility of SQL. It provides a full suite of well-known enterprise-level persistence patterns, designed for efficient and high-performing database access. SQLAlchemy’s philosophy is that databases and SQL are powerful tools that shouldn’t be hidden behind abstraction walls.

Created by Mike Bayer in 2006, SQLAlchemy has become the de facto standard ORM for Python applications. It provides two distinct APIs: the Core layer for direct SQL expression construction, and the ORM layer for mapping Python classes to database tables. SQLAlchemy 2.0 introduced a modern, unified API with full type annotation support, making it more consistent and easier to use while maintaining backward compatibility.

Installation

Setup

# Base installation
pip install sqlalchemy

# With database drivers
pip install sqlalchemy psycopg2-binary    # PostgreSQL
pip install sqlalchemy pymysql            # MySQL
pip install sqlalchemy                     # SQLite (built-in)

# With async support
pip install sqlalchemy[asyncio] asyncpg   # Async PostgreSQL
pip install sqlalchemy[asyncio] aiosqlite # Async SQLite

# Type stubs
pip install sqlalchemy-stubs

Engine and Connection

Creating Engine

from sqlalchemy import create_engine

# SQLite
engine = create_engine("sqlite:///app.db", echo=True)

# PostgreSQL
engine = create_engine(
    "postgresql+psycopg2://user:password@localhost:5432/mydb",
    pool_size=10,
    max_overflow=20,
    pool_recycle=3600,
    echo=False,
)

# MySQL
engine = create_engine("mysql+pymysql://user:pass@localhost/mydb")

# Connection
with engine.connect() as conn:
    result = conn.execute(text("SELECT 1"))
    conn.commit()

Async Engine

from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker

engine = create_async_engine(
    "postgresql+asyncpg://user:pass@localhost/mydb",
    echo=True,
)

async_session = async_sessionmaker(engine, expire_on_commit=False)

async with async_session() as session:
    result = await session.execute(select(User))
    users = result.scalars().all()

ORM Model Definition

Declarative Models (2.0 Style)

from datetime import datetime
from typing import Optional, List
from sqlalchemy import String, Integer, Boolean, ForeignKey, Text, DateTime, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship

class Base(DeclarativeBase):
    pass

class User(Base):
    __tablename__ = "users"

    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column(String(100))
    email: Mapped[str] = mapped_column(String(255), unique=True, index=True)
    age: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
    active: Mapped[bool] = mapped_column(Boolean, default=True)
    created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
    updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())

    # Relationships
    posts: Mapped[List["Post"]] = relationship(back_populates="author", cascade="all, delete-orphan")
    profile: Mapped[Optional["Profile"]] = relationship(back_populates="user", uselist=False)

    def __repr__(self) -> str:
        return f"User(id={self.id}, name={self.name!r})"

class Post(Base):
    __tablename__ = "posts"

    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column(String(255))
    body: Mapped[str] = mapped_column(Text)
    published: Mapped[bool] = mapped_column(Boolean, default=False)
    author_id: Mapped[int] = mapped_column(ForeignKey("users.id", ondelete="CASCADE"))

    author: Mapped["User"] = relationship(back_populates="posts")
    comments: Mapped[List["Comment"]] = relationship(back_populates="post", cascade="all, delete-orphan")
    created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())

class Comment(Base):
    __tablename__ = "comments"

    id: Mapped[int] = mapped_column(primary_key=True)
    content: Mapped[str] = mapped_column(Text)
    post_id: Mapped[int] = mapped_column(ForeignKey("posts.id", ondelete="CASCADE"))
    author_id: Mapped[int] = mapped_column(ForeignKey("users.id"))

    post: Mapped["Post"] = relationship(back_populates="comments")
    author: Mapped["User"] = relationship()

class Profile(Base):
    __tablename__ = "profiles"

    id: Mapped[int] = mapped_column(primary_key=True)
    bio: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
    user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), unique=True)

    user: Mapped["User"] = relationship(back_populates="profile")

Create Tables

# Create all tables
Base.metadata.create_all(engine)

# Drop all tables
Base.metadata.drop_all(engine)

Session and CRUD

Session Setup

from sqlalchemy.orm import Session, sessionmaker

# Session factory
SessionLocal = sessionmaker(bind=engine, expire_on_commit=False)

# Using session
with SessionLocal() as session:
    # operations
    session.commit()

# Context manager
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

Create

from sqlalchemy.orm import Session

with Session(engine) as session:
    # Single insert
    user = User(name="Alice", email="alice@example.com", age=30)
    session.add(user)
    session.commit()
    session.refresh(user)  # Get generated ID

    # Bulk insert
    users = [
        User(name="Bob", email="bob@example.com"),
        User(name="Charlie", email="charlie@example.com"),
    ]
    session.add_all(users)
    session.commit()

    # Create with relationships
    user = User(
        name="Dave",
        email="dave@example.com",
        posts=[
            Post(title="First Post", body="Content"),
            Post(title="Second Post", body="More content"),
        ],
        profile=Profile(bio="Hello!"),
    )
    session.add(user)
    session.commit()

Read

from sqlalchemy import select, and_, or_, func, desc

with Session(engine) as session:
    # Get by primary key
    user = session.get(User, 1)

    # Select all
    stmt = select(User)
    users = session.scalars(stmt).all()

    # Filtering
    stmt = select(User).where(User.active == True).where(User.age >= 18)
    users = session.scalars(stmt).all()

    # Complex conditions
    stmt = select(User).where(
        and_(
            User.active == True,
            or_(
                User.role == "admin",
                User.age >= 18,
            )
        )
    ).order_by(desc(User.created_at)).limit(10).offset(0)

    # Like / ilike
    stmt = select(User).where(User.name.ilike("%alice%"))

    # In clause
    stmt = select(User).where(User.id.in_([1, 2, 3]))

    # Join
    stmt = (
        select(User, Post)
        .join(Post, User.id == Post.author_id)
        .where(Post.published == True)
    )
    results = session.execute(stmt).all()

    # Eager loading
    from sqlalchemy.orm import joinedload, selectinload
    stmt = select(User).options(
        joinedload(User.profile),
        selectinload(User.posts),
    ).where(User.id == 1)
    user = session.scalars(stmt).unique().one()

    # Aggregation
    stmt = select(func.count(User.id), func.avg(User.age)).where(User.active == True)
    count, avg_age = session.execute(stmt).one()

    # Group by
    stmt = (
        select(User.role, func.count(User.id))
        .group_by(User.role)
    )
    results = session.execute(stmt).all()

    # Subquery
    subq = (
        select(Post.author_id, func.count(Post.id).label("post_count"))
        .group_by(Post.author_id)
        .subquery()
    )
    stmt = select(User.name, subq.c.post_count).join(subq, User.id == subq.c.author_id)

Update

with Session(engine) as session:
    # Update by object
    user = session.get(User, 1)
    user.name = "Alice Smith"
    session.commit()

    # Bulk update
    from sqlalchemy import update
    stmt = (
        update(User)
        .where(User.active == False)
        .values(role="inactive")
    )
    session.execute(stmt)
    session.commit()

Delete

with Session(engine) as session:
    # Delete by object
    user = session.get(User, 1)
    session.delete(user)
    session.commit()

    # Bulk delete
    from sqlalchemy import delete
    stmt = delete(User).where(User.active == False)
    session.execute(stmt)
    session.commit()

Configuration

Alembic Migrations

# Install Alembic
pip install alembic

# Initialize
alembic init alembic

# Generate migration
alembic revision --autogenerate -m "create users table"

# Apply migrations
alembic upgrade head

# Rollback
alembic downgrade -1
# alembic/env.py
from app.models import Base
target_metadata = Base.metadata

Advanced Usage

Events

from sqlalchemy import event

@event.listens_for(User, "before_insert")
def set_defaults(mapper, connection, target):
    if not target.role:
        target.role = "user"

@event.listens_for(Session, "after_flush")
def after_flush(session, flush_context):
    for obj in session.new:
        print(f"Created: {obj}")

Hybrid Properties

from sqlalchemy.ext.hybrid import hybrid_property

class User(Base):
    __tablename__ = "users"
    id: Mapped[int] = mapped_column(primary_key=True)
    first_name: Mapped[str] = mapped_column(String(50))
    last_name: Mapped[str] = mapped_column(String(50))

    @hybrid_property
    def full_name(self) -> str:
        return f"{self.first_name} {self.last_name}"

    @full_name.expression
    @classmethod
    def full_name(cls):
        return cls.first_name + " " + cls.last_name

# Works in queries
stmt = select(User).where(User.full_name == "Alice Smith")

Raw SQL

from sqlalchemy import text

with Session(engine) as session:
    result = session.execute(
        text("SELECT * FROM users WHERE age > :age"),
        {"age": 18}
    )
    rows = result.fetchall()

Troubleshooting

ProblemSolution
DetachedInstanceErrorUse expire_on_commit=False or reload object in new session
N+1 queriesUse joinedload() or selectinload() for eager loading
IntegrityError on insertCheck unique constraints; handle with try/except
Session not committingCall session.commit() explicitly; check for exceptions
Stale dataUse session.refresh(obj) or session.expire(obj)
Slow queriesEnable echo=True on engine; add indexes to models
Migration autogenerate misses changesEnsure target_metadata points to your Base.metadata
Connection pool exhaustionIncrease pool_size; ensure sessions are properly closed