Re: [sqlalchemy] Getting the unmodified version of an object without losing changes

2013-10-24 Thread Jonathan Vanasco
I had a similar needs with generating diffs for tracking revision history a 
while back

I ended up pushing everything into a mixin class.  It worked a little like 
this...

class MutatedChecknObject(object):

def generate_diff(self):
insp = sqlalchemy_inspect(self)
rval = {}
for c in 
sqlalchemy_orm.class_mapper(self.__class__).mapped_table.c  :
h = getattr( insp.attrs , c ).history
if h[1]:
continue
rval[c] = h[0][0]
return rval
 


-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.


Re: [sqlalchemy] Getting the unmodified version of an object without losing changes

2013-10-24 Thread Daniel Grace
The problem I was encountering is that history doesn't make it easy to 
differentiate between collections and non-collections very well:

inspect(obj).attrs.intcolumn.history.unchanged returns return [intval], 
rather than intval.

inspect(obj).attrs.children.history.unchanged returns [childobj]

It'd be handy if I can just do something like:

original = get_original(self)
if original.value != self.value:
pass

With some sufficiently-robust-but-should-be-simple implementation of 
get_history.

The main problem with Jonathan's approach is the results will be 
inconsistent if any of the attributes are collections.

In my actual case at the moment, I know every attribute I'm going to look 
at is a scalar, so inspect(self).attrs.target_attribute.non_added()[0] 
works (provided this was loaded from the database at all and isn't a new 
instance of the model).  The problem is that I can't make easily write a 
general case for this, because the [0] is required for scalars, but will 
completely break when applied to any attribute that's a collection.

Michael: By Losing changes, I mean that I know it's easily possible to 
get to the unmodified version of an object via session.rollback() or 
session.expire() -- but then I lose the pending (not yet committed) changes 
that I'm trying to compare against.  I wasn't referring to autoflush.

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.


Re: [sqlalchemy] Getting the unmodified version of an object without losing changes

2013-10-24 Thread Michael Bayer

On Oct 24, 2013, at 4:40 PM, Daniel Grace thisgenericn...@gmail.com wrote:

 The problem I was encountering is that history doesn't make it easy to 
 differentiate between collections and non-collections very well:
 
 inspect(obj).attrs.intcolumn.history.unchanged returns return [intval], 
 rather than intval.
 
 inspect(obj).attrs.children.history.unchanged returns [childobj]

the rationale for this is because the history API was first and foremost 
designed for internals, where it iterates through these collections without 
regard for whether the collection is a many-to-one or one-to-many.   If you 
want to distinguish between scalars and collections, this could be added to 
attribute state directly but for now you can look at 
state.manager[key].collection, which is a boolean.


 
 It'd be handy if I can just do something like:
 
 original = get_original(self)
 if original.value != self.value:
 pass

history.has_changes() tells you the equivalent of if the “old” and “new” values 
are different.


 With some sufficiently-robust-but-should-be-simple implementation of 
 get_history.

there’s many “simple” implementations that wouldn’t do what everyone needs - 
the API you want might not accomplish what someone else wants - which means if 
we rush ahead and implement a “simple” API that doesn’t take enough use cases 
into account, we get two apis instead of one, and new users will still want 
more APIs - this is how confusing and inconsistent APIs are produced, e.g. 
piecemeal in response to individual requests, rather than designing for a 
bigger picture.   The existing API is actually the “simple-yet-robust” 
implementation for what this system was originally designed for.

Once I have dozens of people asking me for the same thing, or different things 
for which I can discern exactly what everyone really needs (e.g. when the “big 
picture” is very clear), then the “simple” API becomes apparent.  We’re not 
there yet, sorry.  We’re only at the “it’s possible” phase on attribute history.

 The main problem with Jonathan's approach is the results will be inconsistent 
 if any of the attributes are collections.

well his approach is iterating through the table’s columns, which is of limited 
use.  The loop I illustrated iterates through all mapped attributes including 
collections which takes more into account.

If you care to illustrate a function with clear inputs and outputs, I can work 
up whatever kind of implementation it needs internally for you, or at least 
point out those areas where more design is needed (such as how to generate 
“diffs” of collections).   

 
 Michael: By Losing changes, I mean that I know it's easily possible to get 
 to the unmodified version of an object via session.rollback() or 
 session.expire() -- but then I lose the pending (not yet committed) changes 
 that I'm trying to compare against.  I wasn't referring to auto flush.

