Hello,
I would like to have some classes derive from a common ancestor and
access them all together having the mapper select the right class for
each instance. I've done a little hack using MapperExtension and
outerjoins, however there are some problems with this solution.
See the attached files.
from sqlalchemy import *
class PolymorphicMixin(object):
@classmethod
def polymorphicJoin(cls):
"""
should follow the complete inheritance tree instead of the first level with __subclasses__...
"""
return reduce(lambda x,y: outerjoin(x, y),
[cls.mapper.table] +
[x.mapper.table.right for x in cls.__subclasses__()])
@classmethod
def polymorphicExtension(cls):
return PolymorphicExtension(cls)
def polymorphic_mapper(class_, **kwargs):
return mapper(class_, class_.polymorphicJoin(), extension=class_.polymorphicExtension(), **kwargs)
class PolymorphicExtension(MapperExtension):
def __init__(self, baseClass):
super(PolymorphicExtension, self).__init__()
self.baseClass = baseClass
self.subclasses = self.baseClass.__subclasses__()
def append_result(self, mapper, row, imap, result, instance, isnew, populate_existing=False):
for s in self.subclasses:
pks = [row[x] for x in s.mapper.columns if x.primary_key]
if reduce(lambda x,y: x is not None and y, pks) is not None:
instance.__class__ = s
return True
#!/usr/bin/env python
from sqlalchemy import *
import sqlalchemy.util as util
from polymorphic import *
engine = create_engine('postgres://database=polytest&host=127.0.0.1&user=polytest&password=polytest', echo=False)
base = Table('base', engine,
Column('id', Integer, Sequence('base_id_seq',optional=False), primary_key=True),
Column('name', String(50), nullable=False),
)
persons = Table('persons', engine,
Column('person_id', Integer, Sequence('persons_id_seq', optional=False), primary_key=True),
Column('base_id', Integer, ForeignKey('base.id')),
Column('first_name', String(50), nullable=True)
)
organizations = Table('organizations', engine,
Column('organization_id', Integer, Sequence('organizations_id_seq', optional=False), primary_key=True),
Column('base_id', Integer, ForeignKey('base.id')),
Column('contact', String(50), nullable=True)
)
base.create()
persons.create()
organizations.create()
class Base(PolymorphicMixin):
def __repr__(self):
return "<Base: %s: %s>" % (id(self), self.name)
def doSomething(self):
print "base doing nothing"
class Person(Base):
def __repr__(self):
return "<Person %s: %s, %s>" % (id(self), self.name, self.first_name)
def doSomething(self):
print "person adding y to address"
self.first_name = self.first_name + "y"
class Organization(Base):
def __repr__(self):
return "<Organization %s: %s, %s>" % (id(self), self.name, self.contact)
def doSomething(self):
print "organization adding x to contact"
self.contact = self.contact + "x"
assign_mapper(Base, base)
assign_mapper(Person, persons, inherits=Base.mapper)
assign_mapper(Organization, organizations, inherits=Base.mapper)
pm = polymorphic_mapper(Base, primary_key=[base.c.id])
print pm.select()
Person(name='Smith', first_name='John')
Organization(name='Label Bleu', contact='David Krakauer')
objectstore.commit()
print pm.select()
for o in pm.select():
o.doSomething()
print pm.select()
# commit necessary to see changes because modifications are not tracked with the "pm" mapper
objectstore.commit()
print pm.select()
# Works without commit because the mapper returns the same instances
for o in Person.select():
o.doSomething()
print Person.select()
One problem is that patching the __class__ attribute with the
append_result hook the identity of the instances is not the same as
those that come from their primary mapper.
Is there some better way to do that?
If I do a objectstore.commit() after modifying objects patched that
way then I can see the changes also from the primary mappers. Is that
safe or there are some subtle interactions that could cause problems?
--
marko