ScriptsApr 16, 2026·3 min read

SQLAlchemy — The Python SQL Toolkit and ORM

The most widely used Python database library, providing both a low-level SQL expression language and a high-level ORM with unit of work, identity map, and eager loading.

TL;DR
SQLAlchemy provides both raw SQL expression building and a full ORM for Python database access.
§01

What it is

SQLAlchemy is the most widely used Python database library. It provides two distinct layers: a Core layer with a SQL expression language for building queries programmatically, and an ORM layer with unit of work pattern, identity map, eager/lazy loading, and relationship management. It supports PostgreSQL, MySQL, SQLite, Oracle, and SQL Server through pluggable dialects.

SQLAlchemy targets Python developers who need database access ranging from simple queries to complex multi-table ORM patterns. It is the default choice for Flask, FastAPI, and many other Python web frameworks.

§02

How it saves time or tokens

SQLAlchemy eliminates hand-written SQL string concatenation and the security risks it creates. The ORM automates object-relational mapping, change tracking, and relationship loading. The unit of work pattern batches database operations for efficiency. For developers using AI coding assistants, SQLAlchemy's well-documented API means generated code is more likely to be correct on the first attempt compared to raw SQL strings.

§03

How to use

  1. Install SQLAlchemy with a database driver:
pip install sqlalchemy psycopg2-binary
  1. Connect and run a query with Core:
from sqlalchemy import create_engine, text

engine = create_engine('postgresql://user:pass@localhost/mydb')
with engine.connect() as conn:
    result = conn.execute(text('SELECT 1'))
    print(result.scalar())
  1. Define an ORM model and query:
from sqlalchemy.orm import DeclarativeBase, Session, Mapped, mapped_column
from sqlalchemy import String

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(200))

with Session(engine) as session:
    users = session.query(User).filter_by(name='Alice').all()
§04

Example

from sqlalchemy import create_engine, ForeignKey
from sqlalchemy.orm import (
    DeclarativeBase, Session, Mapped,
    mapped_column, relationship
)

class Base(DeclarativeBase):
    pass

class Author(Base):
    __tablename__ = 'authors'
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str] = mapped_column()
    books: Mapped[list['Book']] = relationship(back_populates='author')

class Book(Base):
    __tablename__ = 'books'
    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str] = mapped_column()
    author_id: Mapped[int] = mapped_column(ForeignKey('authors.id'))
    author: Mapped['Author'] = relationship(back_populates='books')

engine = create_engine('sqlite:///library.db')
Base.metadata.create_all(engine)

with Session(engine) as session:
    author = Author(name='Guido', books=[Book(title='Python Intro')])
    session.add(author)
    session.commit()
§05

Related on TokRepo

§06

Common pitfalls

  • SQLAlchemy 2.0 introduced a new query style using session.execute(select(...)) instead of session.query(Model); mixing both styles creates confusing code
  • Lazy loading relationships inside loops causes N+1 query problems; use joinedload() or selectinload() for bulk operations
  • Forgetting to call session.commit() after modifications -- the unit of work pattern tracks changes but does not persist them until explicitly committed

Frequently Asked Questions

Should I use SQLAlchemy Core or ORM?+

Use Core when you need precise control over SQL queries, such as complex joins, aggregations, or bulk operations. Use ORM when you want to work with Python objects and let SQLAlchemy handle the SQL generation, change tracking, and relationship management. Many projects use both layers together.

How does SQLAlchemy 2.0 differ from 1.x?+

SQLAlchemy 2.0 introduced a new declarative mapping style using Mapped[] type annotations, a unified select() query interface, and stricter typing throughout. The session.query() API still works but is considered legacy. New projects should use the 2.0 style exclusively.

Does SQLAlchemy support async?+

Yes. SQLAlchemy 2.0 provides an async extension with AsyncSession, AsyncEngine, and async-compatible drivers like asyncpg for PostgreSQL and aiosqlite for SQLite. This integrates with FastAPI and other async Python frameworks.

How do I handle database migrations with SQLAlchemy?+

Use Alembic, the official migration tool by the same author. Alembic auto-generates migration scripts by comparing your SQLAlchemy models against the current database schema. It supports both upgrade and downgrade operations.

Can SQLAlchemy work with multiple databases simultaneously?+

Yes. Create separate Engine instances for each database and use the session.bind parameter or bind_mapper() to route queries to the correct database. This is common in read-replica setups where writes go to a primary and reads to replicas.

Citations (3)

Discussion

Sign in to join the discussion.
No comments yet. Be the first to share your thoughts.

Related Assets