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.