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.

Reply via email to