On Mar 27, 2012, at 6:54 PM, Rich wrote:

> Looking for suggestions on a problem I've run into from time to time.
> 
> I'm trying to use attribute events kindof like database triggers.  The
> only problem is that I only get a "before" trigger
> on an attribute event where what I really need is an "after" trigger.
> 
> For example:  Given two tables, account and account_charges, I would
> like to keep a computed total of the charges on the account.  Whenever
> a charge changes, I need to update the account.  Using sqlalchemy
> events I can watch for the change and easily change the totals on the
> account based on the difference from the charges.  However, if I want
> to do a full recalculation of the totals it becomes harder because if
> I do something like account.recalculate_charges() I get the total
> before the attribute is set.
> 
> My current solution to this problem is to use before_flush events to
> calculate the totals whenever a charge changes.  This has actually
> worked quite well for a long time, however as my application has
> gotten much, much more complex and I have many before_flush handlers
> to run, I gets very complicated to keep track of ordering and
> dependancies of my attribute changes.
> 
> Does anybody else have better techniques for handling this situation?

This issue begins to move outside of the realm of the ORM, what I usually do 
with such a thing is I invalidate the current calculated value when any of the 
dependent attributes change; then I use a memoize decorator on top of the 
actual calculated attribute itself.   So basically lazy evaluation instead of 
eval-on-change.   If you use Pyramid I believe the "@reify" decorator does a 
"memoize" kind of operation as does SQLAlchemy's @memoized_property.

The example below uses only Python itself.   With SQLAlchemy you could use the 
attribute events to handle the invalidation of the @memoized_property:

class memoized_property(object):
    """A read-only @property that is only evaluated once."""
    def __init__(self, fget, doc=None):
        self.fget = fget
        self.__doc__ = doc or fget.__doc__
        self.__name__ = fget.__name__

    def __get__(self, obj, cls):
        if obj is None:
            return self
        obj.__dict__[self.__name__] = result = self.fget(obj)
        return result

class MyClass(object):
    @property
    def a(self):
        return self.__dict__['a']

    @a.setter
    def a(self, value):
        self.__dict__.pop('c', None)
        self.__dict__['a'] = value

    @property
    def b(self):
        return self.__dict__['b']

    @b.setter
    def b(self, value):
        self.__dict__.pop('c', None)
        self.__dict__['b'] = value

    @memoized_property
    def c(self):
        print "evaluating a + b"
        return self.a + self.b


m1 = MyClass()
m1.a = 2
m1.b = 4
print m1.c
print m1.c
m1.b = 8
print m1.c

-- 
You received this message because you are subscribed to the Google Groups 
"sqlalchemy" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/sqlalchemy?hl=en.

Reply via email to