orm query.update() semi-accepts InstrumentedAttributes as keys for the "values" dict

Issue #1436 resolved
Former user created an issue

Calling update() causes an exception any time that a record is affected that was previously fetched in the same session.

Observe this example code from the documentation. The last line was added to illustrate how an exception might occur:

from sqlalchemy import *

engine = create_engine('sqlite:///:memory:', echo=True)
metadata = MetaData()

users_table = Table('users', metadata,
    Column('id', Integer, primary_key=True),
    Column('name', String),
    Column('fullname', String),
    Column('password', String)
)

metadata.create_all(engine)

class User(object):
    def __init__(self, name, fullname, password):
        self.name = name
        self.fullname = fullname
        self.password = password
    def __repr__(self):
        return "<User('%s','%s', '%s')>" % (self.name, self.fullname, self.password)

from sqlalchemy.orm import mapper
mapper(User, users_table)

from sqlalchemy.orm import sessionmaker

Session = sessionmaker(bind=engine)
session = Session()

ed_user = User('ed', 'Ed Jones', 'edspassword')
session.add(ed_user)

#if this line were commented out, the next line would run successfully!
our_user = session.query(User).filter_by(name='ed').first()

#Blows up...
session.query(User).filter(User.name == 'ed').update({ User.fullname : "new fullname" })

I hope this is clear. This is the stacktrace that is produced:

  File "./sa/test/lib/python2.6/site-packages/SQLAlchemy-0.5.4p2-py2.6.egg/sqlalchemy/orm/query.py", line 1704, in update
    session.expire(session.identity_map[identity_key](identity_key), values.keys())
  File "./sa/test/lib/python2.6/site-packages/SQLAlchemy-0.5.4p2-py2.6.egg/sqlalchemy/orm/session.py", line 959, in expire
    _expire_state(state, attribute_names=attribute_names, instance_dict=self.identity_map)
  File "./sa/test/lib/python2.6/site-packages/SQLAlchemy-0.5.4p2-py2.6.egg/sqlalchemy/orm/state.py", line 217, in expire_attributes
    impl = self.manager[key](key).impl
KeyError: <sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x27fb9d0>

Comments (4)

  1. Mike Bayer repo owner

    usage of query.update() is as follows:

    session.query(User).filter(User.name == 'ed').update(dict(fullname ="new fullname" ))
    

    the InstrumentedAttribute you are sending along works by accident here since it gets passed through to table.update() as a column, but query.update() expects it to be a string.

    We can either add an error message here for non-string values, or add support for columns/attributes as keys.

  2. Former user Account Deleted

    Dan Falk here. (I opened the ticket)

    The reason it seems right to pass in the InstrumentedAttribute is, first of all that it can be passed in elsewhere i.e., User.name == 'ed' for example.

    Second, the "User.fullname" helps to specify that it's indeed the fullname property on User and not another fullname property. I'm too new to SA, so I don't know if it supports UPDATEs on JOINs so that might not be an issue here. But if it does (or plans to), it would seem to make more sense to allow the instrumented attributes, right?

    Regardless, it's good to know there's a decent workaround.

  3. Log in to comment