使用自定义字典扩展的集合与SqlAlchemy结合

0 投票
1 回答
2196 浏览
提问于 2025-04-16 06:47
AttributeError: 'ZepConnector' object has no attribute '_sa_instance_state'

我正在尝试使用一个自定义集合来“连接”或关联两个类,但一直没有成功。也许我对SqlAlchemy自定义集合的理解有误,不过让我来解释一下我在做什么(看看有没有人能给我一些提示或建议)

我有一个父类(Parent),一些人可能会记得我之前提到过这个类),里面有几个连接字段(类似于列表)。其中一个连接字段会存储类型为“VR”的子类(Child)的实例,另一个则存储类型为“CC”的子类。

我其实不需要存储子类的集合,但我希望它是一个特殊的类,这样我可以使用我已经实现的一些方法。这个类叫做“ZepConnector”(为了方便说明,这里用到的方法是foo())。在接下来的代码中,我会在父类的addChild1()方法中随机测试它是否可用。

--------------------- Parent.py -----------------

from megrok import rdb
from sqlalchemy import Column
from sqlalchemy import and_
from sqlalchemy.orm import relationship
from sqlalchemy.types import Integer
from sqlalchemy.types import String
from mylibraries.database.tests.Child import Child
from mylibraries.database.tests.Tables import testMetadata
from mylibraries.database.tests.ZepConnector import ZepConnector

class Parent(rdb.Model):
    rdb.metadata(testMetadata)
    rdb.tablename("parents_table")
    rdb.tableargs(schema='test2', useexisting=False)

    id = Column("id", Integer, primary_key=True, nullable=False, unique=True)
    _whateverField1 = Column("whatever_field1", String(16)) #Irrelevant
    _whateverField2 = Column("whatever_field2", String(16)) #Irrelevant

    child1 = relationship(
        "Child",
        uselist=True,
        primaryjoin=lambda: and_((Parent.id == Child.parent_id), (Child.type == "VR")),
        collection_class=ZepConnector("VR")
        )

    child2 = relationship(
        "Child",
        uselist=True,
        primaryjoin=lambda: and_((Parent.id == Child.parent_id), (Child.type == "CC")),
        collection_class=ZepConnector("CC")
        )

    def __init__(self):
        print "Parent __init__"
        self._whateverField1 = "Whatever1"
        self._whateverField2 = "Whatever2"
        self.child1 = ZepConnector("VR")
        self.child2 = ZepConnector("CC")

    def addChild1(self, child):
        if isinstance(child, Child):
            print("::addChild1 > Testing .foo method: " + str(self.child1.foo()))
            # The line above doesn't really makes much 
            # but testing the accessibility of the .foo() method.
            # As I'll explain later, it doesn't work
            self.child1.append(child)

    def addChild2(self, child):
        if isinstance(child, Child):
            self.child2.append(child)

请注意,我正在使用megrok。对于不熟悉这个工具的人,我来解释一下:它只是一个将Python类映射到SqlAlchemy映射器的工具,让使用Grok框架时变得更“友好”。

我想,父类(Parent())在普通SqlAlchemy中的映射大概是这样的:

