I am trying to (programmatically) create partial views of existing SQLAlchemy models, using a metaclass.
A streamlined example, trying to get a subquery view on a model Foo with a relationship bar, would look something like that:
class Bar(db.Model):
id = db.Column(db.Integer, primary_key=True)
class Foo(db.Model):
id = db.Column(db.Integer, primary_key=True)
bar_id = db.Column(db.Integer, db.ForeignKey(
"bar.id"))
bar = db.relationship(Bar, backref='foos')
class FooViewMeta(type(db.Model)):
def __new__(mcs, name, bases, attrs):
attrs["__table__"] = (select(Foo)
.with_only_columns(Foo.id, Foo.bar_id, Foo.bar)
.subquery())
attrs["bar"] = Foo.__mapper__.relationships["bar"]
attrs["__mapper_args__"] = {"exclude_properties": ["bar"]}
return super().__new__(mcs, name, (db.Model,), attrs)
class FooView(metaclass=FooViewMeta):
pass
(Omitting exclude_properties gives me a warning about conflicting columns)
I am able to load FooView and access its bar property fine.
Unfortunately, if I run:
q = Foo.query.options(joinedload(Foo.bar),)
q.first()
I get the following error, which tells me that this is not a correct way to duplicate the mapper for that relationship:
<class 'sqlalchemy.exc.ArgumentError'>: Mapped class Mapper[FooView(anon_1)] does
not apply to any of the root entities in this query, e.g. Mapper[Foo(foo)].
Please specify the full path from one of the root entities to the target attribute.
Clearly, the re-use of this relationship is confusing the mapper, who tries to use the newer subquery model and fails.
Conversely, if I use:
attrs["bar"] = getattr(Foo, "bar")
The code works for 1-1 relationship, but fails for lazy 1-many…
Is there a clean way to (programmatically) duplicate all relationship from an existing model, over to a new model (that targets the same table and selects a subset of columns as a subquery)?
Note: unfortunately, I am not looking for polymorphic subclassing here (the "view" classes are a subset of columns, not rows), so it does not seem like I can use any of SQLAlchemy mapper options for that…