10 messages in com.googlegroups.sqlalchemy[sqlalchemy] Re: post_processors erro...
FromSent OnAttachments
Ron28 Oct 2007 12:39 
Michael Bayer28 Oct 2007 13:46 
Ron28 Oct 2007 15:58 
Michael Bayer28 Oct 2007 18:27 
Ron28 Oct 2007 20:02 
Michael Bayer29 Oct 2007 07:15 
Ron29 Oct 2007 18:40 
Michael Bayer29 Oct 2007 19:30 
Ron30 Oct 2007 10:34 
Michael Bayer30 Oct 2007 11:21 
Subject:[sqlalchemy] Re: post_processors error during 0.3.10 to 0.4 migration (returning different object type based on db data)
From:Ron (rong@gmail.com)
Date:10/28/2007 03:58:00 PM
List:com.googlegroups.sqlalchemy

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