Hmm, the patch seems to fix the case where the relationship is on the subclass, but putting the relationship on the base class is resulting in an AttributeError.
Node.links = sa.orm.relationship( Edge, primaryjoin=NodeWithEdges.element_id == Edge.parent_id, lazy=False, cascade='all, delete-orphan', single_parent=True, passive_updates=False, join_depth=8, ) Node.children = sa.ext.associationproxy.association_proxy( 'links', 'child', creator=lambda child: Edge(child_id=child.element_id)) Results in the stack trace: Traceback (most recent call last): File "./sqlalchemy_todo_test.py", line 108, in <module> new_n1 = session.query(NodeWithEdges).filter(NodeWithEdges.element_id==1).first() File "/home/chris/Documents/cozi/kits/output/site-packages/SQLAlchemy-1.0.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2469, in first ret = list(self[0:1]) File "/home/chris/Documents/cozi/kits/output/site-packages/SQLAlchemy-1.0.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2292, in __getitem__ return list(res) File "/home/chris/Documents/cozi/kits/output/site-packages/SQLAlchemy-1.0.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 2567, in __iter__ context = self._compile_context() File "/home/chris/Documents/cozi/kits/output/site-packages/SQLAlchemy-1.0.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/query.py", line 3029, in _compile_context strategy(*rec[1:]) File "/home/chris/Documents/cozi/kits/output/site-packages/SQLAlchemy-1.0.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/strategies.py", line 1329, in _create_eager_join _entity_for_mapper(self.parent) File "/home/chris/Documents/cozi/kits/output/site-packages/SQLAlchemy-1.0.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/util.py", line 540, in _entity_for_mapper return getattr(self.entity, mapper.class_.__name__)._aliased_insp File "/home/chris/Documents/cozi/kits/output/site-packages/SQLAlchemy-1.0.9-py2.7-linux-x86_64.egg/sqlalchemy/orm/util.py", line 398, in __getattr__ raise AttributeError(key) AttributeError: Node On Wed, Nov 25, 2015 at 10:21 AM, Christopher Lee <c...@sirdodger.net> wrote: > Verified that the patch works. > > On Wed, Nov 25, 2015 at 10:16 AM, Christopher Lee <c...@sirdodger.net> > wrote: > >> Great, I'll try it out. >> >> Thanks, Mike! >> >> >> On Tue, Nov 24, 2015 at 10:15 PM, Mike Bayer <mike...@zzzcomputing.com> >> wrote: >> >>> >>> >>> On 11/24/2015 08:19 PM, Mike Bayer wrote: >>> > I've bisected it down and this one is going to be tough. >>> > >>> https://bitbucket.org/zzzeek/sqlalchemy/issues/3593/eager-loading-single-inh-polymorphic-join >>> > is added. >>> >>> >>> I've figured out exactly where a simple thing was going wrong with this >>> and produced a good patch that is safe to commit. Even though this >>> use case broke between 0.8.0 and 0.8.1, the new approach begun in 0.8.1 >>> on forward removes most of the guessing from how the ORM joins things >>> together and this fix just closes up a loose end, whereas the use case >>> working in 0.8.0 and previously was because the old approach of guessing >>> how to join things just happened to make the right guess here. >>> >>> I will try to add the necessary tests for this tomorrow so that it's >>> good for 1.0.10 at least, feel free to try the patch ahead of time to >>> confirm it resolves all your issues. >>> >>> >>> >>> >>> > >>> > >>> > >>> > On 11/24/2015 07:20 PM, Mike Bayer wrote: >>> >> >>> >> >>> >> On 11/24/2015 07:16 PM, Christopher Lee wrote: >>> >>> The test passes for me in 0.8.0. (Yes, we have 0.8.0 running in >>> >>> production, and are finally getting around to upgrading... sigh...) >>> >> >>> >> >>> >> agree. and it then fails in 0.8.1, so this is something very >>> immediate >>> >> and old in the 0.8 series I'll start looking to see the actual >>> >> condition here. >>> >> >>> >> >>> >> >>> >>> >>> >>> >>> >>> On Tue, Nov 24, 2015 at 4:09 PM, Christopher Lee <c...@sirdodger.net >>> >>> <mailto:c...@sirdodger.net>> wrote: >>> >>> >>> >>> I'll see if I can nail down the repro; I had to abstract some >>> >>> production code that I am migrating between versions, and I may >>> have >>> >>> lost some important detail. >>> >>> >>> >>> Looking at the SQL it generates before and after moving the >>> "links" >>> >>> relationship, it appears that it is outer joining against the >>> wrong >>> >>> target, which explains why the identity map is getting confused. >>> >>> (e.g., it is joining against the innermost anon_1 subquery in >>> the >>> >>> bad case, and against the todo_elements_1 reference in the good >>> >>> case, which is why the good case is forming a nice tree.) >>> >>> >>> >>> On Tue, Nov 24, 2015 at 3:49 PM, Mike Bayer >>> >>> <mike...@zzzcomputing.com <mailto:mike...@zzzcomputing.com>> >>> wrote: >>> >>> >>> >>> >>> >>> >>> >>> On 11/24/2015 06:14 PM, Christopher Lee wrote: >>> >>> > >>> >>> > I am having a problem with SQLAlchemy 1.0.9 that cropped >>> up when I >>> >>> > upgraded from 0.8. >>> >>> >>> >>> this is a nicely written test but I get the same recursion >>> overflow >>> >>> error when I run it in 0.8, 0.9, and 1.0, even back in 0.8.3. >>> >>> Can you >>> >>> provide a script that illustrates success in 0.8 and not in >>> 1.0 >>> >>> ? thanks. >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> >>> > >>> >>> > I have the following polymorphic relationship defined. For >>> >>> some reason, >>> >>> > when I build a tree, and I try to access the children of >>> the >>> >>> middle node >>> >>> > of the tree, it is picking up the wrong edge object and >>> going >>> >>> into a >>> >>> > recursive loop. (Querying for the children of n2 is >>> picking >>> >>> up the edge >>> >>> > e12, instead of the edges e23, e24 and e25.) >>> >>> > >>> >>> > Looking at the identity map, it looks like the query isn't >>> >>> populating >>> >>> > the correct objects. >>> >>> > >>> >>> > If I move the "links" relationship from the NodesWithEdges >>> >>> class to the >>> >>> > base Node class, the query works fine. Also, if I drop >>> >>> join_depth or >>> >>> > remove with with_polymorphic mapper arg, it forces multiple >>> >>> queries, and >>> >>> > the problem doesn't occur. Is this a bug with the new >>> >>> release, or am I >>> >>> > doing something bad? >>> >>> > >>> >>> > >>> >>> > from __future__ import print_function, unicode_literals >>> >>> > >>> >>> > import sqlalchemy as sa >>> >>> > import sqlalchemy.orm >>> >>> > import sqlalchemy.ext.associationproxy >>> >>> > import sqlalchemy.ext.declarative >>> >>> > import sqlalchemy.ext.orderinglist >>> >>> > >>> >>> > Base = sqlalchemy.ext.declarative.declarative_base() >>> >>> > >>> >>> > >>> >>> > class Node(Base): >>> >>> > __tablename__ = 'todo_elements' >>> >>> > >>> >>> > element_id = sa.Column(sa.Integer, nullable=False, >>> >>> primary_key=True) >>> >>> > element_type = sa.Column(sa.String(20), nullable=False) >>> >>> > >>> >>> > __mapper_args__ = { >>> >>> > 'polymorphic_on': element_type, >>> >>> > 'with_polymorphic': ('*', None), >>> >>> > } >>> >>> > >>> >>> > >>> >>> > class NodeWithEdges(Node): >>> >>> > __mapper_args__ = {'polymorphic_identity': 'todo.list'} >>> >>> > >>> >>> > >>> >>> > class LeafNode(Node): >>> >>> > __mapper_args__ = {'polymorphic_identity': 'todo.item'} >>> >>> > >>> >>> > my_flag = sa.Column(sa.Boolean, default=False) >>> >>> > >>> >>> > >>> >>> > class Edge(Base): >>> >>> > __tablename__ = 'todo_links' >>> >>> > __table_args__ = ( >>> >>> > sa.PrimaryKeyConstraint('parent_id', 'child_id'), >>> >>> > sa.ForeignKeyConstraint(['parent_id'], [Node.element_id]), >>> >>> > sa.ForeignKeyConstraint(['child_id'], [Node.element_id]), >>> >>> > ) >>> >>> > >>> >>> > parent_id = sa.Column(sa.Integer, nullable=False) >>> >>> > child_id = sa.Column(sa.Integer, nullable=False) >>> >>> > >>> >>> > >>> >>> > Edge.child = sa.orm.relationship( >>> >>> > Node, >>> >>> > uselist=False, >>> >>> > primaryjoin=Edge.child_id == Node.element_id, >>> >>> > lazy=False, >>> >>> > cascade='all', >>> >>> > passive_updates=False, >>> >>> > join_depth=8, >>> >>> > ) >>> >>> > >>> >>> > >>> >>> > NodeWithEdges.links = sa.orm.relationship( >>> >>> > Edge, >>> >>> > primaryjoin=NodeWithEdges.element_id == Edge.parent_id, >>> >>> > lazy=False, >>> >>> > cascade='all, delete-orphan', >>> >>> > single_parent=True, >>> >>> > passive_updates=False, >>> >>> > join_depth=8, >>> >>> > ) >>> >>> > >>> >>> > NodeWithEdges.children = >>> >>> sa.ext.associationproxy.association_proxy( >>> >>> > 'links', 'child', >>> >>> > creator=lambda child: Edge(child_id=child.element_id)) >>> >>> > >>> >>> > >>> >>> > >>> >>> > engine = sa.create_engine('sqlite:///:memory:', echo=True) >>> >>> > Base.metadata.create_all(engine) >>> >>> > >>> >>> > Session = sa.orm.sessionmaker(bind=engine) >>> >>> > session = Session() >>> >>> > >>> >>> > >>> >>> > # >>> >>> > # 1 --> 2 --> 3 >>> >>> > # --> 4 >>> >>> > # --> 5 >>> >>> > # >>> >>> > >>> >>> > n1 = NodeWithEdges(element_id=1) >>> >>> > n2 = NodeWithEdges(element_id=2) >>> >>> > n3 = LeafNode(element_id=3) >>> >>> > n4 = LeafNode(element_id=4, my_flag=True) >>> >>> > n5 = LeafNode(element_id=5) >>> >>> > >>> >>> > e12 = Edge(parent_id=n1.element_id, child_id=n2.element_id) >>> >>> > e23 = Edge(parent_id=n2.element_id, child_id=n3.element_id) >>> >>> > e24 = Edge(parent_id=n2.element_id, child_id=n4.element_id) >>> >>> > e25 = Edge(parent_id=n2.element_id, child_id=n5.element_id) >>> >>> > >>> >>> > session.add_all([n1, n2, n3, n4, n5, e12, e23, e24, e25]) >>> >>> > session.commit() >>> >>> > session.expunge_all() >>> >>> > >>> >>> > new_n1 = >>> >>> >>> session.query(NodeWithEdges).filter(NodeWithEdges.element_id==1).first() >>> >>> > print(session.identity_map.keys()) >>> >>> > >>> >>> > >>> >>> > def traverse(node, f, depth=0): >>> >>> > f(node, depth) >>> >>> > if hasattr(node, 'children'): >>> >>> > for c in node.children: >>> >>> > traverse(c, f, depth + 1) >>> >>> > >>> >>> > def indent_print(node, depth): >>> >>> > print(' ' * depth + str(node.element_id)) >>> >>> > if hasattr(node, 'my_flag'): >>> >>> > print(node.my_flag) >>> >>> > >>> >>> > traverse(new_n1, indent_print) >>> >>> > >>> >>> > -- >>> >>> > 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 >>> >>> <mailto:sqlalchemy%2bunsubscr...@googlegroups.com> >>> >>> > <mailto:sqlalchemy+unsubscr...@googlegroups.com >>> >>> <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>>. >>> >>> > To post to this group, send email to >>> sqlalchemy@googlegroups.com <mailto:sqlalchemy@googlegroups.com> >>> >>> > <mailto:sqlalchemy@googlegroups.com <mailto: >>> 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 >>> >>> <mailto:sqlalchemy%2bunsubscr...@googlegroups.com>. >>> >>> To post to this group, send email to >>> sqlalchemy@googlegroups.com >>> >>> <mailto: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 >>> >>> <mailto:sqlalchemy+unsubscr...@googlegroups.com>. >>> >>> To post to this group, send email to sqlalchemy@googlegroups.com >>> >>> <mailto: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.