Mixin relationship defaulting to uselist=False unexpectedly

531 views
Skip to first unread message

David Bowser

unread,
Apr 24, 2012, 1:16:09 PM4/24/12
to sqlal...@googlegroups.com
I have a mixin as follows designed to remove a bunch of copy paste relationship code.

class UserMixin(object):
@_declared_attr
def user_id(cls):
return sa.Column('user_id',typ.Integer,sa.ForeignKey("tg_user.user_id", ondelete="CASCADE"),nullable=False)

@_declared_attr
def user(cls):
return orm.relationship("User",primaryjoin='%s.user_id == User.user_id'%cls.__name__)

@_declared_attr
def user_profile(cls):
return orm.relationship('UserProfile',primaryjoin="%s.user_id == UserProfile.id"%cls.__name__,
foreign_keys=lambda:[metadata.tables['user_profiles'].c.id],
backref=cls.__tablename__ if cls.__tablename__ else tablename(cls.__name__))

when I use my mixin in say a class like this:

class JournalEntry(UserMixin,DeclarativeBase):
__tablename__ = 'journal_entries'
#{ Columns
id = Column(Integer,primary_key=True)
is_private = Column(Boolean,default=False)
modified = Column(DateTime,onupdate=datetime.datetime.now)
text = Column(UnicodeText)
#}

And then I:

>>> from myProject.model import *
>>> profile = DBSession.query(UserProfile).get(1)
>>> profile.journal_entries

I keep getting the error:

/Users/Me/Documents/Work/Projects/myProject/myProject/lib/python2.7/site-packages/SQLAlchemy-0.7.6-py2.7-macosx-10.7-intel.egg/sqlalchemy/orm/strategies.py:508: SAWarning: Multiple rows returned with uselist=False for lazily-loaded attribute 'UserProfile.journal_entries'
< myProject.model.user_data.JournalEntry object at 0x10d8b26d0>

I can't figure out why it's using uselist=False, and a joined lazy load. Any help pointing me in the right direction would be great.

~Dave


Michael Bayer

unread,
Apr 24, 2012, 3:36:57 PM4/24/12
to sqlal...@googlegroups.com

it's probably that foreign_keys in your relationship there, which appears to refer to user_profiles.c.id. that's the column which is referred to here, and that parameter would instead be referring to the "user_id" column, i.e. the one that has the foreign key back to the parent row. It's equivalent to the column that would have ForeignKey set up on it here; I guess you have some multiple foreign keys happening here? tg_user.user_id is not the same as UserProfile.id ? This suggests some potential design problems, what is the value of JournalEntries.user_id if it points to row "3" in tg_user.user_id and row "5" in user_profile.id ?


David Bowser

unread,
Apr 24, 2012, 4:42:21 PM4/24/12
to sqlal...@googlegroups.com

The table tg_user only contains the authentication/access data for the user, and is queried extremely frequently. The table user_profile contains, well everything else in the profile that isn't needed very often. For any user with user_id n, that user's user_profile id is also n.

Easy Fix though once you pointed out that was the problem.

Dropped the second relationship, and used joined table inheritance without a discriminator, and the orm is now happy.

Still confused about how it ended getting a uselist=False on the backref for that tho.

Michael Bayer

unread,
Apr 24, 2012, 5:50:06 PM4/24/12
to sqlal...@googlegroups.com

On Apr 24, 2012, at 4:42 PM, David Bowser wrote:

>
> The table tg_user only contains the authentication/access data for the user, and is queried extremely frequently. The table user_profile contains, well everything else in the profile that isn't needed very often. For any user with user_id n, that user's user_profile id is also n.
>
> Easy Fix though once you pointed out that was the problem.
>
> Dropped the second relationship, and used joined table inheritance without a discriminator, and the orm is now happy.
>
> Still confused about how it ended getting a uselist=False on the backref for that tho.

one-to-many is defined as parent->child where child has a foreign key column referring to parent; many-to-one is the reverse, where parent has a foreign key that refers to child. When you tell the ORM "foreign_keys = [some_col_on_parent]", that tells SQLA that the parent refers to the child, hence many-to-one, hence uselist is set to False.

David Bowser

unread,
Apr 24, 2012, 5:59:03 PM4/24/12
to sqlal...@googlegroups.com

On Apr 24, 2012, at 5:50 PM, Michael Bayer <mik...@zzzcomputing.com> wrote:

one-to-many is defined as parent->child where child has a foreign key column referring to parent; many-to-one is the reverse, where parent has a foreign key that refers to child.  When you tell the ORM "foreign_keys = [some_col_on_parent]", that tells SQLA that the parent refers to the child, hence many-to-one, hence uselist is set to False.

My apologies for not being precise.

I stated in the original message that the warning is occurring when using the backref. So it defines many entries -> one profile fine, but when I used the backref it still had uselist set to False.


Michael Bayer

