The problems creep in when you "mix" the mixin and the inheritance
functionality. The primaryjoin gotcha was the first hiccup, but now
I'm having trouble re-using the Mixin class on a second derived model:

ArgumentError: Column 'target_id' on class <class
'uber.model.test.Manager'> conflicts with existing column
'person.target_id'

class
Person(Base):

   valid_types  = [u'Engineer', u'Manager']

   id                     = Column(Integer,
primary_key=True)
   type                   = Column(Enum(*valid_types),
nullable=False)

 
@declared_attr
   def
__tablename__(cls):
       if
has_inherited_table(cls):
           return
None
       return
cls.__name__.lower()

 
@declared_attr
   def
__mapper_args__(cls):
       if cls.__name__ ==
'Person':
           mapper_args =
{}
           mapper_args['polymorphic_on'] =
cls.type
           return
mapper_args
 
else:
           return {"polymorphic_identity":
cls.__name__}

class
Mixin(object):
 
@declared_attr
   def
target_id(cls):
       return Column(Integer,
ForeignKey('target.id'))

 
@declared_attr
   def
target(cls):
       return relationship("Target", primaryjoin=lambda: cls.target_id
== Target.id)

class Engineer(Person, Mixin):
    pass

class Manager(Person, Mixin): #Exception here
    pass

class Target(Base):
    __tablename__ = 'target'
    id = Column(Integer, primary_key=True)

