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.