diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-28 22:23:13 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-28 22:23:13 -0500 |
| commit | 3b41b66981d8665282c645178643d273361eb6ad (patch) | |
| tree | 51955294b658bbf3bc0e840901d3a888b557223e /lib/sqlalchemy | |
| parent | 4f8f6b3989327398c048fa55bc2ed8f26fb022bd (diff) | |
| download | sqlalchemy-3b41b66981d8665282c645178643d273361eb6ad.tar.gz | |
- restore mapper.get_property() to use the _props dict. at the moment
synonyms for relationships might just be taken out altogether, since they aren't
documented and are of little use. a plain proxying descriptor, combined with
attribute-based usage with Query (as opposted to naming it by string)
can do the same thing more simply.
- add event support to composites, change the model around so that the composite
is generated at the point of load.
- add a recipe for tracking mutations on composites. will probably make both
of these mutations examples into extensions since they're intricate, should
have a lot of test coverage, and what they need to do is fairly straightforward.
Will use metaclasses so that no extra userland step is needed beyond usage
of the type.
Diffstat (limited to 'lib/sqlalchemy')
| -rw-r--r-- | lib/sqlalchemy/orm/descriptor_props.py | 92 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/interfaces.py | 2 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/mapper.py | 8 |
3 files changed, 65 insertions, 37 deletions
diff --git a/lib/sqlalchemy/orm/descriptor_props.py b/lib/sqlalchemy/orm/descriptor_props.py index 347f9bce9..5f974e260 100644 --- a/lib/sqlalchemy/orm/descriptor_props.py +++ b/lib/sqlalchemy/orm/descriptor_props.py @@ -66,7 +66,7 @@ class DescriptorProperty(MapperProperty): lambda: self._comparator_factory(mapper), doc=self.doc ) - + proxy_attr.property = self proxy_attr.impl = _ProxyImpl(self.key) mapper.class_manager.instrument_attribute(self.key, proxy_attr) @@ -81,6 +81,10 @@ class CompositeProperty(DescriptorProperty): self.group = kwargs.get('group', None) util.set_creation_order(self) self._create_descriptor() + + def instrument_class(self, mapper): + super(CompositeProperty, self).instrument_class(mapper) + self._setup_event_handlers() def do_init(self): """Initialization which occurs after the :class:`.CompositeProperty` @@ -88,43 +92,55 @@ class CompositeProperty(DescriptorProperty): """ 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. + """Create the Python descriptor that will serve as + the access point on instances of the mapped class. """ def fget(instance): 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 + + # key not present, assume the columns aren't + # loaded. The load events will establish + # the item. + if self.key not in dict_: + for key in self._attribute_keys: + getattr(instance, key) + + return dict_.get(self.key, None) def fset(instance, value): + dict_ = attributes.instance_dict(instance) + state = attributes.instance_state(instance) + attr = state.manager[self.key] + previous = dict_.get(self.key, attributes.NO_VALUE) + for fn in attr.dispatch.on_set: + value = fn(state, value, previous, attr.impl) + dict_[self.key] = value if value is None: - fdel(instance) + for key in self._attribute_keys: + setattr(instance, key, None) else: - 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): + state = attributes.instance_state(instance) + dict_ = attributes.instance_dict(instance) + previous = dict_.pop(self.key, attributes.NO_VALUE) + attr = state.manager[self.key] + attr.dispatch.on_remove(state, previous, attr.impl) 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 + """Propagate configuration arguments made on this composite to the target columns, for those that apply. """ @@ -137,19 +153,35 @@ class CompositeProperty(DescriptorProperty): 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. + """Establish events that populate/expire the composite attribute.""" - """ def load_handler(state): - state.dict.pop(self.key, None) + dict_ = state.dict + + if self.key in dict_: + return + + # if column elements aren't loaded, skip. + # __get__() will initiate a load for those + # columns + for k in self._attribute_keys: + if k not in dict_: + return + + dict_[self.key] = self.composite_class( + *[state.dict[key] for key in + self._attribute_keys] + ) 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) + state.dict[self.key] = self.composite_class( + *[state.dict.get(key, None) for key in + self._attribute_keys] + ) event.listen(self.parent, 'on_after_insert', insert_update_handler, raw=True) @@ -159,14 +191,6 @@ class CompositeProperty(DescriptorProperty): 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): @@ -293,10 +317,18 @@ class SynonymProperty(DescriptorProperty): self.descriptor = descriptor self.comparator_factory = comparator_factory self.doc = doc or (descriptor and descriptor.__doc__) or None + util.set_creation_order(self) - + + # TODO: when initialized, check _proxied_property, + # emit a warning if its not a column-based property + + @util.memoized_property + def _proxied_property(self): + return getattr(self.parent.class_, self.name).property + def _comparator_factory(self, mapper): - prop = getattr(mapper.class_, self.name).property + prop = self._proxied_property if self.comparator_factory: comp = self.comparator_factory(prop, mapper) diff --git a/lib/sqlalchemy/orm/interfaces.py b/lib/sqlalchemy/orm/interfaces.py index 47f63a7d6..6c512100f 100644 --- a/lib/sqlalchemy/orm/interfaces.py +++ b/lib/sqlalchemy/orm/interfaces.py @@ -168,7 +168,7 @@ class MapperProperty(object): pass - def compare(self, operator, value): + def compare(self, operator, value, **kw): """Return a compare operation for the columns represented by this ``MapperProperty`` to the given value, which may be a column value or an instance. 'operator' is an operator from diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 346d7d4bf..cfd175008 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -894,18 +894,14 @@ class Mapper(object): def get_property(self, key, _compile_mappers=True): """return a MapperProperty associated with the given key. - - Calls getattr() against the mapped class itself, so that class-level - proxies will be resolved to the underlying property, if any. - """ if _compile_mappers and _new_mappers: configure_mappers() try: - return getattr(self.class_, key).property - except AttributeError: + return self._props[key] + except KeyError: raise sa_exc.InvalidRequestError( "Mapper '%s' has no property '%s'" % (self, key)) |
