40% LOC != 40% of the value, which is what your comment stated.
My most recent experience of needing to hand-craft queries because SQLAlchemy was doing something truly hilarious was on 1.4.40-something from 2022 (I think possibly .41 or .42?).
I don't have the specifics in front of me as it was for a prior job but rough outline of what I observed:
A simple select including several related tables with filters on each table resulted in an individual subquery for each *filter* being applied. The resulting SQL was incredibly large, selected data in a poor way, and when replaced with a simplified single query that let the DB do its thing sped up about 100x.
Those days may be gone now with 2.0 but I don't consider barely a year ago "long gone".
I think there's a great place for Create/Update/Delete, and generally when doing trivial Reads. But the lesson I've learned with basically every ORM is that once a third table becomes involved you probably want to write your own SQL because they'll break in some weird way.
This is essentially how I use SQLAlchemy in current projects and it's been great in that regard.