I understand, I had a feeling it would be something like that. Don't worry, I can work around it by using a subqueryload() instead (which I guess fixes it by changing the order things are loaded?)
Thank you very much for taking the time to answer; still loving SQLAlchemy! On Tue, Mar 25, 2014 at 1:06 PM, Michael Bayer <mike...@zzzcomputing.com>wrote: > > On Mar 25, 2014, at 7:48 AM, Philip Scott <safetyfirstp...@gmail.com> > wrote: > > Ah, so, it turns out to be more subtle than I first thought. It took me > quite a while to narrow it down to an easily reproducible case. To trigger > the behavior you need to be: joinedloading() along a backref, and also I > think it matters that I am joining back onto the same table and returning a > bunch of objects. Quite a corner case I think. > > This code demonstrates the behavior - it issues a second query in the for > loop for the 'A' which has no child. > > > > this is very difficult to resolve and it may have been something I've said > was unfixable in the past. The "a2" object here is loaded in two different > contexts, one is as the joined loaded child of a1, the other as a first > class result. Because when the query orders by a1, a2, we hit a1 first, > a2 is necessarily loaded as the child of a1. The joined loading only goes > one level deep, that is, it doesn't load the children of children, unless > you told it to by saying joinedload("child").joinedload("child"). So > a2.child's loader is declared as "not loaded". then on the next row it > comes in as a first class result, but the a2 object we get there is only an > identity map lookup - this object is already loaded. > > Adjusting this behavior would require the loading logic figure out > mid-results that the context for a particular object is changing. pretty > complicated. > > > > > from sqlalchemy import * > from sqlalchemy.orm import * > from sqlalchemy.ext.declarative import declarative_base > > Base = declarative_base() > > class A(Base): > __tablename__ = 'a' > > id = Column(Integer, primary_key=True) > > join_table = Table('parent_child', Base.metadata, > Column('id_a', ForeignKey('a.id')), > Column('id_b', ForeignKey('a.id'))) > > parent = relationship("A", > secondary=join_table, > primaryjoin = (id == join_table.c.id_a), > secondaryjoin = (id == join_table.c.id_b), > uselist=False, > backref=backref("child", uselist=False)) > > e = create_engine("sqlite://", echo=True) > Base.metadata.create_all(e) > > sess = Session(e) > a1 = A() > a2 = A(parent=a1) > sess.add(a1) > sess.add(a2) > sess.commit() > sess.close() > > results = sess.query(A).options(joinedload("child")).all() > print "----" > for a in results: > print a.child is None > > > > > > On Mon, Mar 24, 2014 at 8:34 PM, Michael Bayer > <mike...@zzzcomputing.com>wrote: > >> >> On Mar 24, 2014, at 2:09 PM, Philip Scott <safetyfirstp...@gmail.com> >> wrote: >> >> > >> > Is this a bug, or perhaps some expected side effect of the joined load? >> >> seemed like something that might be possible but the scalar loader is >> initializing the attribute to None to start with, here's a simple test that >> doesn't show your behavior, so see if you can just modify this one to show >> what you are seeing. note we only need to see that 'bs' is in a1.__dict__ >> to prevent a lazyload. >> >> from sqlalchemy import * >> from sqlalchemy.orm import * >> from sqlalchemy.ext.declarative import declarative_base >> >> Base = declarative_base() >> >> class A(Base): >> __tablename__ = 'a' >> >> id = Column(Integer, primary_key=True) >> bs = relationship("B", secondary=Table('atob', Base.metadata, >> Column('aid', ForeignKey('a.id')), >> Column('bid', ForeignKey('b.id')) >> ), >> uselist=False) >> >> class B(Base): >> __tablename__ = 'b' >> >> id = Column(Integer, primary_key=True) >> >> e = create_engine("sqlite://", echo=True) >> Base.metadata.create_all(e) >> >> sess = Session(e) >> sess.add(A()) >> sess.commit() >> sess.close() >> >> a1 = sess.query(A).options(joinedload("bs")).first() >> assert 'bs' in a1.__dict__ >> assert a1.__dict__['bs'] is None >> assert a1.bs is None >> >> >> >> -- >> You received this message because you are subscribed to the Google Groups >> "sqlalchemy" group. >> To unsubscribe from this group and stop receiving emails from it, send an >> email to sqlalchemy+unsubscr...@googlegroups.com. >> To post to this group, send email to sqlalchemy@googlegroups.com. >> Visit this group at http://groups.google.com/group/sqlalchemy. >> For more options, visit https://groups.google.com/d/optout. >> > > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to sqlalchemy+unsubscr...@googlegroups.com. > To post to this group, send email to sqlalchemy@googlegroups.com. > Visit this group at http://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/d/optout. > > > -- > You received this message because you are subscribed to the Google Groups > "sqlalchemy" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to sqlalchemy+unsubscr...@googlegroups.com. > To post to this group, send email to sqlalchemy@googlegroups.com. > Visit this group at http://groups.google.com/group/sqlalchemy. > For more options, visit https://groups.google.com/d/optout. > -- You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To unsubscribe from this group and stop receiving emails from it, send an email to sqlalchemy+unsubscr...@googlegroups.com. To post to this group, send email to sqlalchemy@googlegroups.com. Visit this group at http://groups.google.com/group/sqlalchemy. For more options, visit https://groups.google.com/d/optout.