2014-11-03 72 views
0

以下是鄰接列表+繼承的示例。這工作正常,但如果我嘗試使用它在另一個模型Mammut的關係,它拋出我這個錯誤:鄰接列表+抽象基類關係中使用的繼承

Traceback (most recent call last): 
    File "bin/py", line 73, in <module> 
    exec(compile(__file__f.read(), __file__, "exec")) 
    File "../adjacency_list.py", line 206, in <module> 
    create_entries(IntTreeNode) 
    File "../adjacency_list.py", line 170, in create_entries 
    mut.nodes.append(node) 
    File "/home/xxx/.buildout/eggs/SQLAlchemy-0.9.8-py3.4-linux-x86_64.egg/sqlalchemy/orm/dynamic.py", line 304, in append 
    attributes.instance_dict(self.instance), item, None) 
    File "/home/xxx/.buildout/eggs/SQLAlchemy-0.9.8-py3.4-linux-x86_64.egg/sqlalchemy/orm/dynamic.py", line 202, in append 
    self.fire_append_event(state, dict_, value, initiator) 
    File "/home/xxx/.buildout/eggs/SQLAlchemy-0.9.8-py3.4-linux-x86_64.egg/sqlalchemy/orm/dynamic.py", line 99, in fire_append_event 
    value = fn(state, value, initiator or self._append_token) 
    File "/home/xxx/.buildout/eggs/SQLAlchemy-0.9.8-py3.4-linux-x86_64.egg/sqlalchemy/orm/attributes.py", line 1164, in emit_backref_from_collection_append_event 
    child_impl.append(
AttributeError: '_ProxyImpl' object has no attribute 'append' 

驗證碼:

from sqlalchemy import (Column, ForeignKey, Integer, String, create_engine, 
         Float) 
from sqlalchemy.orm import (Session, relationship, backref, joinedload_all) 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm.collections import attribute_mapped_collection 
from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase 


Base = declarative_base() 


class Mammut(Base): 
    __tablename__ = "mammut" 

    id = Column(Integer, primary_key=True) 
    nodes = relationship(
     'TreeNode', 
     backref='mammut', 
     lazy='dynamic', 
     cascade="all, delete-orphan", 
     #viewonly=True 
    ) 


class TreeNode(AbstractConcreteBase, Base): 
    id = Column(Integer, primary_key=True) 
    name = Column(String(50), nullable=False) 
    depth = Column(Integer, default=0) 
    data_type = Column(String(50)) 

    @declared_attr 
    def mammut_id(cls): 
     return Column(Integer, ForeignKey('mammut.id')) 

    @declared_attr 
    def __tablename__(cls): 
     return cls.__name__.lower() 

    @declared_attr 
    def __mapper_args__(cls): 
     ret = {} 
     if cls.__name__ != "TreeNode": 
      ret = {'polymorphic_identity': cls.__name__, 
        'concrete': True, 
        # XXX redundant makes only sense if we use one table 
        'polymorphic_on': cls.data_type} 
     return ret 

    @declared_attr 
    def parent_id(cls): 
     _fid = '%s.id' % cls.__name__.lower() 
     return Column(Integer, ForeignKey(_fid)) 

    @declared_attr 
    def children(cls): 
     _fid = '%s.id' % cls.__name__ 
     return relationship(cls.__name__, 
          # 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=_fid), 
          # children will be represented as a dictionary 
          # on the "name" attribute. 
          collection_class=attribute_mapped_collection(
           'name'), 
          ) 

    def get_path(self, field): 
     if self.parent: 
      return self.parent.get_path(field) + [getattr(self, field)] 
     else: 
      return [getattr(self, field)] 

    @property 
    def name_path(self): 
     # XXX there is no way to query for it except we add a function with a 
     # cte (recursive query) to our database see [1] for it 
     # https://stackoverflow.com/questions/14487386/sqlalchemy-recursive-hybrid-property-in-a-tree-node 
     return '/'.join(self.get_path(field='name')) 

    def __init__(self, name, value=None, parent=None): 
     self.name = name 
     self.parent = parent 
     self.depth = 0 
     self.value = value 
     if self.parent: 
      self.depth = self.parent.depth + 1 

    def __repr__(self): 
     ret = "%s(name=%r, id=%r, parent_id=%r, value=%r, depth=%r, " \ 
      "name_path=%s data_type=%s)" % (
       self.__class__.__name__, 
       self.name, 
       self.id, 
       self.parent_id, 
       self.value, 
       self.depth, 
       self.name_path, 
       self.data_type 
      ) 
     return ret 

    def dump(self, _indent=0): 
     return " " * _indent + repr(self) + \ 
      "\n" + \ 
      "".join([ 
       c.dump(_indent + 1) 
       for c in self.children.values()] 
     ) 


class IntTreeNode(TreeNode): 
    value = Column(Integer) 


class FloatTreeNode(TreeNode): 
    value = Column(Float) 
    miau = Column(String(50), default='zuff') 

    def __repr__(self): 
     ret = "%s(name=%r, id=%r, parent_id=%r, value=%r, depth=%r, " \ 
      "name_path=%s data_type=%s miau=%s)" % (
       self.__class__.__name__, 
       self.name, 
       self.id, 
       self.parent_id, 
       self.value, 
       self.depth, 
       self.name_path, 
       self.data_type, 
       self.miau 
      ) 
     return ret 


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) 

    def create_entries(Cls): 
     node = Cls('rootnode', value=2) 
     Cls('node1', parent=node) 
     Cls('node3', parent=node) 

     node2 = Cls('node2') 
     Cls('subnode1', parent=node2) 
     node.children['node2'] = node2 
     Cls('subnode2', parent=node.children['node2']) 

     msg("Created new tree structure:\n%s", node.dump()) 

     msg("flush + commit:") 
     # XXX this throws the error 
     mut = Mammut() 
     mut.nodes.append(node) 
     session.add(mut) 
     session.add(node) 
     session.commit() 

     msg("Tree After Save:\n %s", node.dump()) 

     Cls('node4', parent=node) 
     Cls('subnode3', parent=node.children['node4']) 
     Cls('subnode4', parent=node.children['node4']) 
     Cls('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(Cls).\ 
      options(joinedload_all("children", "children", 
            "children", "children")).\ 
      filter(Cls.name == "rootnode").\ 
      first() 

     msg("Full Tree:\n%s", node.dump()) 

     # msg("Marking root node as deleted, flush + commit:") 
     # session.delete(node) 
     # session.commit() 

    create_entries(IntTreeNode) 
    create_entries(FloatTreeNode) 

    nodes = session.query(TreeNode).filter(
     TreeNode.name == "rootnode").all() 
    for idx, n in enumerate(nodes): 
     msg("Full (%s) Tree:\n%s" % (idx, n.dump())) 

回答

1

具體的繼承可以是非常困難的, AbstractConcreteBase本身在0.9中有一些錯誤,從而避免了使用這種複雜的映射。

使用1.0(未公佈,使用git碩士),我可以得到的主要元素去如下:

from sqlalchemy import Column, String, Integer, create_engine, ForeignKey, Float 
from sqlalchemy.orm import Session, relationship 
from sqlalchemy.ext.declarative import declarative_base 
from sqlalchemy.orm.collections import attribute_mapped_collection 
from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase 


Base = declarative_base() 


class Mammut(Base): 
    __tablename__ = "mammut" 

    id = Column(Integer, primary_key=True) 
    nodes = relationship(
     'TreeNode', 
     lazy='dynamic', 
     back_populates='mammut', 
    ) 


class TreeNode(AbstractConcreteBase, Base): 
    id = Column(Integer, primary_key=True) 
    name = Column(String) 

    @declared_attr 
    def __tablename__(cls): 
     if cls.__name__ == 'TreeNode': 
      return None 
     else: 
      return cls.__name__.lower() 

    @declared_attr 
    def __mapper_args__(cls): 
     return {'polymorphic_identity': cls.__name__, 'concrete': True} 

    @declared_attr 
    def parent_id(cls): 
     return Column(Integer, ForeignKey(cls.id)) 

    @declared_attr 
    def mammut_id(cls): 
     return Column(Integer, ForeignKey('mammut.id')) 

    @declared_attr 
    def mammut(cls): 
     return relationship("Mammut", back_populates="nodes") 

    @declared_attr 
    def children(cls): 
     return relationship(
      cls, 
      back_populates="parent", 
      collection_class=attribute_mapped_collection('name'), 
     ) 

    @declared_attr 
    def parent(cls): 
     return relationship(
      cls, remote_side="%s.id" % cls.__name__, 
      back_populates='children') 


class IntTreeNode(TreeNode): 
    value = Column(Integer) 


class FloatTreeNode(TreeNode): 
    value = Column(Float) 
    miau = Column(String(50), default='zuff') 

e = create_engine("sqlite://", echo=True) 
Base.metadata.create_all(e) 

session = Session(e) 

root = IntTreeNode(name='root') 
IntTreeNode(name='n1', parent=root) 
n2 = IntTreeNode(name='n2', parent=root) 
IntTreeNode(name='n2n1', parent=n2) 

m1 = Mammut() 
m1.nodes.append(n2) 
m1.nodes.append(root) 

session.add(root) 
session.commit() 


session.close() 

root = session.query(TreeNode).filter_by(name='root').one() 
print root.children