mapper(Parent, parents_table, properties={
    id = Column("id", Integer, primary_key=True, nullable=False, unique=True)
    _whateverField1 = Column("whatever_field1", String(16)) #Irrelevant
    _whateverField2 = Column("whatever_field2", String(16)) #Irrelevant
    child1 = relationship( # etc, etc, etc
})
#

但我百分之百……呃……九十……呃……七十……可以肯定使用这个工具并不是我在这里提问的原因(我的意思是:我不认为它干扰了SqlAlchemy自定义集合的功能)

子类(Child)是一个非常简单的类:

--------------- Child.py --------------------------

import random

from megrok import rdb
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy.types import Integer
from sqlalchemy.types import String
from mylibraries.database.tests.Tables import testMetadata

class Child(rdb.Model):
    rdb.metadata(testMetadata)
    rdb.tablename("children_table")
    rdb.tableargs(schema='test2', useexisting=False)

    parent_id = Column("parent_id", Integer, ForeignKey("test2.parents_table.id"), primary_key=True)
    type = Column("type", String(2), nullable=True, primary_key=True)
    hasher = Column("hasher", String(5))

    def __init__(self):
        self.type = None
        self.hasher = self.generateHasher()

    def setType(self, typeParameter):
        if typeParameter in set(["VR", "CC"]):
            self.type = typeParameter

    @staticmethod
    def generateHasher():
        retval = str()
        for i in random.sample('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', 5):
            retval += i
        return retval

假设每个子类实例都有一个唯一的“hasher”字段,可以用作字典中的键(上面的例子离实际情况有点远,但它稍微说明了子类的工作方式,并且可以用来创建测试)

接下来是我的自定义连接器。我希望它表现得像一个列表集合(更像是集合,虽然我不太在意),但它是一个继承自字典的类。

-------------------- ZepConnector.py --------------------

from sqlalchemy.orm.collections import collection

class ZepConnector(dict):
    __emulates__ = list

    def __init__(self, type):
        self.type = type 
        # The 'type' will be "VR" or "CC" and it will be stamped
        # on every Child() class added through this ZepConnector

    def foo(self):
        return True

    @collection.appender
    def append(self, item):
        #Appends a child to itself
        if self.foo():
            item.setType(self.type)
            self[item.hasher] = item

    @collection.remover
    def remove(self, item):
        try:
            del self[item.hasher]
        except ValueError, e:
            print("::remove > Got exception when trying to remove entry=" + str(item.hasher) + ". The exception is: " + str(e))

    def extend(self, items):
        pass

但我不知道为什么,父类中的“ZepConnector”实例似乎不是“ZepConnector”类型,而是“InstrumentedList”:

当我在父类的addChild1方法中尝试测试.foo()方法(应该只是打印“True”)时,我遇到了这个错误:

AttributeError: 'InstrumentedList' object has no attribute 'foo'

显示完整的追踪信息:

Traceback (most recent call last):
  File "/home/ae/mytests-cms/grokserver/eggs/zope.publisher-3.12.0-py2.4.egg/zope/publisher/publish.py", line 134, in publish
    result = publication.callObject(request, obj)
  File "/home/ae/mytests-cms/grokserver/eggs/grok-1.1rc1-py2.4.egg/grok/publication.py", line 89, in callObject
    return super(ZopePublicationSansProxy, self).callObject(request, ob)
  File "/home/ae/mytests-cms/grokserver/eggs/zope.app.publication-3.10.2-py2.4.egg/zope/app/publication/zopepublication.py", line 205, in callObject
    return mapply(ob, request.getPositionalArguments(), request)
  File "/home/ae/mytests-cms/grokserver/eggs/zope.publisher-3.12.0-py2.4.egg/zope/publisher/publish.py", line 109, in mapply
    return debug_call(obj, args)
  File "/home/ae/mytests-cms/grokserver/eggs/zope.publisher-3.12.0-py2.4.egg/zope/publisher/publish.py", line 115, in debug_call
    return obj(*args)
  File "/home/ae/mytests-cms/grokserver/eggs/grokcore.view-1.13.2-py2.4.egg/grokcore/view/components.py", line 101, in __call__
    return mapply(self.render, (), self.request)
  File "/home/ae/mytests-cms/grokserver/eggs/zope.publisher-3.12.0-py2.4.egg/zope/publisher/publish.py", line 109, in mapply
    return debug_call(obj, args)
  File "/home/ae/mytests-cms/grokserver/eggs/zope.publisher-3.12.0-py2.4.egg/zope/publisher/publish.py", line 115, in debug_call
    return obj(*args)
  File "/home/ae/mytests-cms/grokserver/src/grokserver/app.py", line 1575, in render
    mylibraries.database.tests.Test.runWholeTest()
  File "/home/ae/mytests-cms/mylibraries/database/tests/Test.py", line 54, in runWholeTest
    __test()
  File "/home/ae/mytests-cms/mylibraries/database/tests/Test.py", line 35, in __test
    parent.addChild1(child)
  File "/home/ae/mytests-cms/mylibraries/database/tests/Parent.py", line 54, in addChild1
    print("::addChild1 > Testing .foo method: " + str(self.child1.foo()))
AttributeError: 'InstrumentedList' object has no attribute 'foo'
Debug at: http://127.0.0.1:8080/_debug/view/1289342582

这很奇怪……ZepConnector的init方法正常执行……但当我尝试使用它时,它似乎不是ZepConnector……

我做了一些其他测试,但都没有成功:

在第二次尝试中,我写了:

class ZepConnector(dict):
    __emulates__ = set

但这让事情变得更糟,因为我得到了:

TypeError: Incompatible collection type: ZepConnector is not list-like

在第三次(或者说第二次的第二次)尝试中,我想……“好吧……如果它说ZepConnector不是一个列表,也许告诉父类不要在关系中使用列表会有帮助……也许声明collection_class是ZepConnector就不需要在关系中使用uselist参数……”

于是我写了:

child1 = relationship(
    "Child",
    uselist = False,
    primaryjoin=lambda: and_((Parent.id == Child.parent_id),(Child.type == "VR")),
    collection_class=ZepConnector("VR")
    )

但这引发了一个奇怪的异常,提到了一个我不应该看到的字段,我也不想看到……永远……:-D

我正在使用Python2.4和SqlAlchemy 0.6.6,以防这有关系。

如果有人有任何想法、指导或建议……无论是什么……我真的很感激你能和我……呃……我们分享一下……

提前谢谢你!

(如果你能看到这一行,你肯定值得一个“谢谢”,因为你耐心地读完了这么长的帖子)

1 个回答

2

明白了。

我之前也在SqlAlchemy的谷歌小组问过同样的问题,刚刚得到了回复。

http://groups.google.com/group/sqlalchemy/msg/5c8fc09a75fd4fa7

引用:

这个说法是不对的 - collection_class 需要一个类或者 其他可以调用的东西作为参数,这个参数会生成你的 集合实例。你下面提到的 ZepConnector 源码表明 ZepConnector("VR") 是集合的一个实例。 你需要在这里使用一个 lambda 表达式。 你遇到的其他错误似乎都是由这个问题引起的(这也是为什么 init 会在 ZepConnector 上被调用 - 因为你自己在调用它)。

感谢 Michael Bayer(以及所有尝试帮助的人,即使只是阅读这么长的帖子)

撰写回答