What do you think about the pattern I've implemented for this purpose using 
metaclasses?

https://gist.github.com/aelaguiz/7691751

I've also pasted the code here but it's nicer to look at on github via the 
link above:

import logging
import sqlalchemy as sqla
import sqlalchemy.ext.declarative as decl

from .signals import signaler

log = logging.getLogger(u"cratejoy")


class _hooked(object):
    def __init__(self, validate_func, normalize_func, field_name, 
private_name):
        self.validate_func = validate_func
        self.normalize_func = normalize_func
        self.field_name = field_name
        self.private_name = private_name

    def __get__(self, instance, owner):
        if not instance:
            return getattr(owner, self.private_name)

        val = getattr(instance, self.private_name)

        return val

    def __set__(self, instance, val):
        namespace = instance.__class__.__name__ + "." + self.field_name

        if self.normalize_func:
            val = self.normalize_func(val)

        if self.validate_func:
            assert self.validate_func(val)

        old_value = None
        if hasattr(instance, self.private_name):
            old_value = getattr(instance, self.private_name)

        signaler.signal(namespace + ":before_update", instance=instance, 
new_value=val, old_value=old_value)
        setattr(instance, self.private_name, val)
        signaler.signal(namespace + ":after_update", instance=instance, 
new_value=val, old_value=old_value)


class DispatchingModelMeta(decl.DeclarativeMeta):
    def __new__(cls, name, bases, attrs):
        new_attrs = {}
        for key, val in attrs.iteritems():
            if isinstance(val, sqla.Column):
                log.debug(u"{} Column {} {}".format(name, key, val))
                if not val.name:
                    val.name = key

                val.key = key

                validator_name = 'validate_' + key
                normalize_name = 'normalize_' + key
                private_name = '_' + key

                validator_func = None
                normalize_func = None

                if validator_name in attrs:
                    validator_func = attrs[validator_name]

                if normalize_name in attrs:
                    normalize_func = attrs[normalize_name]

                new_attrs[private_name] = val
                new_attrs[key] = _hooked(validate_func=validator_func, 
normalize_func=normalize_func, field_name=key, private_name=private_name)
            else:
                new_attrs[key] = val

        return super(DispatchingModelMeta, cls).__new__(cls, name, bases, 
new_attrs)


On Wednesday, November 27, 2013 6:26:58 PM UTC-6, Michael Bayer wrote:
>
>
> On Nov 27, 2013, at 12:48 PM, Amir Elaguizy <aela...@gmail.com<javascript:>> 
> wrote: 
>
> > If I have a model like: 
> > 
> > class Test(Base): 
> >     value = sqlalchemy.Column(db.String) 
> > 
> > and I have a function like: 
> > 
> > def on_value_change(model, oldValue): 
> >     # Do stuff 
> > 
> > 
> > I'd like on_value_change called *after* Test.value has been changed 
>
> yeah there’s been a bit of discussion about that but it isn’t present in a 
> simple way.   attribute mechanics already take up a lot of overhead and add 
> lots of complexity so adding an “after” event isn’t something I’m in a 
> hurry to do. 
>
> In the rare occasions that I need this, sometimes what I will do is, just 
> set the value within the before event, then work with it - I haven’t done 
> this much so YMMV: 
>
> @event.listens_for(A.data, "set") 
> def set(target, value, oldvalue, initiator): 
>     target.__dict__['data'] = value 
>     work_with(target) 
>     return value 
>
> the reason __dict__ is used is otherwise you trigger an endless loop with 
> the event.     The reason doing things in this way is dangerous (and why 
> it’s extra hard to make this work) is that if you pass around “target” to 
> other parts of your app, which are themselves doing things with attributes, 
> now you have a nesting pattern going on that can easily enter more endless 
> recursion types of issues.   
>
> usually what I’ll do is just stick to simple things and use a descriptor 
> like a synonym or a hybrid to set the value, which does what it needs after 
> the set event. that’s pretty much the normal Python way of doing this sort 
> of thing in any case.    Attribute events are in particular tailored 
> towards validating / processing the immediate value given, not so much 
> calling out into the bigger ecosystem of the application, as it is already 
> occurring within a critical part of the attribute mechanics. 
>
>
>

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

Reply via email to