Well if you are looking to actually re-construct the entire object graph from 
memory as it was before the flush,  this is something I looked into with great 
detail some years ago and determined it would be enormously complex to do 
completely - it is of course simple for scalar attributes but once you start 
dealing with collections and bidirectional connections between objects, 
reproducing the full state of objects prior to user and within-flush 
manipulation of state becomes a major task, which also leans towards adding a 
lot more overhead to the history tracking system in the first place which 
starts to degrade performance even further than the instrumentation already 
does.
 
If you want to maintain your own “diff” of changes as they are produced, which 
is entirely independent of flush(), you can do this - just implement attribute 
event listening for all attributes, where you’ll be able to log every change to 
an object graph into the format that you need.   This is not trivial but you’d 
have full control over things.   Instrumentation for all attributes is 
illustrated in the examples/custom_attributes/listen_for_events.py example.




-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.


[sqlalchemy] Getting the unmodified version of an object without losing changes

2013-10-23 Thread Daniel Grace
I have a situation where an object in the ORM performs some calculations 
and other assorted checks.  One of these situations is checking to see the 
difference between the original value of an attribute and the current value 
-- in particular, some of the validation going on should prohibit a change 
that causes a particular check to fail, but only if that check wasn't 
already failing (e.g. some legacy)

I see that I can use inspect() to get the history of attributes, but that 
quickly gets unwieldy when working with a large number of attributes.  What 
I'd love to be able to do is something like:

def do_something(self):
orig = get_original_version(self)
delta = self.value - orig.value
# 

Is there a simple way to accomplish this?  Right now I'm doing something 
like:

orig_value, = inspect(self).attrs.value.history.non_added or 
(default_value, )

which seems messy and only works for scalar values.

-- Daniel

-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.


Re: [sqlalchemy] Getting the unmodified version of an object without losing changes

2013-10-23 Thread Michael Bayer

On Oct 23, 2013, at 8:06 PM, Daniel Grace thisgenericn...@gmail.com wrote:

 I have a situation where an object in the ORM performs some calculations and 
 other assorted checks.  One of these situations is checking to see the 
 difference between the original value of an attribute and the current value 
 -- in particular, some of the validation going on should prohibit a change 
 that causes a particular check to fail, but only if that check wasn't already 
 failing (e.g. some legacy)
 
 I see that I can use inspect() to get the history of attributes, but that 
 quickly gets unwieldy when working with a large number of attributes.  What 
 I'd love to be able to do is something like:
 
 def do_something(self):
 orig = get_original_version(self)
 delta = self.value - orig.value
 # 
 
 Is there a simple way to accomplish this?  Right now I'm doing something like:
 
 orig_value, = inspect(self).attrs.value.history.non_added or (default_value, )
 
 which seems messy and only works for scalar values.

a phrase like “unwieldy with a large number of attributes” doesn’t make sense 
to me, is this a collection of attributes in a list or something, in which case 
iterate through that list and call getattr(attrs, attrname).history?   if an 
API is verbose, just put a function around it that does what you need.  if as 
implied in the subject that some side effect is occurring that’s causing a 
flush to occur hence “losing changes”, probably run your operations within a 
session.no_autoflush: block 
(http://docs.sqlalchemy.org/en/rel_0_8/orm/session.html?highlight=no_autoflush#sqlalchemy.orm.session.Session.no_autoflush).

history works for collections as well.   this is the fine grained API to get at 
attribute history before a flush and attrs supports iteration through all 
available attributes, so just use a loop like this:

def delta(obj):
d = {}
for attr in inspect(obj).attrs:
if attr.history.has_changes():
d[attr.key] = attr.history.added, attr.history.deleted
return d

im not sure what exactly you’d like to see for collections, the above will just 
put a tuple of (things added), (things removed) for that entry in the 
dictionary.   



-- 
You received this message because you are subscribed to the Google Groups 
sqlalchemy group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to sqlalchemy+unsubscr...@googlegroups.com.
To post to this group, send email to sqlalchemy@googlegroups.com.
Visit this group at http://groups.google.com/group/sqlalchemy.
For more options, visit https://groups.google.com/groups/opt_out.