yes so, SQLAlchemy 2.0's approach is frankly at odds with the spirit of Flask-SQLAlchemy. The Query and "dynamic" loaders are staying around largely so that Flask can come on board, however the patterns in F-S are pretty much the ones I want to get away from.
2.0's spirit is one where the act of creating a SELECT statement is a standalone thing that is separate from being attached to any specific class (really all of SQLAlchemy was like this, but F-S has everyone doing the Model.query thing that I've always found to be more misleading than helpful), but SELECT statements are now also disconnected from any kind of "engine" or "Session" when constructed.
as for with_parent(), with_parent is what the dynamic loader actually uses to create the query. so this is a matter of code organization.
F-S would have you say:
user = User.query.filter_by(name='name').first()
address = user.addresses.filter_by(email='email').first()
SQLAlchemy 2.0 would have you say instead:
with Session(engine) as session:
user = session.execute(
select(User).filter_by(name='name')
).scalars().first()
address = session.execute(
select(Address).where(with_parent(user, Address.user)).filter_by(email='email')
).scalars().first()
Noting above, a web framework integration may still wish to provide the "session" to data-oriented methods and manage its scope, but IMO it should be an explicit object passed around. The database connection / transaction shouldn't be made to appear to be inside the ORM model object, since that's not what's actually going on.
If you look at any commentary anywhere about SQLAlchemy, the top complaints are:
1. too magical, too implicit
2. what's wrong with just writing SQL?
SQLAlchemy 2.0 seeks to streamline the act of ORMing such that the user *is* writing SQL, they're running it into an execute() method, and they are managing the scope of connectivity and transactions in an obvious way. People don't necessarily want bloat and verbosity but they do want to see explicitness when the computer is being told to do something, especially running a SQL query. We're trying to hit that balance as closely as possible.
The above style also has in mind compatibility with asyncio, which we now support. With asyncio, it's very important that the boundary where IO occurs is very obvious. Hence the Session.execute() method now becomes the place where users have to "yield". With the older Query interface, the "yields" would be all over the place and kind of arbirary, since some Query methods decide to execute at one point or another.
Flask-SQLAlchemy therefore has to decide where it wants to go with this direction, and there are options, including sticking with the legacy query / dynamic loader, perhaps vendoring a new interface that behaves in the flask-sqlalchemy style but uses 2.0-style patterns under the hood, or it can go along with the 2.0 model for future releases. From SQLAlchemy's point of view, the Query was always not well thought out and was inconsistent with how Core worked, and I've wanted for years to resolve that problem.
- mike