diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-25 14:08:03 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-25 14:08:03 -0500 |
commit | 7dbb6bffc20bccb6e7087f9cfddb8463d9178204 (patch) | |
tree | 4e99df724be8b07beb47c5740481f088ddd35450 /lib/sqlalchemy/orm/descriptor_props.py | |
parent | 9c7b00455954239dd2f8f9813fb1c024f4202ebf (diff) | |
download | sqlalchemy-7dbb6bffc20bccb6e7087f9cfddb8463d9178204.tar.gz |
- on_expire event, since we are starting to build off of events around
full lifecycle
- composite will use events to do everything we want it to, i.e.
storing the composite in __dict__, invalidating it on change
of any of the columns.
- will reinstate mutability of composites via callable attached
to the composite - but userland code will still need to establish
change event listening on the composite itself, perhaps via
a "mutable" mixin like the scalars.py example, perhaps via
addition of descriptors to the mutable object.
Diffstat (limited to 'lib/sqlalchemy/orm/descriptor_props.py')
-rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 92 |
1 files changed, 74 insertions, 18 deletions
diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 6fb2d2c57..4c4bc821f 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -15,7 +15,7 @@ build on the "hybrid" extension to produce class descriptors. from sqlalchemy.orm.interfaces import \ MapperProperty, PropComparator, StrategizedProperty from sqlalchemy.orm import attributes -from sqlalchemy import util, sql, exc as sa_exc +from sqlalchemy import util, sql, exc as sa_exc, event from sqlalchemy.sql import expression properties = util.importlater('sqlalchemy.orm', 'properties') @@ -96,28 +96,94 @@ class CompositeProperty(DescriptorProperty): self.deferred = kwargs.get('deferred', False) self.group = kwargs.get('group', None) util.set_creation_order(self) + self._create_descriptor() + def do_init(self): + """Initialization which occurs after the :class:`.CompositeProperty` + has been associated with its parent mapper. + + """ + self._setup_arguments_on_columns() + self._setup_event_handlers() + + def _create_descriptor(self): + """Create the actual Python descriptor that will serve as + the access point on the mapped class. + + """ + def fget(instance): - # this could be optimized to store the value in __dict__, - # but more complexity and tests would be needed to pick - # up on changes to the mapped columns made independently - # of those on the composite. - return self.composite_class( + dict_ = attributes.instance_dict(instance) + if self.key in dict_: + return dict_[self.key] + else: + dict_[self.key] = composite = self.composite_class( *[getattr(instance, key) for key in self._attribute_keys] ) + return composite def fset(instance, value): if value is None: fdel(instance) else: - for key, value in zip(self._attribute_keys, value.__composite_values__()): + dict_ = attributes.instance_dict(instance) + dict_[self.key] = value + for key, value in zip( + self._attribute_keys, + value.__composite_values__()): setattr(instance, key, value) def fdel(instance): for key in self._attribute_keys: setattr(instance, key, None) + self.descriptor = property(fget, fset, fdel) - + + def _setup_arguments_on_columns(self): + """Propigate configuration arguments made on this composite + to the target columns, for those that apply. + + """ + for col in self.columns: + prop = self.parent._columntoproperty[col] + prop.active_history = self.active_history + if self.deferred: + prop.deferred = self.deferred + prop.strategy_class = strategies.DeferredColumnLoader + prop.group = self.group + + def _setup_event_handlers(self): + """Establish events that will clear out the composite value + whenever changes in state occur on the target columns. + + """ + def load_handler(state): + state.dict.pop(self.key, None) + + def expire_handler(state, keys): + if keys is None or set(self._attribute_keys).intersection(keys): + state.dict.pop(self.key, None) + + def insert_update_handler(mapper, connection, state): + state.dict.pop(self.key, None) + + event.listen(self.parent, 'on_after_insert', + insert_update_handler, raw=True) + event.listen(self.parent, 'on_after_update', + insert_update_handler, raw=True) + event.listen(self.parent, 'on_load', load_handler, raw=True) + event.listen(self.parent, 'on_refresh', load_handler, raw=True) + event.listen(self.parent, "on_expire", expire_handler, raw=True) + + # TODO: add listeners to the column attributes, which + # refresh the composite based on userland settings. + + # TODO: add a callable to the composite of the form + # _on_change(self, attrname) which will send up a corresponding + # refresh to the column attribute on all parents. Basically + # a specialization of the scalars.py example. + + @util.memoized_property def _attribute_keys(self): return [ @@ -155,16 +221,6 @@ class CompositeProperty(DescriptorProperty): (),[self.composite_class(*added)], () ) - def do_init(self): - for col in self.columns: - prop = self.parent._columntoproperty[col] - prop.active_history = self.active_history - if self.deferred: - prop.deferred = self.deferred - prop.strategy_class = strategies.DeferredColumnLoader - prop.group = self.group - # strategies ... - def _comparator_factory(self, mapper): return CompositeProperty.Comparator(self) |