Release: 1.2.8 | Release Date: May 28, 2018

# SQLAlchemy 1.2 Documentation

# Source code for examples.adjacency_list.adjacency_list

    from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
    from sqlalchemy.orm import Session, relationship, backref, joinedload_all
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm.collections import attribute_mapped_collection

    Base = declarative_base()

    class TreeNode(Base):
        __tablename__ = 'tree'
        id = Column(Integer, primary_key=True)
        parent_id = Column(Integer, ForeignKey(id))
        name = Column(String(50), nullable=False)

        children = relationship(
            "TreeNode",
            # cascade deletions
            cascade="all, delete-orphan",

            # many to one + adjacency list - remote_side
            # is required to reference the 'remote'
            # column in the join condition.
            backref=backref("parent", remote_side=id),

            # children will be represented as a dictionary
            # on the "name" attribute.
            collection_class=attribute_mapped_collection('name'),
        )

        def __init__(self, name, parent=None):
            self.name = name
            self.parent = parent

        def __repr__(self):
            return "TreeNode(name=%r, id=%r, parent_id=%r)" % (
                self.name,
                self.id,
                self.parent_id
            )

        def dump(self, _indent=0):
            return "   " * _indent + repr(self) + \
                "\n" + \
                "".join([
                    c.dump(_indent + 1)
                    for c in self.children.values()
                ])

    if __name__ == '__main__':
        engine = create_engine('sqlite://', echo=True)

        def msg(msg, *args):
            msg = msg % args
            print("\n\n\n" + "-" * len(msg.split("\n")[0]))
            print(msg)
            print("-" * len(msg.split("\n")[0]))

        msg("Creating Tree Table:")

        Base.metadata.create_all(engine)

        session = Session(engine)

        node = TreeNode('rootnode')
        TreeNode('node1', parent=node)
        TreeNode('node3', parent=node)

        node2 = TreeNode('node2')
        TreeNode('subnode1', parent=node2)
        node.children['node2'] = node2
        TreeNode('subnode2', parent=node.children['node2'])

        msg("Created new tree structure:\n%s", node.dump())

        msg("flush + commit:")

        session.add(node)
        session.commit()

        msg("Tree After Save:\n %s", node.dump())

        TreeNode('node4', parent=node)
        TreeNode('subnode3', parent=node.children['node4'])
        TreeNode('subnode4', parent=node.children['node4'])
        TreeNode('subsubnode1', parent=node.children['node4'].children['subnode3'])

        # remove node1 from the parent, which will trigger a delete
        # via the delete-orphan cascade.
        del node.children['node1']

        msg("Removed node1.  flush + commit:")
        session.commit()

        msg("Tree after save:\n %s", node.dump())

        msg("Emptying out the session entirely, selecting tree on root, using "
            "eager loading to join four levels deep.")
        session.expunge_all()
        node = session.query(TreeNode).\
            options(joinedload_all("children", "children",
                                   "children", "children")).\
            filter(TreeNode.name == "rootnode").\
            first()

        msg("Full Tree:\n%s", node.dump())

        msg("Marking root node as deleted, flush + commit:")

        session.delete(node)
        session.commit()