Skilllibrary orm-patterns
install
source · Clone the upstream repo
git clone https://github.com/merceralex397-collab/skilllibrary
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/merceralex397-collab/skilllibrary "$T" && mkdir -p ~/.claude/skills && cp -r "$T/09-backend-api-and-data/orm-patterns" ~/.claude/skills/merceralex397-collab-skilllibrary-orm-patterns && rm -rf "$T"
manifest:
09-backend-api-and-data/orm-patterns/SKILL.mdsource content
Purpose
Write correct SQLAlchemy 2.0 queries with proper relationship loading, N+1 prevention, and session management.
When to use this skill
- writing SQLAlchemy 2.0 models with
andmapped_column
typesMapped - fixing N+1 query problems with eager loading strategies
- choosing between
,joinedload
, andselectinloadsubqueryload - migrating from SQLAlchemy 1.x to 2.0 style
Do not use this skill when
- working with BigQuery — prefer
bigquery - writing raw SQL without an ORM
- using a non-Python ORM (Prisma, TypeORM, GORM)
Procedure
- Define models with 2.0 syntax — use
,Mapped[type]
, andmapped_column()
with type annotations.relationship() - Detect N+1 queries — enable
on engine or use SQLAlchemy event listeners to count queries per request.echo=True - Choose loading strategy —
for one-to-one/many-to-one;joinedload
for one-to-many;selectinload
for deep nesting.subqueryload - Use
overselect()
— 2.0 style:query()
notsession.execute(select(User).where(...))
.session.query(User).filter(...) - Manage sessions — use
orasync_sessionmaker
with context manager; never share sessions across threads.sessionmaker - Add indexes — annotate columns with
; create composite indexes viaindex=True
.__table_args__ - Write migrations — use Alembic
to detect model changes; always review generated SQL.--autogenerate - Test queries — assert query count in tests using
event counters.connection.execute(text("SELECT ..."))
Model example (2.0 style)
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from sqlalchemy import ForeignKey, String from datetime import datetime class Base(DeclarativeBase): pass class User(Base): __tablename__ = "users" id: Mapped[int] = mapped_column(primary_key=True) email: Mapped[str] = mapped_column(String(255), unique=True, index=True) created_at: Mapped[datetime] = mapped_column(default=datetime.utcnow) posts: Mapped[list["Post"]] = relationship(back_populates="author") class Post(Base): __tablename__ = "posts" id: Mapped[int] = mapped_column(primary_key=True) title: Mapped[str] = mapped_column(String(500)) author_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True) author: Mapped["User"] = relationship(back_populates="posts")
Loading strategies
from sqlalchemy.orm import joinedload, selectinload from sqlalchemy import select # One-to-many: use selectinload (2 queries, no cartesian product) stmt = select(User).options(selectinload(User.posts)).where(User.id == 1) # Many-to-one: use joinedload (single JOIN) stmt = select(Post).options(joinedload(Post.author)).limit(20) # Nested: chain strategies stmt = select(User).options( selectinload(User.posts).joinedload(Post.tags) )
Decision rules
- Default to
for collections — avoids cartesian product explosion.selectinload - Use
only for single-object relationships (many-to-one, one-to-one).joinedload - Never access lazy-loaded relationships outside an active session — use eager loading or
.expire_on_commit=False - Use
for all columns — it provides type checking and IDE support.Mapped[type] - Run
in CI to ensure models and migrations stay in sync.alembic check
References
- https://docs.sqlalchemy.org/en/20/orm/quickstart.html
- https://docs.sqlalchemy.org/en/20/orm/queryguide/relationships.html
- https://alembic.sqlalchemy.org/
Related skills
— analytics SQL patternsbigquery