summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authormike bayer <mike_mp@zzzcomputing.com>2018-08-01 23:20:01 -0400
committerGerrit Code Review <gerrit@ci.zzzcomputing.com>2018-08-01 23:20:01 -0400
commite9e869f4d557a2a4e9a57f74862b4db7493b657e (patch)
treee5ca369d077ea151082dfde10e186b34c519006b /lib
parent887bd34b2f5af210bfbb59947419d44b70588f4e (diff)
parent19cd4d4bc01d6176a23c72d37634c8ee4acbac40 (diff)
downloadsqlalchemy-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.py23
-rw-r--r--lib/sqlalchemy/orm/attributes.py15
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_):