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#mixing-in-relationships

but I might add my non-string version to that.



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

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