Ok, I've figured out the problem but not really sure what the proper solution is.
Basically, I have Thing objects that can have attributes associated with them. I have other classes that are subclasses of the Thing object. These classes can provide more specific functionality based on the type of Thing it is. Since Thing and it's subclasses all share the same table, I need a way to get the correct class based on what type of Thing it is. I do this by examining the Attributes associated with a thing. The different subclasses of Thing match different attributes. In 0.3 I did this by called an instance._setProperClass() function in the populate_instance method of a MapperExtension. This seems to make 0.4 angry. If I call the same _setProperClass() after I get the object normally everything seems to work fine. I've attached a simplified version of what I do in my code to illustrate the problem. What I did was kind of a hack in 0.3 so I'm not that surprised that it doesn't work in 0.4, but I'm not sure how else to achieve the functionality I'm looking for. Is there a better way to allow for sqlalchemy to return objects of different types based on the data they happen to contain? -Ron #!/usr/bin/env python from sqlalchemy import * from sqlalchemy.ext.sessioncontext import SessionContext from sqlalchemy.ext.assignmapper import assign_mapper from sqlalchemy.orm import * #Mapper, MapperExtension from sqlalchemy.orm.mapper import Mapper #from clusto.sqlalchemyhelpers import ClustoMapperExtension import sys # session context METADATA = MetaData() SESSION = scoped_session(sessionmaker(autoflush=True, transactional=True)) THING_TABLE = Table('things', METADATA, Column('name', String(128), primary_key=True), #Column('thingtype', String(128)), mysql_engine='InnoDB' ) ATTR_TABLE = Table('thing_attrs', METADATA, Column('attr_id', Integer, primary_key=True), Column('thing_name', String(128), ForeignKey('things.name', ondelete="CASCADE", onupdate="CASCADE")), Column('key', String(1024)), Column('value', String), mysql_engine='InnoDB' ) class CustomMapperExtension(MapperExtension): def populate_instance(self, mapper, selectcontext, row, instance, **flags): Mapper.populate_instance(mapper, selectcontext, instance, row, **flags) ## Causes problems if run here! instance._setProperClass() return EXT_CONTINUE class Attribute(object): """ Attribute class holds key/value pair backed by DB """ def __init__(self, key, value, thing_name=None): self.key = key self.value = value if thing_name: self.thing_name = thing_name def __repr__(self): return "thingname: %s, keyname: %s, value: %s" % (self.thing_name, self.key, self.value) def delete(self): SESSION.delete(self) SESSION.mapper(Attribute, ATTR_TABLE) DRIVERLIST = {} class Thing(object): """ Anything """ someattrs = (('klass', 'server'),) def __init__(self, name, *args, **kwargs): self.name = name for attr in self.someattrs: self.addAttr(*attr) def _setProperClass(self): """ Set the class for the proper object to the best suited driver """ if self.hasAttr('klass'): klass = self.getAttr('klass') self.__class__ = DRIVERLIST[klass] def getAttr(self, key, justone=True): """ returns the first value of a given key. if justone is False then return all values for the given key. """ attrlist = filter(lambda x: x.key == key, self._attrs) if not attrlist: raise KeyError(key) return justone and attrlist[0].value or [a.value for a in attrlist] def hasAttr(self, key, value=None): if value: attrlist = filter(lambda x: x.key == key and x.value == value, self._attrs) else: attrlist = filter(lambda x: x.key == key, self._attrs) return attrlist and True or False def addAttr(self, key, value): """ Add an attribute (key/value pair) to this Thing. Attribute keys can have multiple values. """ self._attrs.append(Attribute(key, value)) SESSION.mapper(Thing, THING_TABLE, properties={'_attrs' : relation(Attribute, lazy=False, cascade='all, delete- orphan',), }, extension=CustomMapperExtension()) DRIVERLIST['thing'] = Thing class Server(Thing): someattrs = (('klass', 'server'),) pass DRIVERLIST['server'] = Server selection = select([THING_TABLE], and_(ATTR_TABLE.c.key=='klass', ATTR_TABLE.c.value=='server', ATTR_TABLE.c.thing_name==THING_TABLE.c.name) ).alias('serveralias') SESSION.mapper(Server, selection, properties={'_attrs' : relation(Attribute, lazy=False, cascade='all, delete- orphan',), }, extension=CustomMapperExtension()) ## TEST BEGINS HERE METADATA.bind = create_engine('sqlite:///:memory:') METADATA.create_all(METADATA.bind) t1 = Thing('t1') t2 = Thing('t2') t1.addAttr('a1', 1) t1.addAttr('a1', 3) t1.addAttr('a2', 2) # Servers s1 = Server('s1') s2 = Server('s2') s3 = Server('s3') s4 = Server('s4') s5 = Server('s5') s2.addAttr('attr1', 1) s2.addAttr('attr2', 2) s4.addAttr('attr1', 1) s4.addAttr('attr2', 2) SESSION.flush() ## this line raises if _setProperClass is run in the MapperExtension: ## KeyError: ('post_processors', <sqlalchemy.orm.mapper.Mapper object at 0xb78ca7ac>, None) ret = Thing.query.filter(Thing.c.name == 's2').one() # no error if I run it manually here #ret._setProperClass() print ret --~--~---------~--~----~------------~-------~--~----~ You received this message because you are subscribed to the Google Groups "sqlalchemy" group. To post to this group, send email to sqlalchemy@googlegroups.com To unsubscribe from this group, send email to [EMAIL PROTECTED] For more options, visit this group at http://groups.google.com/group/sqlalchemy?hl=en -~----------~----~----~----~------~----~------~--~---