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.
signature.asc
Description: Message signed with OpenPGP using GPGMail
