How can I achieve a self-referencing many-to-many relationship on the SQLAlchemy ORM back referencing to the same attribute? How can I achieve a self-referencing many-to-many relationship on the SQLAlchemy ORM back referencing to the same attribute? python python

How can I achieve a self-referencing many-to-many relationship on the SQLAlchemy ORM back referencing to the same attribute?


Here's the UNION approach I hinted at on the mailing list earlier today.

from sqlalchemy import Integer, Table, Column, ForeignKey, \    create_engine, String, selectfrom sqlalchemy.orm import Session, relationshipfrom sqlalchemy.ext.declarative import declarative_baseBase= declarative_base()friendship = Table(    'friendships', Base.metadata,    Column('friend_a_id', Integer, ForeignKey('users.id'),                                         primary_key=True),    Column('friend_b_id', Integer, ForeignKey('users.id'),                                         primary_key=True))class User(Base):    __tablename__ = 'users'    id = Column(Integer, primary_key=True)    name = Column(String)    # this relationship is used for persistence    friends = relationship("User", secondary=friendship,                            primaryjoin=id==friendship.c.friend_a_id,                           secondaryjoin=id==friendship.c.friend_b_id,    )    def __repr__(self):        return "User(%r)" % self.name# this relationship is viewonly and selects across the union of all# friendsfriendship_union = select([                        friendship.c.friend_a_id,                         friendship.c.friend_b_id                        ]).union(                            select([                                friendship.c.friend_b_id,                                 friendship.c.friend_a_id]                            )                    ).alias()User.all_friends = relationship('User',                       secondary=friendship_union,                       primaryjoin=User.id==friendship_union.c.friend_a_id,                       secondaryjoin=User.id==friendship_union.c.friend_b_id,                       viewonly=True) e = create_engine("sqlite://",echo=True)Base.metadata.create_all(e)s = Session(e)u1, u2, u3, u4, u5 = User(name='u1'), User(name='u2'), \                    User(name='u3'), User(name='u4'), User(name='u5')u1.friends = [u2, u3]u4.friends = [u2, u5]u3.friends.append(u5)s.add_all([u1, u2, u3, u4, u5])s.commit()print u2.all_friendsprint u5.all_friends


I needed to solve this same problem and messed about quite a lot with self referential many-to-many relationship wherein I was also subclassing the User class with a Friend class and running into sqlalchemy.orm.exc.FlushError. In the end instead of creating a self referential many-to-many relationship, I created a self referential one-to-many relationship using a join table (or secondary table).

If you think about it, with self referential objects, one-to-many IS many-to-many. It solved the issue of the backref in the original question.

I also have a gisted working example if you want to see it in action. Also it looks like github formats gists containing ipython notebooks now. Neat.

friendship = Table(    'friendships', Base.metadata,    Column('user_id', Integer, ForeignKey('users.id'), index=True),    Column('friend_id', Integer, ForeignKey('users.id')),    UniqueConstraint('user_id', 'friend_id', name='unique_friendships'))class User(Base):    __tablename__ = 'users'    id = Column(Integer, primary_key=True)    name = Column(String(255))    friends = relationship('User',                           secondary=friendship,                           primaryjoin=id==friendship.c.user_id,                           secondaryjoin=id==friendship.c.friend_id)    def befriend(self, friend):        if friend not in self.friends:            self.friends.append(friend)            friend.friends.append(self)    def unfriend(self, friend):        if friend in self.friends:            self.friends.remove(friend)            friend.friends.remove(self)    def __repr__(self):        return '<User(name=|%s|)>' % self.name