On Oct 4, 2010, at 5:32 PM, Ted Turocy wrote:

> 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 :)

Its not public because the "public" use case would have to be nailed down very 
well first.  we like to identify use cases first so that we can come up with 
the most useful version of a feature, rather than exposing internals.     
However, this particular attribute is already semi-public in that you can 
create an attribute listener that turns it on.

> 
>> 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?

there's no other downside to turning off that flag.   If your app tends to 
finish what it's doing after a commit then there's usually no reason to have it 
on.

> 
> 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.
> 

-- 
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.

Reply via email to