Modern database toolkit for Python. Two layers: Core (SQL expression language) and ORM (object-relational mapper). SQLAlchemy 2.0+ uses typed mappings, select() style queries, and full async support.
Installation
uv add sqlalchemy
uv add alembic # database migrations
uv add asyncpg # async PostgreSQL driver
uv add psycopg2-binary # sync PostgreSQL driver
uv add aiosqlite # async SQLite driver (dev/testing)
When to Use What
| Layer |
Best for |
ORM (Mapped, Session) |
CRUD, domain models, relationships, identity tracking |
Core (select, insert) |
Complex queries, bulk operations, reports |
text() |
Raw SQL when ORM/Core is overkill |
Async (AsyncSession) |
FastAPI, async web frameworks, high-concurrency apps |
Section Map
| File |
Topics |
| 01 Engine & Models |
Engine, DeclarativeBase, Mapped, mapped_column, column types |
| 02 Relationships & Queries |
One-to-many, many-to-many, CRUD, select, join, filtering |
| 03 Sessions & Transactions |
Session lifecycle, transactions, unit of work, context managers |
| 04 Async Patterns |
AsyncEngine, AsyncSession, loading strategies, FastAPI integration |
| 05 Alembic Migrations |
Setup, auto-generate, operations, versioning, async migrations |
| 06 Advanced Recipes |
Hybrid properties, events, connection pooling, testing, performance |
Quick Start
from sqlalchemy import create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[int] = mapped_column(primary_key=True)
name: Mapped[str] = mapped_column()
email: Mapped[str] = mapped_column(unique=True)
engine = create_engine("sqlite:///app.db", echo=True)
Base.metadata.create_all(engine)
with Session(engine) as session:
session.add(User(name="Alice", email="alice@example.com"))
session.commit()
stmt = select(User).where(User.name == "Alice")
user = session.scalars(stmt).one()
print(user.email)
Common Column Types
| Python type |
SQLAlchemy type |
DB type |
int |
Integer |
INTEGER |
str |
String(n) |
VARCHAR(n) |
str (unbounded) |
Text |
TEXT |
float |
Float |
FLOAT |
bool |
Boolean |
BOOLEAN |
datetime |
DateTime |
TIMESTAMP |
date |
Date |
DATE |
Decimal |
Numeric(p, s) |
NUMERIC |
bytes |
LargeBinary |
BLOB |
uuid.UUID |
Uuid |
UUID (PG) / CHAR(32) |
dict / list |
JSON |
JSON / JSONB |
Quick Rules
- Use 2.0 style —
Mapped[], mapped_column(), select().
- Keep sessions short-lived — one per request or unit of work.
- Never share sessions across threads — use
scoped_session or async.
- Use
selectinload/joinedload — avoid N+1 queries.
- Alembic for migrations — never use
metadata.create_all() in production.
- Use connection pooling defaults — tune
pool_size and max_overflow for load.
- Prefer
session.scalars() — returns ORM objects directly, cleaner than .execute().scalars().
See also