Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SQLAlchemy: Hybrid expression with relationship

I have two Flask-SQLAlchemy models with a simple one-to-many relationship, like the minimal example below:

class School(db.Model):     id = db.Column(db.Integer, primary_key=True)     name = db.Column(db.String(30))     address = db.Column(db.String(30))  class Teacher(db.Model):     id = db.Column(db.Integer, primary_key=True)     name = db.Column(db.String(30))     id_school = db.Column(db.Integer, db.ForeignKey(School.id))      school = relationship('School', backref='teachers') 

Then I add an hybrid property to teacher that uses the relationship, like so:

@hybrid_property def school_name(self):     return self.school.name 

And that property works just fine when I use it as teacher_instance.school_name. However, I'd also like to make queries like Teacher.query.filter(Teacher.school_name == 'x'), but that gives me an error:

`AttributeError: Neither 'InstrumentedAttribute' object nor  'Comparator' object has an attribute 'school_name'`.  

Following SQLAlchemy documentation, I added a simple hybrid expression, like the following:

@school_name.expression def school_name(cls):     return School.name 

However, when I try the same query again, it generates an SQL query without the join clause, so I get all available rows in School, not only those matching the foreign key in Teacher.

From SQLAlchemy documentation I realized that the expression expects a context where the join is already present, so I tried the query again as:

Teacher.query.join(School).filter(Teacher.school_name == 'x')

And that actually works, but it defeats the purpose of trying to get the syntactic sugar in there in the first place if I need knowledge of the School model to get that. I expect there's a way to get that join in the expression, but I couldn't find it anywhere. The documentation has an example with the expression returning a subquery built directly with the select(), but even that didn't work for me.

Any ideas?

UPDATE

After Eevee's answer below, I used the association proxy as suggested and it works, but I also got curious with the comment that it should work with the select() subquery and tried to figure out what I did wrong. My original attempt was:

@school_name.expression def school_name(cls):     return select(School.name).where(cls.id_school == School.id).as_scalar() 

And it turns out that was giving me an error because I had missed the list in select(). The code below works fine:

@school_name.expression def school_name(cls):     return select([School.name]).where(cls.id_school == School.id).as_scalar() 
like image 224
Pedro Werneck Avatar asked Nov 05 '13 00:11

Pedro Werneck


1 Answers

A much simpler approach for a simple case like this is an association proxy:

class Teacher(db.Model):     school_name = associationproxy('school', 'name') 

This supports querying (at least with ==) automatically.

I'm curious how the hybrid select() example didn't work for you, since that's the easiest way to fix this within a hybrid. And for the sake of completion, you could also use a transformer to amend the query directly rather than subquerying.

like image 64
Eevee Avatar answered Sep 27 '22 20:09

Eevee