Hi Mike,

How about this approach with a custom metaclass; so far it works and seems 
the cleanest to me:

from sqlalchemy import *
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy.ext.declarative import declarative_base, 
has_inherited_table, DeclarativeMeta

engine = create_engine('sqlite:///:memory:', echo=False)

class InheritMeta(DeclarativeMeta):
    
    def __init__(cls, name, bases, clsdict):
        cls.__tablename__ = cls.__name__
        if not has_inherited_table(cls):
            cls.id = Column(Integer, primary_key = True)
            cls.discriminator = Column(String(50))
            cls.__mapper_args__ = {'polymorphic_on': 'discriminator'}
        else:
            cls.id = Column(Integer, ForeignKey(bases[0].id), primary_key = 
True)
            cls.__mapper_args__ = {'polymorphic_identity': cls.__name__}
        super(InheritMeta, cls).__init__(name, bases, clsdict)

InheritableBase = declarative_base(bind = engine, metaclass = InheritMeta)

class Person(InheritableBase):
    name = Column(String(50))
   
class Engineer(Person):
    job = Column(String(50))

class MasterEngineer(Engineer):
    specialty = Column(String(50))

    
if __name__ == '__main__':
    InheritableBase.metadata.create_all(engine)
    session = sessionmaker(bind=engine)()
    
    a = Person(name = 'ann')
    b = Engineer(name = 'bob', job = 'car repair')
    c = MasterEngineer(name = 'carl', job = 'car repair', specialty = 
'tires')
    session.add_all([a, b, c])
    session.commit()
    people = session.query(Person).all()
    print people

Do you see any drawbacks, gotchas for later on?

Regards, Lars

On Friday, April 20, 2012 12:41:28 PM UTC+2, Michael Bayer wrote:
>
>
> On Apr 20, 2012, at 4:59 AM, lars van gemerden wrote:
>
> > this is the testcase:
> > 
> > 
> > What am i missing?
>
>
> the issue here is one of Python inheritance mechanics.   Declarative calls 
> upon @declared_attr in terms of the class, that is, we look through the 
> class to find each @declared_attr, but when we find one, we invoke it by 
> just calling it as a method, that is, getattr(cls, name).   This works for 
> things like __mapper_args__ which remain as callable methods on classes 
> like Person, Engineer.   But "id", when that is declared on Person is 
> immediately replaced with a mapping.   By the time you get to Engineer, the 
> id() method is gone.
>
> So for inheriting cases you need to build a mixin that is applied to every 
> subclass.  This makes sense because a mixin with a column on it implies 
> that the column is being associated only with that immediate class - if you 
> wanted a subclass to act as single table inheritance, you'd omit this 
> class.  In this case you want the same column on all subclasses.    So you 
> can do it like this (note also using declarative.has_inherited_table 
> helper):
>
> class InheritMixin(object):
>
>    @declared_attr
>    def __tablename__(cls):
>        return cls.__name__
>
>    @declared_attr
>    def id(cls):
>        return Column(Integer, primary_key = True)
>
>    @declared_attr
>    def __mapper_args__(cls):
>        if not has_inherited_table(cls):
>            return {'polymorphic_on': 'discriminator'}
>        else:
>            return {'polymorphic_identity': cls.__name__}
>
> class Inherits(InheritMixin):
>     @declared_attr
>     def id(cls):
>         super_id = super(Inherits, cls).id
>         return Column(Integer, ForeignKey(super_id),primary_key = True)
>
> class Person(InheritMixin, Base):
>    discriminator = Column(String(50))
>    name = Column(String(50))
>
> class Engineer(Inherits, Person):
>    job = Column(String(50))
>
>
> this should be in the docs so I've added ticket #2471 to handle this.
>
> > 
> > Cheers, Lars
> > 
> > 
> > On Apr 19, 4:13 pm, Michael Bayer <[email protected]> wrote:
> >> On Apr 19, 2012, at 6:23 AM, lars van gemerden wrote:
> >> 
> >> 
> >> 
> >> 
> >> 
> >> 
> >> 
> >> 
> >> 
> >>> I am trying to my my joined inheritance code clearer, for the dynamic
> >>> generation of sa classes and tried to do something like this:
> >> 
> >>> class InheritMixin(object):
> >> 
> >>>    @declared_attr
> >>>    def __tablename__(cls):
> >>>        return cls.__name__
> >>>    @declared_attr
> >>>    def id(cls):
> >>>        if cls.__name__ == 'Object':
> >>>            return Column(Integer, primary_key = True)
> >>>        else:
> >>>            print 'in id: ', cls.__name__, cls.__bases__[0].__name__
> >>>            return Column(Integer,
> >>> ForeignKey(cls.__bases__[0].__name__ + '.id'), primary_key = True)
> >>>    @declared_attr
> >>>    def __mapper_args__(cls):
> >>>        if cls.__name__ == 'Object':
> >>>            return {'polymorphic_on': 'discriminator'}
> >>>        else:
> >>>            print 'in mapper_args: ', cls.__name__,
> >>> cls.__bases__[0].__name__
> >>>            return {'polymorphic_identity': cls.__name__,
> >>>                    'inherit_condition': (cls.id ==
> >>> cls.__bases__[0].id)}
> >> 
> >>> Object = type('Object', (Base, InheritMixin), clsdict)
> >> 
> >>> Where Object should be the (not necessarily direct) baseclass of all
> >>> inheriting classes. However I get errors: "Mapper Mapper|person|person
> >>> could not assemble any primary key columns for mapped table 'Join
> >>> object on Object(65389120) and person(65428224)' " etc ..
> >> 
> >> im not sure of the cause of that error, can you attach a full test case 
> which illustrates this message being generated ?
> >> 
> >> 
> >> 
> >>> I noticed that the method __mapper_args__(cls) is always called before
> >>> id(cls) (which is never called, probably due to the error.
> >> 
> >> the __mapper_args__(cls) method here directly calls upon .id, so if you 
> see .id() not being called it suggests some other form of .id is being used.
> >> 
> >> Is it possible that Base or something else has a conflicting "id" 
> attribute?
> >> 
> >> 
> >> 
> >>> Also, is there a way to add the discriminator column to the mixin (if
> >>> i just directly add it to the declaration, this gave another maybe
> >>> related error)?
> >> 
> >> maybe, let's start with the general idea of the mixin you're going to 
> send me as a working script.
> > 
> > -- 
> > You received this message because you are subscribed to the Google 
> Groups "sqlalchemy" group.
> > To post to this group, send email to [email protected].
> > 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.
> > 
>
>

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/sqlalchemy/-/WmPfsv2Y-0YJ.
To post to this group, send email to [email protected].
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