unread,
Apr 24, 2012, 6:14:22 PM4/24/12
to sqlal...@googlegroups.com
UserMixin.user_id -> JournalEntry.id , foreign key is established as JournalEntry.id via the "foreign_keys" argument which propagates by default to the backref, so UserMixin.user_profile evaluates as one-to-many, hence uselist=True, JournalEntry.journal_entries evaluates as many-to-one, hence uselist=False.

If you enable INFO logging for the "sqlalchemy.orm" logger, it will output the "direction" that it picks up on for all relationships.


David Bowser

unread,
Apr 24, 2012, 7:18:38 PM4/24/12
to sqlal...@googlegroups.com
Look at that again:

   @_declared_attr
   def user_profile(cls):
       return orm.relationship('UserProfile',primaryjoin="%s.user_id == UserProfile.id"%cls.__name__,
               foreign_keys=lambda:[metadata.tables['user_profiles'].c.id],
               backref=cls.__tablename__ if cls.__tablename__ else tablename(cls.__name__))

UserMixin.user_id -> UserProfile.id

The foreign_key is set as UserProfile.id, which should make JournalEntry.user_profile evaluate as one-to-many as per your statement above. UserProfile.journal_entries should be many-to-one.

That is exactly what happens....

Relevant Log Lines with that code:

19:07:35,441 INFO  [sqlalchemy.orm.mapper.Mapper] (JournalEntry|journal_entries) initialize prop user_profile
19:07:35,441 INFO  [sqlalchemy.orm.properties.RelationshipProperty] JournalEntry.user_profile setup primary join journal_entries.user_id = user_profiles.id
19:07:35,442 INFO  [sqlalchemy.orm.mapper.Mapper] (UserProfile|user_profiles) _configure_property(journal_entries, RelationshipProperty)
19:07:35,442 INFO  [sqlalchemy.orm.properties.RelationshipProperty] JournalEntry.user_profile local/remote pairs [(journal_entries.user_id / user_profiles.id)]
19:07:35,442 INFO  [sqlalchemy.orm.properties.RelationshipProperty] JournalEntry.user_profile relationship direction <symbol 'ONETOMANY>
19:07:35,442 INFO  [sqlalchemy.orm.properties.RelationshipProperty] JournalEntry.user_profile secondary synchronize pairs []
19:07:35,442 INFO  [sqlalchemy.orm.properties.RelationshipProperty] JournalEntry.user_profile setup secondary join None
19:07:35,442 INFO  [sqlalchemy.orm.properties.RelationshipProperty] JournalEntry.user_profile synchronize pairs [(journal_entries.user_id => user_profiles.id)]
19:07:35,442 INFO  [sqlalchemy.orm.properties.RelationshipProperty] UserProfile.journal_entries setup primary join journal_entries.user_id = user_profiles.id
19:07:35,442 INFO  [sqlalchemy.orm.properties.RelationshipProperty] UserProfile.journal_entries setup secondary join None
19:07:35,443 INFO  [sqlalchemy.orm.properties.RelationshipProperty] UserProfile.journal_entries local/remote pairs [(user_profiles.id / journal_entries.user_id)]
19:07:35,443 INFO  [sqlalchemy.orm.properties.RelationshipProperty] UserProfile.journal_entries relationship direction <symbol 'MANYTOONE>
19:07:35,443 INFO  [sqlalchemy.orm.properties.RelationshipProperty] UserProfile.journal_entries secondary synchronize pairs []
19:07:35,443 INFO  [sqlalchemy.orm.properties.RelationshipProperty] UserProfile.journal_entries synchronize pairs [(journal_entries.user_id => user_profiles.id)]

and yet I still get:

>>> profile.journal_entries
/Users/Me/Documents/Work/Projects/myProject/myProject/lib/python2.7/site-packages/SQLAlchemy-0.7.6-py2.7-macosx-10.7-intel.egg/sqlalchemy/orm/strategies.py:508: SAWarning: Multiple rows returned with uselist=False for lazily-loaded attribute 'UserProfile.journal_entries' 
<myProject.model.user_data.JournalEntry object at 0x10ba21210>

Which leaves me really confused...

~Dave
 


David Bowser

unread,
Apr 24, 2012, 7:22:09 PM4/24/12
to sqlal...@googlegroups.com
Never mind, That log show it forming as a one to many the wrong way. I confused between that and the updated version that worked, when I flipping back and forth in my version control.

However the one-to-many foreignkey matches your explaination.

It's not a pressing issue, and I can let it drop since I have working solution anyways, it just seems off.

~Dave

Michael Bayer

unread,
Apr 24, 2012, 7:30:42 PM4/24/12
to sqlal...@googlegroups.com
sorry, I'm getting names mixed up because your example is not complete and it slipped my mind that JournalEntry extends UserMixin.   Above, UserProfile.journal_entries, primaryjoin is user_profiles_id->journal_entries.user_id, foreign key is user_profiles.id, which is local to UserProfile - many to one.


Reply all
Reply to author
Forward
0 new messages