在SQLAlchemy

时间:2015-08-13 00:10:13

标签: python postgresql sqlalchemy

我在网络应用中使用SQLAlchemy作为我的ORM。当我尝试创建一个新对象并将其添加为其他对象的子对象时,我得到以下异常:

Traceback (most recent call last):
File "/usr/local/lib/python3.4/dist-packages/tornado/web.py", line 1346, in _execute
    result = method(*self.path_args, **self.path_kwargs)
File "/usr/share/app/server/handlers.py", line 248, in wrapper
    return fn(self, *args, **kwargs)
File "/usr/share/app/server/crud/node.py", line 157, in post
    session.flush()
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/session.py", line 2004, in flush
    self._flush(objects)
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/session.py", line 2122, in _flush
    transaction.rollback(_capture_exception=True)
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/util/langhelpers.py", line 60, in __exit__
    compat.reraise(exc_type, exc_value, exc_tb)
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/util/compat.py", line 182, in reraise
    raise value
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/session.py", line 2086, in _flush
    flush_context.execute()
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/unitofwork.py", line 373, in execute
    rec.execute(self)
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/unitofwork.py", line 532, in execute
    uow
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/persistence.py", line 149, in save_obj
    base_mapper, states, uowtransaction
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/persistence.py", line 270, in _organize_states_for_save
    states):
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/persistence.py", line 1035, in _connections_for_states
    for state in _sort_states(states):
File "/usr/local/lib/python3.4/dist-packages/sqlalchemy/orm/persistence.py", line 1057, in _sort_states
    sorted(persistent, key=lambda q: q.key[1])
TypeError: unorderable types: str() < UUID()

主键由两个UUID组成(如多租户)。我正在使用sqlalchemy.ext.orderinglist对子项进行排序,并使用整数seq列来存储索引。创建新对象的代码大致是:

node = Node(group_id=group_id)
hierarchy = (session.query(Hierarchy).get((hierarchy_id, group_id)))
node.hierarchy = hierarchy
hierarchy.nodes.append(node)
hierarchy.nodes.reorder()
session.flush()

为了完整性,以下是相关的映射和关系:

class Group(Base):
    __tablename__ = 'group'
    id = Column(GUID, default=uuid.uuid4, primary_key=True)

class Hierarchy(Base):
    __tablename__ = 'hierarchy'
    id = Column(GUID, default=uuid.uuid4, primary_key=True)
    group_id = Column(
        GUID, ForeignKey('group.id'), nullable=False, primary_key=True)

class Node(Base):
    __tablename__ = 'node'
    id = Column(GUID, default=uuid.uuid4, primary_key=True)
    group_id = Column(GUID, nullable=False, primary_key=True)
    hierarchy_id = Column(GUID, nullable=False)
    parent_id = Column(GUID)
    seq = Column(Integer)

    __table_args__ = (
        ForeignKeyConstraint(
            ['parent_id', 'group_id'],
            ['node.id', 'node.group_id']
        ),
        ForeignKeyConstraint(
            ['hierarchy_id', 'group_id'],
            ['hierarchy.id', 'hierarchy.group_id']
        ),
        ForeignKeyConstraint(
            ['group_id'],
            ['group.id']
        ),
    )

    group = relationship(Group)

Group.hierarchies = relationship(
    Hierarchy, backref="group", passive_deletes=True,
    order_by='Hierarchy.title')

Hierarchy.nodes_all = relationship(
    Node, backref='hierarchy', passive_deletes=True,
    primaryjoin=and_(foreign(Node.hierarchy_id) == Hierarchy.id,
                    Node.group_id == Hierarchy.group_id))

Hierarchy.nodes = relationship(
    Node, passive_deletes=True,
    order_by=Node.seq, collection_class=ordering_list('seq'),
    primaryjoin=and_(and_(foreign(Node.hierarchy_id) == Hierarchy.id,
                        Node.group_id == Hierarchy.group_id),
                    Node.parent_id == None))

Node.parent = relationship(
    Node, backref=backref(
        'children', passive_deletes=True,
        order_by=Node.seq, collection_class=ordering_list('seq')),
    primaryjoin=and_(foreign(Node.parent_id) == remote(Node.id),
                    Node.group_id == remote(Node.group_id)))

结构是:

  • 一个群体拥有多个层次结构
  • 一个层次结构拥有许多节点(node.parent_id is NULL;这是filtering primary join
  • 一个节点拥有许多节点(其中node.parent_id = remote(node.parent_id)
  • 所有节点都有一个指向其组和层次结构的链接,即使它们不是由层次结构直接拥有(是的,有点非规范化)

1 个答案:

答案 0 :(得分:1)

事实证明我将对象添加到子项列表中两次。删除node.hierarchy = hierarchy行可解决问题。 然而,这是次优解决方案,因为我希望它很容易设置非根节点的层次结构。真正的解决方法是阻止Node.hierarchy关系将项插入Hierarchy.nodes列表。应该改变关系以使用单向反射:

Node.hierarchy = relationship(
    Hierarchy,
    primaryjoin=and_(foreign(Node.hierarchy_id) == remote(Hierarchy.id),
                    Node.group_id == remote(Hierarchy.group_id)))

Hierarchy.nodes = relationship(
    Node, back_populates='hierarchy', passive_deletes=True,
    order_by=Node.seq, collection_class=ordering_list('seq'),
    primaryjoin=and_(and_(foreign(Node.hierarchy_id) == Hierarchy.id,
                        Node.group_id == Hierarchy.group_id),
                    Node.parent_id == None))

Linking Relationships with Backref > One Way Backrefs下的文档对此进行了描述。正如文档所指出的那样,这也会阻止项目被插入nodes列表,即使Node.parent_id == None也是如此,这有点不幸。

我还决定摆脱Hierarchy.nodes_all关系,因为这只是让问题复杂化。如果我需要,我会使用特殊查询。