Release: 1.4.46 | Release Date: January 3, 2023

# SQLAlchemy 1.4 Documentation

# Source code for examples.adjacency_list.adjacency_list

    from sqlalchemy import Column
    from sqlalchemy import create_engine
    from sqlalchemy import ForeignKey
    from sqlalchemy import Integer
    from sqlalchemy import String
    from sqlalchemy.ext.declarative import declarative_base
    from sqlalchemy.orm import backref
    from sqlalchemy.orm import joinedload
    from sqlalchemy.orm import relationship
    from sqlalchemy.orm import Session
    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("children")
                .joinedload("children")
                .joinedload("children")
                .joinedload("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()