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!

On Apr 16, 7:35 am, Michael Bayer <mike...@zzzcomputing.com> 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 sqlalchemy@googlegroups.com.
> > To unsubscribe from this group, send email to 
> > sqlalchemy+unsubscr...@googlegroups.com.
> > 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 sqlalchemy@googlegroups.com.
To unsubscribe from this group, send email to 
sqlalchemy+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to