在声明式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}
作为一个例子,下面是其中一个派生类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))