Hi, On Sep 27, 11:53 pm, Michael Bayer <mike...@zzzcomputing.com> wrote: > This is a limitation of is_modified(), and it is definitely a bug here that > this limitation is not documented (ticket 1928).
Thanks. I could probably be prevailed upon to submit a documentation patch once I'm sure I understand everything and know what to write. The explanation makes sense, and is what I had inferred was going on. The reasons for why it is the way it is are OK by me. It was more my confusion and surprise at the way the behavior was, especially since it wasn't mentioned in the documentation -- and because at least one of the sample extensions on versioning would be affected by this behavior in principle. Let me ask after some workarounds: > A workaround is to enable full history tracking for the attribute, so that > the "old" value is loaded upon a set event. This is not public API at the > moment but it would look like: > > Spam.cans.impl.active_history = True Since I want to know about changes to just about all attributes, this doesn't really scale well (aside from not wanting to trust something not *yet* in the public API :) > or, set expire_on_commit=False on your session. Let me see if I understand the implications of this. The downside risk of expire_on_commit=False is concurrency; if there are changes being written to the same record elsewhere, there is a risk of unwittingly carrying around stale data. Is there any other downside to expire_on_commit? It seems a third suggestion would be simply not to re-use instances across transactions, and instead always start fresh by pulling rows into objects via queries in each new transaction. In my case, that ought to work just fine, and would require just about no changes to my existing code. Ted > > The "active_history" feature should become public API at some point. The > alternative notion of modifying attributes.get_history() to get the "old" > value to load from a SELECT at the point of history, rather than upon > attribute set, would be doable but would require more intensive changes, as > we'd have to break out the "load the old value" behavior into a separate > function that calls upon loader callables in such a way that they know not to > populate the current instance dictionary. The feature would still not be > ideal as a default within the ORM since you'd see many more SELECTs being > emitted within a typical flush() against a set of persistent, modified > objects, most of which are unnecessary. > > > > > > > ----- > > import sqlalchemy > > import sqlalchemy.orm > > from sqlalchemy import Table, Column, Integer > > > class Spam(object): > > def __init__(self, cans): > > self.cans = cans > > > engine = sqlalchemy.create_engine('sqlite:///:memory:') > > Session = sqlalchemy.orm.sessionmaker(autocommit=False, bind=engine) > > session = Session() > > meta = sqlalchemy.MetaData() > > meta.bind = engine > > > spam_table = Table('spam', meta, > > Column('id', Integer, primary_key=True), > > Column('cans', Integer) > > ) > > > meta.create_all(engine) > > > sqlalchemy.orm.mapper(Spam, spam_table) > > > # Setup finished. Now test things out > > instance = Spam(42) > > session.add(instance) > > session.commit() > > > print "Cans of spam = ", instance.cans > > instance.cans = 42 > > print "Has instance been modified? (Should be False): ", > > session.is_modified(instance) > > session.commit() > > > instance.cans = 42 > > print "We have made no net change to the instance..." > > print "Has instance been modified? (Should be False): ", > > session.is_modified(instance) > > ----- > > > When I run this, I get: > > > ----- > > Cans of spam = 42 > > Has instance been modified? (Should be False): False > > We have made no net change to the instance... > > Has instance been modified? (Should be False): True > > ----- > > > That's surprising to me. In both cases, no net change is being made to > > the instance. > > > Noodling around further, it appears that this is triggered by re-using > > the same instance object, and doing another __setattr__ on the column > > without having done a __getattr__ on it earlier. I suspect, without > > having dug around, that after the commit, the instance's attribute > > values are marked as needing to be re-loaded, but they are not being > > re-loaded first in order to check whether the new value is in fact > > different than the existing one. > > > I've also noticed if I re-obtain the instance via a query using .get() > > on the primary key, the behavior is the expected one, again presumably > > because the query operation is loading in all the column values. > > > Am I missing something here? > > > Ted > > > -- > > You received this message because you are subscribed to the Google Groups > > "sqlalchemy" group. > > To post to this group, send email to sqlalch...@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 sqlalch...@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.