使用声明式SQLAlchemy创建容器关系

3

我的Python/SQLAlchemy应用程序管理一组节点,这些节点都是从一个基类Node派生而来。我正在使用SQLAlchemy的多态特性来在SQLite3表中管理这些节点。以下是基本Node类的定义:

class Node(db.Base):
    __tablename__ = 'nodes'
    id = Column(Integer, primary_key=True)
    node_type = Column(String(40))
    title = Column(UnicodeText)
    __mapper_args__ = {'polymorphic_on': node_type}

例如,其中一个派生类NoteNode

class NoteNode(Node):
    __mapper_args__ = {'polymorphic_identity': 'note'}
    __tablename__ = 'nodes_note'
    id = Column(None,ForeignKey('nodes.id'),primary_key=True)
    content_type = Column(String)
    content = Column(UnicodeText)

现在我需要一个新的节点类型,ListNode,它是零个或多个Node的有序容器。 当我加载一个ListNode时,我希望它具有其ID和标题(来自基类Node)以及其包含的(子)节点集合。 一个Node可能出现在多个ListNode中,因此它不是一个适当的层次结构。 我会按照以下方式创建它们:

note1 = NoteNode(title=u"Note 1", content_type="text/text", content=u"I am note #1")
session.add(note1)

note2 = NoteNode(title=u"Note 2", content_type="text/text", content=u"I am note #2")
session.add(note2)

list1 = ListNode(title=u"My List")
list1.items = [note1,note2]
session.add(list1)

子节点列表应该只包含Node对象 - 也就是说,我只需要它们的基类内容。它们不应该完全实现为专门的类(因此我不能一次获取整个图形,还有其他原因)。

我开始按照以下方式进行,拼凑了我在各种地方找到的碎片,但并没有完全理解正在发生什么,所以这可能没有太多意义:

class ListNode(Node):
    __mapper_args__ = {'polymorphic_identity': 'list', 'inherit_condition':id==Node.id}
    __tablename__ = 'nodes_list_contents'
    id = Column(None, ForeignKey('nodes.id'), primary_key=True)
    item_id = Column(None, ForeignKey('nodes.id'), primary_key=True)
    items = relation(Node, primaryjoin="Node.id==ListNode.item_id")

这种方法有几个问题:似乎不允许空的ListNode,而将items属性设置为列表会导致SQLAlchemy抱怨'list'对象没有'_sa_instance_state'属性。毫不奇怪,对这个主题进行数小时的随机变异也没有给出任何好的结果。
我在SQLAlchemy方面经验有限,但真的希望尽快使其正常工作。非常感谢您能提供任何建议或指导。提前致谢!
1个回答

5
你需要一个额外的表来处理多对多关系:
nodes_list_nodes = Table(
    'nodes_list_nodes', metadata,
    Column('parent_id', None, ForeignKey('nodes_list.id'), nullable=False),
    Column('child_id', None, ForeignKey(Node.id), nullable=False),
    PrimaryKeyConstraint('parent_id', 'child_id'),
)

class ListNode(Node):
    __mapper_args__ = {'polymorphic_identity': 'list'}
    __tablename__ = 'nodes_list'
    id = Column(None, ForeignKey('nodes.id'), primary_key=True)
    items = relation(Node, secondary=nodes_list_nodes)

更新: 下面是使用 association_proxy 的有序列表示例:

from sqlalchemy.orm.collections import InstrumentedList
from sqlalchemy.ext.associationproxy import association_proxy


class ListNodeAssociation(Base):
    __tablename__ = 'nodes_list_nodes'
    parent_id = Column(None, ForeignKey('nodes_list.id'), primary_key=True)
    child_id = Column(None, ForeignKey(Node.id), primary_key=True)
    order = Column(Integer, nullable=False, default=0)
    child = relation(Node)
    __table_args__ = (
        PrimaryKeyConstraint('parent_id', 'child_id'),
        {},
    )


class OrderedList(InstrumentedList):

    def append(self, item):
        if self:
            item.order = self[-1].order+1
        else:
            item.order = 1
        InstrumentedList.append(self, item)


class ListNode(Node):
    __mapper_args__ = {'polymorphic_identity': 'list'}
    __tablename__ = 'nodes_list'
    id = Column(None, ForeignKey('nodes.id'), primary_key=True)
    _items = relation(ListNodeAssociation,
                      order_by=ListNodeAssociation.order,
                      collection_class=OrderedList,
                      cascade='all, delete-orphan')
    items = association_proxy(
                '_items', 'child',
                creator=lambda item: ListNodeAssociation(child=item))

这将导致“items”成为实际Item对象的列表,是吗?二元关系最终变得完全透明,以便他不需要使用associationproxy跳过它到实际对象,对吗? - Brandon Rhodes
没错,这个情况很简单。当您使用中间模型时,即使要使列表有序(在连接表中添加顺序字段),也需要使用associationproxy。 - Denis Otkidach
非常感谢,Denis - 这真的打破了僵局。如果我可以再深入一点,我已经用声明式风格重写了关系表:class ListNodeAssociation(Base): __tablename__ = 'nodes_list_nodes' parent_id = Column(None, ForeignKey('nodes_list.id'),primary_key=True) child_id = Column(None, ForeignKey(Node.id),primary_key=True)我仍然需要按顺序排列关系,最好通过SQLAlchemy的“orderinglist”机制实现,但我不清楚它在这种情况下如何应用...它是否适用,如果是,如何使用? - jgarbers
您可以通过将新代码标记为更新来正确地更新原始问题。或者发布另一个标题更好的问题(参考此问题),以便其他人可以轻松找到它。 - Denis Otkidach
我已经添加了一个有序列表的例子。 - Denis Otkidach
谢谢,Denis - 跑得非常好。感谢您关于更新问题的提示! - jgarbers

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接