SQLAlchemy was just more expressive in previous years and was a requirement for projects that had more advanced data types and didn't want to use raw sql (or build their own ORM pieces).
Django is sealing its fate with the opposition to type annotations. I hope sqlc continues to grow on the Python side because it is wonderful in Go.
alembic is also much better than Django's migrations for the ability to expose the tree like structure with operation commands to manage it.
If Django would add some concept of project level migrations I would be much happier. When I build internal software I prefer one app to rule the domains so I have one set of migration history to manage. But everyone leans extra hard into over packaging into Django apps as a mechanism for name spacing domains and then cross app references / foreign keys make long term migration management a giant pain.
I prefer working with Django ORM too by a large margin. But I think it's more opiniated than SQLAlchemy, which may be why SQLAlchemy is considered the reference (well.... this and the fact that Django ORM is not a standalone lib). It's great if your use case fits to it but if not, SQLAlchemy probably gives you more adaptability.
But yes, Django ORM any day... or just no ORM at all.
But why? Although I have way more experience with Django ORM, I find that SQLAlchemy is closer to SQL and I have to think less how to express complex queries. With Django ORM changing a few characters can change resulting query from LEFT JOIN to INNER JOIN, for example. I find it more difficult to write complex queries in Django ORM.
Fun story: I once worked on a C piece of industrial software that was running on AIX 3.x to 6.x; the goal was to have a nicer (web) status monitoring UI as well as being able to implement stuff in a "safer" language and onboard more people.
The main problem was that the "database" was IBM C-ISAM. Think MySQL MyISAM tables, except don't even dream about SQL, you interact directly with the internal primitives through a C API; when you'r used to SQL it feels like bitbanging in ASM.
The plan:
- Write a Python binding to the C-ISAM library
- Write a subset of the Django ORM from scratch that would use the above behind the scenes; of course it's more limited but whatever's there must behave the same.
- Write any new software using that subset; slowly port over the old code to the new software; of course it can't use _everything_ that one would otherwise use in a normal app; but then again C-ISAM was so constrained that expectations were incredibly limited anyway from the very beginning.
- [much later] pivot! swap out the mock-Django models and drop in the real Django ORM (basically s/from mockdjango import/from django import/g) and hit some mysql/psql/whathaveyou that you've populated with the C-ISAM schema and data
- All the software written is all the merrier and Just Works; the world's your oyster.
This was made possible because the Django ORM is _incredibly simple_: the PoC was done in an afternoon, the hardest part being understanding Python meta classes.
Sad not to see SQL Server listed, if only because my first ever open source contribution was a patch to that driver for Django around 0.96 (the Internet seems to have no memory of it). I guess it’s not surprising as the fix was simply to correct a typo in the word “python” IIRC, so that spoke to the level of love it was going to see.
SQL Server was the only “real” database available to me at work at the time.
"My goal is to help you see quickly where each database works well and where it has some limits. I also hope this can be useful for anyone who wants to improve Django, or just understand it better."
OK, then I look at the feature matrix and see a lot of obvious errors, then I see:
"The data in the table below is entirely fictional and intentionally provided only for example!
I included these features just to show what the final matrix could look like, to help start a discussion in the community. Do not use them for any real analysis or decisions."
OK...so....we'll come back when you've written it? What are we looking at?
Is Django's ORM even supported outside Django sites?
I had a consulting gig that had a FastAPI website using Django's ORM and it produced a bunch of weird bugs.
Isn't Django the one embeds its logic into the names of keys in the arguments list? That might actually be worse than Laravel Eloquent's abuse of magic methods. Way back when I was doing Python, I was using SQLAlchemy, and I recall the design was much more sane.
I like the ORM but Django has stagnated in so so many ways.
Most of my startup friends basically use Ruby on Rails for their startup webap, and python microservices these days.
If you know python (hate ruby) and like javascript well enough FastAPI and javascript frontends seems way better.
I used to love the Django ORM when I didn't know any SQL. Then I had to learn SQL so that I could model data properly, and optimize access patterns.
These days I hate working with the ORM because it uses weird abstractions that make your life harder as you try to do more complicated stuff with your data. I had a small bug lately where a queryset would aggregate twice because I filtered an aggregated queryset, and this caused it to aggregate again on top of the previous result. I wouldn't have this bug if I was writing my own SQL, or if I used a query builder instead of an ORM. This is just a small example, I have many more annoying things that will cause me to use SQL directly instead of an ORM for my next project.
ORMs are one of those topics that get hotly debated for little reason IMO.
ORMs like almost everything else in SWE they are _tool_. It's not a law or a prescription. It's not mandatory.
ORMs are fine for 99% of cases. When it isn't fine use raw sql, no one is going to mock you, no one is going to jeer at you. Most times ORMs are fine, sometimes they are not.
The problem with ORMs is that they're a very leaky abstraction. Database performance matters a lot for any non-trivial application, and in order to use an ORM performantly you need to understand both the underlying SQL+database but also all of the nuances of how the ORM maps to SQL.
So basically you end up with two situations:
1. You need to hire engineers with double the expertise (not just a SQL engineer, but a SQL+ORM engineer).
1a. You hire a SQL engineer who now has to learn Yet Another ORM.
2. You hire engineers who only know the ORM but not SQL, and your app ends up having shit performance.
Basically, ORMs are simply complicated SQL generating macro frameworks. They are way too leaky to provide a useful level of abstraction like most programming languages.
LLM coding tools may displace ORMs, because they can take away a lot of the tedium with integrating application SQL, which is what ORMs are supposed to do.
I really, really love the Django ORM. We had some good times together, and we still occasional work together.
But at the end of the day, SQL is text. That's life. Sorry - i know it sucks.
I've fallen in love with lightweight frameworks like pypika.
Part of this is the fact that AI smoothes over the rough spots. I would hate to have do an extremely complex query - highly complex case statements with unusualy datatypes - in pypika without AI. But with AI it gets really easy.
More broadly, AI makes using low-level frameworks much more practical. Requests is on the way out, and the socket framework is back.
The Django ORM is still amazing, especially when it comes to speed and handling multiple different types of databases.
But if you want to do something like create soft deletes (including cascading), it get's really hard really fast.
21 comments
[ 3.6 ms ] story [ 45.3 ms ] threadDjango is sealing its fate with the opposition to type annotations. I hope sqlc continues to grow on the Python side because it is wonderful in Go.
alembic is also much better than Django's migrations for the ability to expose the tree like structure with operation commands to manage it.
If Django would add some concept of project level migrations I would be much happier. When I build internal software I prefer one app to rule the domains so I have one set of migration history to manage. But everyone leans extra hard into over packaging into Django apps as a mechanism for name spacing domains and then cross app references / foreign keys make long term migration management a giant pain.
But yes, Django ORM any day... or just no ORM at all.
The main problem was that the "database" was IBM C-ISAM. Think MySQL MyISAM tables, except don't even dream about SQL, you interact directly with the internal primitives through a C API; when you'r used to SQL it feels like bitbanging in ASM.
The plan:
- Write a Python binding to the C-ISAM library
- Write a subset of the Django ORM from scratch that would use the above behind the scenes; of course it's more limited but whatever's there must behave the same.
- Write any new software using that subset; slowly port over the old code to the new software; of course it can't use _everything_ that one would otherwise use in a normal app; but then again C-ISAM was so constrained that expectations were incredibly limited anyway from the very beginning.
- [much later] pivot! swap out the mock-Django models and drop in the real Django ORM (basically s/from mockdjango import/from django import/g) and hit some mysql/psql/whathaveyou that you've populated with the C-ISAM schema and data
- All the software written is all the merrier and Just Works; the world's your oyster.
This was made possible because the Django ORM is _incredibly simple_: the PoC was done in an afternoon, the hardest part being understanding Python meta classes.
SQL Server was the only “real” database available to me at work at the time.
"My goal is to help you see quickly where each database works well and where it has some limits. I also hope this can be useful for anyone who wants to improve Django, or just understand it better."
OK, then I look at the feature matrix and see a lot of obvious errors, then I see:
"The data in the table below is entirely fictional and intentionally provided only for example! I included these features just to show what the final matrix could look like, to help start a discussion in the community. Do not use them for any real analysis or decisions."
OK...so....we'll come back when you've written it? What are we looking at?
These days I hate working with the ORM because it uses weird abstractions that make your life harder as you try to do more complicated stuff with your data. I had a small bug lately where a queryset would aggregate twice because I filtered an aggregated queryset, and this caused it to aggregate again on top of the previous result. I wouldn't have this bug if I was writing my own SQL, or if I used a query builder instead of an ORM. This is just a small example, I have many more annoying things that will cause me to use SQL directly instead of an ORM for my next project.
ORMs like almost everything else in SWE they are _tool_. It's not a law or a prescription. It's not mandatory.
ORMs are fine for 99% of cases. When it isn't fine use raw sql, no one is going to mock you, no one is going to jeer at you. Most times ORMs are fine, sometimes they are not.
So basically you end up with two situations:
1. You need to hire engineers with double the expertise (not just a SQL engineer, but a SQL+ORM engineer).
1a. You hire a SQL engineer who now has to learn Yet Another ORM.
2. You hire engineers who only know the ORM but not SQL, and your app ends up having shit performance.
Basically, ORMs are simply complicated SQL generating macro frameworks. They are way too leaky to provide a useful level of abstraction like most programming languages.
LLM coding tools may displace ORMs, because they can take away a lot of the tedium with integrating application SQL, which is what ORMs are supposed to do.
But at the end of the day, SQL is text. That's life. Sorry - i know it sucks.
I've fallen in love with lightweight frameworks like pypika.
Part of this is the fact that AI smoothes over the rough spots. I would hate to have do an extremely complex query - highly complex case statements with unusualy datatypes - in pypika without AI. But with AI it gets really easy.
More broadly, AI makes using low-level frameworks much more practical. Requests is on the way out, and the socket framework is back.
The Django ORM is still amazing, especially when it comes to speed and handling multiple different types of databases.
But if you want to do something like create soft deletes (including cascading), it get's really hard really fast.