Reputation: 3086
I am trying to create a directed graph (with backward references) via SQLAlchemy; how can I get the desired output listed below? [see section titled 'Desired Output']
from sqlalchemy import Column, Integer, ForeignKey, Table
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref
Base = declarative_base()
node_connector = Table('node_connector', Base.metadata,
Column('from_node_handle', Integer, ForeignKey('node.handle')),
Column('to_node_handle', Integer, ForeignKey('node.handle')))
class Node(Base):
__tablename__ = 'node'
handle = Column(Integer, nullable=False, primary_key=True)
next_nodes = relationship('Node',
secondary=node_connector,
foreign_keys=[node_connector.c.to_node_handle],
backref=backref("previous_nodes",
foreign_keys=[node_connector.c.from_node_handle]))
Module being executed
from sqlalchemy import create_engine, event
from sqlalchemy.engine import Engine
from sqlalchemy.orm import sessionmaker
import unittest
from ..example_db import Base, Node
class TestNodeDB(unittest.TestCase):
def setUp(self):
engine = create_engine('sqlite:///:memory:', echo=False)
SessionMaker = sessionmaker(bind=engine)
self.__session = SessionMaker()
Base.metadata.create_all(engine)
def test_node_creation(self):
node_a = Node()
node_b = Node()
node_c = Node()
self.__session.add(node_a)
self.__session.add(node_b)
self.__session.add(node_c)
node_a.next_nodes.append(node_b)
node_b.next_nodes.append(node_c)
self.__session.commit()
nodes = self.__session.query(Node)
for node in nodes:
print "Node handle " + str(node.handle) + ":"
for next_node in node.next_nodes:
print "\t next node handle " + str(next_node.handle)
for previous_node in node.previous_nodes:
print "\t previous node handle " + str(previous_node.handle)
if __name__ == "__main__":
unittest.main()
Node handle 1:
Node handle 2:
next node handle 2
previous node handle 2
Node handle 3:
next node handle 3
previous node handle 3
Node handle 1:
next node handle 2
Node handle 2:
next node handle 3
previous node handle 1
Node handle 3:
previous node handle 2
How can I get this desired output?
Thanks!
Upvotes: 2
Views: 923
Reputation: 7544
SQLAlchemy even has documentation on this: Self-Referential Many-to-Many Relationship, which covers exactly what you want. Basically, you got it almost right, just instead of using the foreign_keys
setting use primaryjoin
and secondaryjoin
:
next_nodes = relationship('Node',
secondary=node_connector,
primaryjoin=handle==node_connector.c.to_node_handle,
secondaryjoin=handle==node_connector.c.from_node_handle,
backref="previous_nodes",
)
Now your tests work. One additional detail: You don't need to add node_b
and node_c
: They are added through the casacde: If node_a
is in the session and you add something to a configured relationship, they will also be added. Also you don't need to commit
: When you query the session figures it must first flush, then query.
And also pay attention that you need to watch over loading strategies here. Take the example on Configuring Self-Referential Eager Loading to figure out a way that works for you.
Upvotes: 3