Skip to content

Why Python Developers Are Frustrated with SQLAlchemy (and What to Use Instead)

Problem

SQLAlchemy is the de facto Python ORM, but many developers feel something is off with it. The r/Python thread “Are we happy with SQLAlchemy?” captures this sentiment well — even the OP, who is building a sqlc-equivalent for Python, said “SQLAlchemy is the best option we have in the Python ecosystem, but it still sucks compared to ORMs in other ecosystems.”

I’ve had this feeling too. Let me dig into what specifically frustrates developers and what alternatives actually exist.

The Three Frustrations

Reading through the Reddit thread, three pain points keep surfacing.

1. Complexity Overhead

SQLAlchemy has a two-tier architecture: Core and ORM. Newcomers import sqlalchemy.orm by default, struggle with session management and the “unit of work” pattern, and never realize Core exists as a simpler alternative.

One commenter said: “It’s so much more complicated than what I’ve used in other languages, like Entity Framework.”

2. Barely an Abstraction

Some developers feel SQLAlchemy is more like a thin dialect-normalization layer than a real ORM. It maps closely to SQL concepts but doesn’t hide SQL details the way Django ORM does.

The most positive review in the thread comes with a caveat: SQLAlchemy is “the best ORM I’ve used” — with the key reason being that Core lets you avoid the ORM layer entirely.

3. Dynamic Typing Issues

Python’s lack of compile-time type safety means ORM errors surface at runtime. In Go with sqlc, you get compile-time SQL validation. In Java with Hibernate, you get IDE support. In Python, a typo in a column name only shows up when you run the query.

What to Use Instead

Raw SQL + Connection Pool

The “just write raw queries” camp has multiple proponents in the thread. If you already know SQL, why add another layer?

Raw SQL with psycopg2
import psycopg2
conn = psycopg2.connect("dbname=test user=postgres")
with conn.cursor() as cur:
cur.execute("SELECT id, title, pub_date FROM blogs WHERE pub_date > %s", ("2026-01-01",))
rows = cur.fetchall()

This is simple, transparent, and fast. The downside: no query builder, no object tracking, and you manually handle result mapping.

Pydantic + Raw SQL

A middle ground: define models as Pydantic schemas, write raw SQL, and validate results.

Pydantic + raw SQL
from pydantic import BaseModel
import psycopg2
class Blog(BaseModel):
id: int
title: str
pub_date: str
with conn.cursor() as cur:
cur.execute("SELECT id, title, pub_date FROM blogs")
blogs = [Blog(**row) for row in cur.fetchall()]

This gives you type validation at the boundary without ORM overhead.

Django ORM

If you’re already in the Django ecosystem, its ORM is widely considered more ergonomic. The tradeoff is framework coupling.

Code-Generated SQL (Emerging)

The most interesting direction from the thread: code-generated SQL tools inspired by Go’s sqlc. The OP is building one using sqlglot that generates type-safe Python from SQL files, with support for dynamic filters and sorting.

Code-generated SQL concept
-- query.sql
-- name: get_blogs_by_date :many
SELECT * FROM blogs WHERE pub_date >= :min_date ORDER BY pub_date DESC;
# Generated Python (idea)
# blogs = get_blogs_by_date(engine, min_date="2026-01-01")

This approach combines the safety of ORMs with the transparency of raw SQL. It’s still emerging in the Python ecosystem, but it’s worth watching.

A Decision Framework

Here’s how I think about the choice:

Decision framework
Are you using Django? → Django ORM
Do you need SQL control? → SQLAlchemy Core
Do you hate abstractions? → Raw SQL
Want type safety? → Watch for code-gen tools

Flowchart decision tree: Using Django leads to Django ORM, needing SQL control leads to SQLAlchemy Core, hating abstractions leads to raw SQL, wanting type safety points to code-gen tools

For most projects, SQLAlchemy (especially Core mode) is still the most balanced choice. It gives you enough abstraction to be productive without hiding what SQL is actually being executed.

Summary

In this post, I explored why Python developers get frustrated with SQLAlchemy — complexity overhead, thin abstraction, and dynamic typing issues — and reviewed alternatives including raw SQL, Django ORM, and emerging code-generated SQL tools. The key point is that SQLAlchemy is still the most balanced choice for most projects, but knowing your alternatives helps you make smarter decisions.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments