if it works, that’s fine.  as far as metaclasses I like to recommend using 
SQLAlchemy class instrumentation events instead so that there’s no complication 
re: declarative’s own metaclass and such.


On Nov 28, 2013, at 8:24 AM, Amir Elaguizy <[email protected]> wrote:

> 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 <[email protected]> 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 [email protected].
> To post to this group, send email to [email protected].
> Visit this group at http://groups.google.com/group/sqlalchemy.
> For more options, visit https://groups.google.com/groups/opt_out.

Attachment: signature.asc
Description: Message signed with OpenPGP using GPGMail

Reply via email to