Working with SQLAlchemy 2.x data layers#
_SQLAlchemy changelog states:
> Loader options no longer accept strings for attribute names. The long-documented
> approach of using Class.attrname
for loader option targets is now standard.
Assuming following classes:
class Person(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String)
class Computer(db.Model):
id = db.Column(db.Integer, primary_key=True)
person_id = db.Column(db.Integer, db.ForeignKey('person.id'))
person = db.relationship('Person', backref=db.backref('computers'))
Then in SQLAlchemy 1.x, following expressions both worked:
import sqlalchemy as sa
from sqlalchemy import orm
query = sa.select(Computer).options(orm.joinedload("person"))
query = sa.select(Computer).options(orm.joinedload(Computer.person))
In SQLAlchemy 2.x, only this works:
import sqlalchemy as sa
from sqlalchemy import orm
query = sa.select(Computer).options(orm.joinedload(Computer.person))
flask-rest-jsonapi-next
data layer is using orm.joinedload()
to eagerly load
objects for requests with ?include
URL parameter and thus tries to prevent N + 1
query problems for these requests.
To do that, it relies on marshmallow schema for given resource and ability of SQLAlchemy
to place “random” strings it gets as argument of orm.joinedload()
into proper
context of mapped entity. This is no longer possible without having more context in
schema itself.
In short assuming we have following schema in our code:
class ComputerSchema(Schema):
class Meta:
type_ = 'computer'
# ...
id = fields.Integer(as_string=True, dump_only=True)
person = Relationship(...)
And we make following request:
GET /api/computers?include=person
Everything except eager loading will still work and we will get expected response. But,
in contrary to app that is using SQLAlchemy 1.x data layer, above request will generate
additional SQL query for each returned computer
.
Also, flask-rest-jsonapi-next
will emit helpful warning:
FlaskRestJsonApiNextWarning: When using SQLAlchemy 2.x, resource schema classes must
have 'SchemaClass.Meta.model' attribute. Without this, eager loading of included
objects can't work and is disabled. Without eager loading, app may run into N + 1
query problem which will affect performance. Warning for:
<class 'may.app.package.ComputerSchema'>.person
To get eager loading back, each schema in your codebase must add Meta.model
attribute like this:
class ComputerSchema(Schema):
class Meta:
type_ = 'computer'
# ...
# Following is required for SQLAlchemy 2.x eager loading
model = Computer
id = fields.Integer(as_string=True, dump_only=True)
person = Relationship(...)