Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"stale association proxy, parent object has gone out of scope" with Flask-SQLAlchemy

I've actually never encountered this error before:

sqlalchemy.exc.InvalidRequestError: stale association proxy, parent object has gone out of scope

After doing some research, it looks like its because the parent object is being garbage collected while the association proxy is working. Fantastic.

However, I'm not sure where it's happening.

Relevant code:

# models.py

class Artist(db.Model):
    # ...
    tags = association_proxy('_tags', 'tag', 
        creator=lambda t: ArtistTag(tag=t))
    # ...

class Tag(db.Model):
    # ...
    artist = association_proxy('_artists', 'artist', 
        creator=lambda a: ArtistTag(artist=a))
    # ...

class ArtistTag(db.Model):
    # ...
    artist_id = db.Column(db.Integer, ForeignKey('artists.id'))
    artist = db.relationship('Artist', backref='_tags')
    tag_id = db.Column(db.Integer, ForeignKey('tags.id'))
    tag = db.relationship('Tag', backref='_artists')

# api/tag.py
from flask.ext.restful import Resource
from ..
class ListArtistTag(Resource):
    def get(self, id):
        # much safer in actual app
        return TagSchema(many=True)
               .dump(Artist.query.get(id).tags)
               .data
like image 642
justanr Avatar asked May 05 '15 04:05

justanr


1 Answers

I know it's an old question, but I haven't found a clear solution to a similar problem anywhere on the web, so I've decided to reply here.

The key here is to assign the object that holds the association proxy to a variable before performing any further operations on them. Association proxies aren't regular object properties which would force the GC to hold the reference to the parent object. Actually, the call in form of:

tags = association_proxy('_tags', 'tag', creator=lambda t: ArtistTag(tag=t))

will result in creation of a new AssociationProxy class object, with a weak reference to the target's collection. In low memory conditions, GC may try to collect Artist.query.get(id) result, leaving just the result's tags collection (being a AssociationProxy class object), but it's required that the object having a association proxy to be present, due to SQLAlchemy's implementation (lazy loading mechanism precisely, I believe).

To fix this situation, we need to make sure that the Artist object returned from Artist.query.get(id) call is assigned to a variable, so that the reference count to that object is explicitly of non-zero value. So this:

class ListArtistTag(Resource):
    def get(self, id):
        # much safer in actual app
        return TagSchema(many=True)
               .dump(Artist.query.get(id).tags)
               .data

becomes this:

class ListArtistTag(Resource):
    def get(self, id):
        artist = Artist.query.get(id)
        return TagSchema(many=True)
               .dump(artist.tags)
               .data

And it will work as expected. Simple, right?

like image 115
synweap15 Avatar answered Nov 15 '22 00:11

synweap15