[Sqlalchemy-tickets] Issue #4268: AssociationProxy becomes useless when parent goes out of scope (z
Brought to you by:
zzzeek
From: Chris W. <iss...@bi...> - 2018-06-01 08:09:23
|
New issue 4268: AssociationProxy becomes useless when parent goes out of scope https://bitbucket.org/zzzeek/sqlalchemy/issues/4268/associationproxy-becomes-useless-when Chris Wilson: This fails: ``` #!python from sqlalchemy import Column, ForeignKey, Integer, Table, Text, create_engine from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relationship, sessionmaker Base = declarative_base() userkeywords_table = Table('userkeywords', Base.metadata, Column('user_id', Integer, ForeignKey("user.id"), primary_key=True), Column('keyword_id', Integer, ForeignKey("keyword.id"), primary_key=True) ) class User(Base): __tablename__ = 'user' id = Column(Integer, primary_key=True) name = Column(Text) kw = relationship("Keyword", secondary=lambda: userkeywords_table) def __init__(self, name): self.name = name # proxy the 'keyword' attribute from the 'kw' relationship keywords = association_proxy('kw', 'keyword') class Keyword(Base): __tablename__ = 'keyword' id = Column(Integer, primary_key=True) keyword = Column('keyword', Text) def __init__(self, keyword): self.keyword = keyword engine = create_engine('sqlite://') engine.echo = True Base.metadata.create_all(engine) DBSession = sessionmaker(bind=engine) session = DBSession(autocommit=True) with session.begin(): user = User('jek') user.keywords.append('cheese inspector') session.add(user) user = None with session.begin(): print(session.query(User).one().keywords) ``` Because User is garbage-collected before the association can be read: ``` File untitled0.py, line 60, in : print(session.query(User).one().keywords) File ...\sqlalchemy-1.2.7-py3.6-win-amd64.egg\sqlalchemy\ext\associationproxy.py, line 780, in __repr__ : return repr(list(self)) File ...\sqlalchemy-1.2.7-py3.6-win-amd64.egg\sqlalchemy\ext\associationproxy.py, line 583, in __len__ : return len(self.col) File ...\sqlalchemy-1.2.7-py3.6-win-amd64.egg\sqlalchemy\ext\associationproxy.py, line 580, in : col = property(lambda self: self.lazy_collection()) File ...\sqlalchemy-1.2.7-py3.6-win-amd64.egg\sqlalchemy\ext\associationproxy.py, line 536, in __call__ : "stale association proxy, parent object has gone out of " sqlalchemy.exc.InvalidRequestError: stale association proxy, parent object has gone out of scope ``` Whereas if you forcibly keep a reference in a variable, then it works: ``` #!python user = session.query(User).one() print(user.keywords) ``` Other people have run into this issue as well: * https://stackoverflow.com/questions/30044069/stale-association-proxy-parent-object-has-gone-out-of-scope-with-flask-sqlalc * https://groups.google.com/forum/#!topic/sqlalchemy/b-ams8tgcDU The latter has some explanation as to why we only keep a weakref: > As a proxy, it doesn't actually store the collection that > it proxies, because that collection itself might not have been loaded > yet, or can be expired, replaced, etc. and it's just less complicated > for it to look at whatever collection is currently on the parent. > > If the parent object is lost, and you still are pointing to the proxied > collection, it can't give you the collection anymore. The association > proxy stores only a weak reference to the parent... I would guess that's > just to prevent reference cycles; I tried adding a strong ref and of > course all the tests still pass, so I'm not totally sure why that was so > important here, but there you go. Preventing reference cycles doesn't seem like a good reason to have such counter-intuitive behaviour. Also we may not need to hold a strong reference to the underlying *association*. A strong reference to the *parent* of that association should be enough to prevent it from going out of scope, keeping the association object accessible/constructable. |