Assigning new item to inherited relation causes error

Issue #500 resolved
Former user created an issue

The attached test case demonstrates the problem. Email has a relation with User, and Companymail inherits from Email, as declared in companymapper.

If in the subclass we now assign a different user via the user-email relation, SQLAlchemy complains with the following traceback. This happens only if a user has been previously assigned to an Email instance, and if we perform the assignment via the subclass.

$ python inheritance.py
Acme, Inc. a@foo Paul
Traceback (most recent call last):
  File "inheritance.py", line 73, in ?
    c.user = newuser  # <-------- changing relation: this bombs out
  File "/home/cvogler/src/taubenschlag/aggregator/turbogears/lib/python2.4/SQLAlchemy-0.3.5-py2.4.egg/sqlalchemy/orm/attributes.py", line 42, in __set__
    self.set(None, obj, value)
  File "/home/cvogler/src/taubenschlag/aggregator/turbogears/lib/python2.4/SQLAlchemy-0.3.5-py2.4.egg/sqlalchemy/orm/attributes.py", line 242, in set
    ext.set(event or self, obj, value, old)
  File "/home/cvogler/src/taubenschlag/aggregator/turbogears/lib/python2.4/SQLAlchemy-0.3.5-py2.4.egg/sqlalchemy/orm/attributes.py", line 502, in set
    getattr(oldchild.__class__, self.key).remove(event, oldchild, obj)
  File "/home/cvogler/src/taubenschlag/aggregator/turbogears/lib/python2.4/SQLAlchemy-0.3.5-py2.4.egg/sqlalchemy/orm/attributes.py", line 285, in remove
    self.get(obj).remove_with_event(value, event)
  File "/home/cvogler/src/taubenschlag/aggregator/turbogears/lib/python2.4/SQLAlchemy-0.3.5-py2.4.egg/sqlalchemy/orm/attributes.py", line 411, in remove_with_event
    self.data.remove(item)
ValueError: list.remove(x): x not in list

Comments (2)

  1. Mike Bayer repo owner

    this error is ultimately caused by your "emails" collection receiving an object of type Companymail, which since it is managed by a non-polymorphic Email mapper, it later loads the instance as a plain Email object when negotiating the bi-directional relationship (an Email instance distinct from the already-loaded Companymail instance). It then fails to remove the old Companymail instance from the original User since its the Email instance that is actually in the collection.

    To detect the incorrect loading of the Companymail as an Email is impossible, since nothing about the row identifies it as a Companymail row and would require that the correct instance already be loaded in the session, which we cant assume is the case (and would be an extremely expensive operation anyway).

    So the only point at which this issue can be reasonably detected is at flush time, so I have added a check there. as of changeset:2382 the test program will now raise:

    sqlalchemy.exceptions.FlushError: Attempting to flush an item of type <class '__main__.Companymail'> on collection 'User.email (Email)', which is handled by mapper 'Mapper|Email|xemails' and does not load items of that type.  Did you mean to use a polymorphic mapper for this relationship ?
    

    The two mappings which will alleviate this issue are:

    polymorphic:

    punion = polymorphic_union(
        {"email":emails.outerjoin(companymail).select(companymail.c.company_id == None),
        "companies":emails.join(companymail),},
        "type", "punion"
    )
    emailmapper = mapper(Email, emails, select_table=punion, polymorphic_identity="email", polymorphic_on=punion.c.type)
    companymapper = mapper(Companymail, companymail, inherits=emailmapper, polymorphic_identity="companies")
    usermapper = mapper(User, users, properties = {'email': relation(emailmapper, backref='user')})
    

    Or more easily, just remove the email collection (since it wasnt going to load correctly anyway):

    usermapper = mapper(User, users)
    emailmapper = mapper(Email, emails, properties={'user':relation(usermapper)})
    companymapper = mapper(Companymail, companymail, inherits=emailmapper, )
    
  2. Log in to comment