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
-~----------~----~----~----~------~----~------~--~---

Reply via email to