SQLAlchemy 2.0 ORM
Modern SQLAlchemy 2.x patterns: models, sessions, relationships, queries.
pythondatabaseorm
# SQLAlchemy 2.0 ORM
## Setup
```py
from sqlalchemy import create_engine, ForeignKey, String
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
engine = create_engine('postgresql+psycopg://user:pw@host/db', echo=True)
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)
posts: Mapped[list['Post']] = relationship(back_populates='author', cascade='all, delete-orphan')
class Post(Base):
__tablename__ = 'posts'
id: Mapped[int] = mapped_column(primary_key=True)
title: Mapped[str]
user_id: Mapped[int] = mapped_column(ForeignKey('users.id'))
author: Mapped[User] = relationship(back_populates='posts')
Base.metadata.create_all(engine)
```
## Sessions
```py
with Session(engine) as s, s.begin():
s.add(User(email='a@b.c'))
# commit on context exit
```
## Querying (2.0 style)
```py
from sqlalchemy import select
stmt = select(User).where(User.email == 'a@b.c')
user = s.scalars(stmt).first()
stmt = select(User).join(User.posts).where(Post.title.ilike('%hello%'))
users = s.scalars(stmt).all()
```
## Eager loading (avoid N+1)
```py
from sqlalchemy.orm import selectinload, joinedload
select(User).options(selectinload(User.posts))
```
## Update / delete
```py
from sqlalchemy import update, delete
s.execute(update(User).where(User.id==1).values(email='x@y.z'))
s.execute(delete(Post).where(Post.user_id==1))
```
## Migrations
Use Alembic: `alembic init migrations`, `alembic revision --autogenerate -m "msg"`, `alembic upgrade head`.
API: /api/skills/sqlalchemy-orm