On Apr 17, 3:15 pm, Michael Bayer <[email protected]> wrote:
> On Apr 17, 2012, at 5:34 PM, Amos wrote:
>
>
>
>
>
>
>
>
>
> > Yes - this example works as you said. The problem I was having was
> > that relationship was created on top of a non-standard column name,
> > forcing me to use the primaryjoin kwarg. This is what was causing the
> > failure:
>
> > class Mixin(object):
> >    @declared_attr
> >    def target_id(cls):
> >        return Column('target_id', Integer, ForeignKey('target.id'))
>
> >    @declared_attr
> >    def target(cls):
> >        return relationship("Target", primaryjoin='Person.target_id ==
> > Target.id') # Problem: Person mapper does not have a target_id column
>
> > The Person mapper has no knowledge of the target_id defined in the
> > Mixin - which was confusing originally because the declarative STI
> > docs seem to indicate that the column is copied to the parent mapper
> > (http://docs.sqlalchemy.org/en/latest/orm/extensions/
> > declarative.html#single-table-inheritance). The reality is (correct me
> > if I'm wrong) is that the column is copied to the person Table but NOT
> > to the Person class Mapper.
>
> > So to fix the original exception I changed the primaryjoin condition
> > to set the class name dynamically. This way, the relationship is
> > referencing a class who's mapper does have the column defined.
>
> >    @declared_attr
> >    def target(cls):
> >        return relationship("Target", primaryjoin=cls.__name__ +
> > '.target_id == Target.id')
>
> > An example like this would be a great addition to the docs. Thanks!
>
> I'm a little hazy on whether or not Person gets a "target_id" mapped 
> attribute, when I was playing with this yesterday it seemed like it was but 
> today it does not.  However it also doesn't make a difference if target_id is 
> using @declared_attr on the mixin, or if it's established directly on 
> Engineer.  The paths within declarative for this to occur are the same and in 
> neither case does Person get a "target_id" attribute.
>
> The easiest way to set this up is to use a callable, since that "cls" right 
> there is your desired class.  You just don't have "Target" yet:
>
>   @declared_attr
>    def target(cls):
>        return relationship("Target", primaryjoin=lambda: 
> cls.target_id==Target.id)
>
> But oddly enough (this is more surprising to me, actually) we in fact have 
> exactly that example you have above, in the docs, almost verbatim, right 
> where you'd expect it in "mixing in 
> relationships":http://docs.sqlalchemy.org/en/latest/orm/extensions/declarative.html#...
>
> but I might add my non-string version to that.
>
>
>
>
>
>
>
>
>
> > On Apr 16, 7:35 am, Michael Bayer <[email protected]> wrote:
> >> what version of SQLA is that ? I cannot reproduce.   test case:
>
> >> from sqlalchemy import *
> >> from sqlalchemy.orm import *
> >> from sqlalchemy.ext.declarative import declarative_base, declared_attr, 
> >> has_inherited_table
>
> >> Base= declarative_base()
>
> >> class Person(Base):
> >>    id = Column(Integer, primary_key=True)
> >>    type = Column('type', String(50))
>
> >>    @declared_attr
> >>    def __tablename__(cls):
> >>        if has_inherited_table(cls):
> >>            return None
> >>        return cls.__name__.lower()
>
> >>    @declared_attr
> >>    def __mapper_args__(cls):
> >>        if cls.__name__ == 'Person':
> >>            mapper_args = {}
> >>            mapper_args['polymorphic_on'] = cls.type
> >>            return mapper_args
> >>        else:
> >>            return {"polymorphic_identity": cls.__name__}
>
> >> class Mixin(object):
>
> >>    @declared_attr
> >>    def target_id(cls):
> >>        return Column('target_id', ForeignKey('target.id'))
>
> >>    @declared_attr
> >>    def target(cls):
> >>        return relationship("Target")
>
> >> class Engineer(Person, Mixin):
> >>     pass
>
> >> class Target(Base):
> >>     __tablename__ = 'target'
> >>     id = Column(Integer, primary_key=True)
>
> >> configure_mappers()
>
> >> e = create_engine("sqlite://", echo=True)
> >> Base.metadata.create_all(e)
> >> s = Session(e)
> >> s.add_all([
> >>     Engineer(),
> >>     Engineer(target=Target())
> >> ])
> >> print s.query(Engineer, Target).outerjoin(Engineer.target).all()
>
> >> output:
>
> >> 2012-04-16 10:34:51,368 INFO sqlalchemy.engine.base.Engine BEGIN (implicit)
> >> 2012-04-16 10:34:51,369 INFO sqlalchemy.engine.base.Engine INSERT INTO 
> >> target DEFAULT VALUES
> >> 2012-04-16 10:34:51,369 INFO sqlalchemy.engine.base.Engine ()
> >> 2012-04-16 10:34:51,369 INFO sqlalchemy.engine.base.Engine INSERT INTO 
> >> person (type, target_id) VALUES (?, ?)
> >> 2012-04-16 10:34:51,370 INFO sqlalchemy.engine.base.Engine ('Engineer', 
> >> None)
> >> 2012-04-16 10:34:51,370 INFO sqlalchemy.engine.base.Engine INSERT INTO 
> >> person (type, target_id) VALUES (?, ?)
> >> 2012-04-16 10:34:51,370 INFO sqlalchemy.engine.base.Engine ('Engineer', 1)
> >> 2012-04-16 10:34:51,371 INFO sqlalchemy.engine.base.Engine SELECT 
> >> person.id AS person_id, person.type AS person_type, person.target_id AS 
> >> person_target_id, target.id AS target_id
> >> FROM person LEFT OUTER JOIN target ON target.id = person.target_id
> >> WHERE person.type IN (?)
> >> 2012-04-16 10:34:51,371 INFO sqlalchemy.engine.base.Engine ('Engineer',)
> >> [(<__main__.Engineer object at 0x1014ff090>, None), (<__main__.Engineer 
> >> object at 0x1014ffa10>, <__main__.Target object at 0x1014ffed0>)]
>
> >> On Apr 16, 2012, at 12:17 AM, Amos wrote:
>
> >>> I am attempting to use STI with mixins on the derived/inheriting
> >>> classes. If the mixin has a simple Column, everything works great. But
> >>> when I have a ForeignKey on the Column, and therefore use the
> >>> `declared_attr` decorator, I get an exception when I attempt to query
> >>> the child collection.
>
> >>> For the example below, querying the Engineer class throws the
> >>> following exception:
>
> >>> InvalidRequestError: Class <class 'Person'> does not have a mapped
> >>> column named 'target_id'
>
> >>> Here's some code:
>
> >>>    class Person(Base):
> >>>        id = Column(Integer, primary_key=True)
> >>>        type = Column('type', String(50))
>
> >>>        @declared_attr
> >>>        def __tablename__(cls):
> >>>            if has_inherited_table(cls):
> >>>                return None
> >>>            return cls.__name__.lower()
>
> >>>        @declared_attr
> >>>        def __mapper_args__(cls):
> >>>            if cls.__name__ == 'Person':
> >>>                mapper_args = {}
> >>>                mapper_args['polymorphic_on'] = cls.type
> >>>                return mapper_args
> >>>            else:
> >>>                return {"polymorphic_identity": cls.__name__}
>
> >>>    class Mixin(object):
> >>>        simple_column = Column(Integer, nullable=True) # This column
> >>> is mapped correctly
>
> >>>        @declared_attr
> >>>        def target_id(cls): # But when I have a column with a
> >>> ForeignKey, and I used `declared_attr` - everything breaks down
> >>>            return Column('target_id', ForeignKey('target.id'))
>
> >>>        @declared_attr
> >>>        def target(cls): # Relationships also work correctly, if
> >>> target_id is defined in the Person class
> >>>            return relationship("Target")
>
> >>>    class Engineer(Person, Mixin):
> >>>        pass
>
> >>> I know I can get around this by declaring the target_id on the Person
> >>> class but then I lose the encapsulation provided by the Mixin class.
> >>> Why does declaring a simple Column on the Mixin work perfectly, but
> >>> fail when declaring a column using the `declared_attr` decorator?
>
> >>> --
> >>> 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 
> >>> athttp://groups.google.com/group/sqlalchemy?hl=en.
>
> > --
> > 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 
> > athttp://groups.google.com/group/sqlalchemy?hl=en.

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

Reply via email to