diff options
| author | mike bayer <mike_mp@zzzcomputing.com> | 2018-08-01 23:20:01 -0400 |
|---|---|---|
| committer | Gerrit Code Review <gerrit@ci.zzzcomputing.com> | 2018-08-01 23:20:01 -0400 |
| commit | e9e869f4d557a2a4e9a57f74862b4db7493b657e (patch) | |
| tree | e5ca369d077ea151082dfde10e186b34c519006b /lib | |
| parent | 887bd34b2f5af210bfbb59947419d44b70588f4e (diff) | |
| parent | 19cd4d4bc01d6176a23c72d37634c8ee4acbac40 (diff) | |
| download | sqlalchemy-e9e869f4d557a2a4e9a57f74862b4db7493b657e.tar.gz | |
Merge "Handle association proxy delete and provide for scalar delete cascade"
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/sqlalchemy/ext/associationproxy.py | 23 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/attributes.py | 15 |
2 files changed, 32 insertions, 6 deletions
diff --git a/lib/sqlalchemy/ext/associationproxy.py b/lib/sqlalchemy/ext/associationproxy.py index a0945fa6c..3c27cb59f 100644 --- a/lib/sqlalchemy/ext/associationproxy.py +++ b/lib/sqlalchemy/ext/associationproxy.py @@ -94,7 +94,8 @@ class AssociationProxy(interfaces.InspectionAttrInfo): def __init__(self, target_collection, attr, creator=None, getset_factory=None, proxy_factory=None, - proxy_bulk_set=None, info=None): + proxy_bulk_set=None, info=None, + cascade_scalar_deletes=False): """Construct a new :class:`.AssociationProxy`. The :func:`.association_proxy` function is provided as the usual @@ -119,6 +120,15 @@ class AssociationProxy(interfaces.InspectionAttrInfo): If you want to construct instances differently, supply a 'creator' function that takes arguments as above and returns instances. + :param cascade_scalar_deletes: when True, indicates that setting + the proxied value to ``None``, or deleting it via ``del``, should + also remove the source object. Only applies to scalar attributes. + Normally, removing the proxied target will not remove the proxy + source, as this object may have other state that is still to be + kept. + + .. versionadded:: 1.3 + :param getset_factory: Optional. Proxied attribute access is automatically handled by routines that get and set values based on the `attr` argument for this proxy. @@ -150,6 +160,7 @@ class AssociationProxy(interfaces.InspectionAttrInfo): self.getset_factory = getset_factory self.proxy_factory = proxy_factory self.proxy_bulk_set = proxy_bulk_set + self.cascade_scalar_deletes = cascade_scalar_deletes self.owning_class = None self.key = '_%s_%s_%s' % ( @@ -308,9 +319,13 @@ class AssociationProxy(interfaces.InspectionAttrInfo): creator = self.creator and self.creator or self.target_class target = getattr(obj, self.target_collection) if target is None: + if values is None: + return setattr(obj, self.target_collection, creator(values)) else: self._scalar_set(target, values) + if values is None and self.cascade_scalar_deletes: + setattr(obj, self.target_collection, None) else: proxy = self.__get__(obj, None) if proxy is not values: @@ -321,7 +336,11 @@ class AssociationProxy(interfaces.InspectionAttrInfo): if self.owning_class is None: self._calc_owner(obj, None) - delattr(obj, self.key) + if self.scalar: + target = getattr(obj, self.target_collection) + if target is not None: + delattr(target, self.value_attr) + delattr(obj, self.target_collection) def _initialize_scalar_accessors(self): if self.getset_factory: diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py index e9227362e..0bbe70655 100644 --- a/lib/sqlalchemy/orm/attributes.py +++ b/lib/sqlalchemy/orm/attributes.py @@ -673,7 +673,6 @@ class ScalarAttributeImpl(AttributeImpl): def delete(self, state, dict_): - # TODO: catch key errors, convert to attributeerror? if self.dispatch._active_history: old = self.get(state, dict_, PASSIVE_RETURN_NEVER_SET) else: @@ -682,7 +681,10 @@ class ScalarAttributeImpl(AttributeImpl): if self.dispatch.remove: self.fire_remove_event(state, dict_, old, self._remove_token) state._modified_event(dict_, self, old) - del dict_[self.key] + try: + del dict_[self.key] + except KeyError: + raise AttributeError("%s object does not have a value" % self) def get_history(self, state, dict_, passive=PASSIVE_OFF): if self.key in dict_: @@ -742,7 +744,10 @@ class ScalarObjectAttributeImpl(ScalarAttributeImpl): def delete(self, state, dict_): old = self.get(state, dict_) self.fire_remove_event(state, dict_, old, self._remove_token) - del dict_[self.key] + try: + del dict_[self.key] + except: + raise AttributeError("%s object does not have a value" % self) def get_history(self, state, dict_, passive=PASSIVE_OFF): if self.key in dict_: @@ -969,7 +974,9 @@ class CollectionAttributeImpl(AttributeImpl): collection = self.get_collection(state, state.dict) collection.clear_with_event() - # TODO: catch key errors, convert to attributeerror? + + # key is always present because we checked above. e.g. + # del is a no-op if collection not present. del dict_[self.key] def initialize(self, state, dict_): |
