在声明性SQLAlchemy中创建容器关系

jga*_*ers 3 python sqlalchemy

我的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}
Run Code Online (Sandbox Code Playgroud)

并且,作为示例,派生类之一,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)
Run Code Online (Sandbox Code Playgroud)

现在我需要一种新的节点ListNode,它是零个或多个Node的有序容器.当我加载ListNode时,我希望它具有其ID和标题(来自基本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)
Run Code Online (Sandbox Code Playgroud)

子项列表应该只包含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")
Run Code Online (Sandbox Code Playgroud)

这种方法在几个方面失败:它似乎不允许空ListNode,并且将items属性设置为列表会导致SQLAlchemy抱怨'list'对象没有属性'_sa_instance_state'.毫不奇怪,这个主题的数小时随机突变没有给出任何好的结果,

我在SQLAlchemy的经验有限,但我真的希望尽快完成这项工作.我非常感谢您提供的任何建议或指导.提前致谢!

Den*_*ach 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)
Run Code Online (Sandbox Code Playgroud)

更新:下面是有序列表的示例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))
Run Code Online (Sandbox Code Playgroud)