summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/attributes.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2017-01-25 11:51:04 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2017-03-16 17:16:49 -0400
commit9974e9a46bdf6c570c650aa911b76c2dcfd9327b (patch)
tree5631c6d247855cb8572d6c634987f23e6c068e0d /lib/sqlalchemy/orm/attributes.py
parent63a7b2d2d9402b06f9bc7745eed2d98ae9f8b11c (diff)
downloadsqlalchemy-9974e9a46bdf6c570c650aa911b76c2dcfd9327b.tar.gz
Add bulk_replace event, integrate with @validates
Added new attribute event :meth:`.AttributeEvents.bulk_replace`. This event is triggered when a collection is assigned to a relationship, before the incoming collection is compared with the existing one. This early event allows for conversion of incoming non-ORM objects as well. The event is integrated with the ``@validates`` decorator. The ``@validates`` decorator now allows the decorated method to receive objects from a "bulk collection set" operation that have not yet been compared to the existing collection. This allows incoming values to be converted to compatible ORM objects as is already allowed from an "append" event. Note that this means that the ``@validates`` method is called for **all** values during a collection assignment, rather than just the ones that are new. Change-Id: I27f59db008d9e521d31a3e30143d7cd997e4b7b3 Fixes: #3896
Diffstat (limited to 'lib/sqlalchemy/orm/attributes.py')
-rw-r--r--lib/sqlalchemy/orm/attributes.py21
1 files changed, 14 insertions, 7 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index fc81db782..2b8b38d58 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -324,6 +324,7 @@ def create_proxied_attribute(descriptor):
OP_REMOVE = util.symbol("REMOVE")
OP_APPEND = util.symbol("APPEND")
OP_REPLACE = util.symbol("REPLACE")
+OP_BULK_REPLACE = util.symbol("BULK_REPLACE")
class Event(object):
@@ -348,8 +349,9 @@ class Event(object):
:var impl: The :class:`.AttributeImpl` which is the current event
initiator.
- :var op: The symbol :attr:`.OP_APPEND`, :attr:`.OP_REMOVE` or
- :attr:`.OP_REPLACE`, indicating the source operation.
+ :var op: The symbol :attr:`.OP_APPEND`, :attr:`.OP_REMOVE`,
+ :attr:`.OP_REPLACE`, or :attr:`.OP_BULK_REPLACE`, indicating the
+ source operation.
"""
@@ -1062,6 +1064,10 @@ class CollectionAttributeImpl(AttributeImpl):
iterable = iter(iterable)
new_values = list(iterable)
+ evt = Event(self, OP_BULK_REPLACE)
+
+ self.dispatch.bulk_replace(state, new_values, evt)
+
old = self.get(state, dict_, passive=PASSIVE_ONLY_PERSISTENT)
if old is PASSIVE_NO_RESULT:
old = self.initialize(state, dict_)
@@ -1078,7 +1084,8 @@ class CollectionAttributeImpl(AttributeImpl):
dict_[self.key] = user_data
collections.bulk_replace(
- new_values, old_collection, new_collection)
+ new_values, old_collection, new_collection,
+ initiator=evt)
del old._sa_adapter
self.dispatch.dispose_collection(state, old, old_collection)
@@ -1163,7 +1170,7 @@ def backref_listeners(attribute, key, uselist):
impl = old_state.manager[key].impl
if initiator.impl is not impl or \
- initiator.op not in (OP_REPLACE, OP_REMOVE):
+ initiator.op is OP_APPEND:
impl.pop(old_state,
old_dict,
state.obj(),
@@ -1179,7 +1186,7 @@ def backref_listeners(attribute, key, uselist):
initiator.parent_token is not child_impl.parent_token:
_acceptable_key_err(state, initiator, child_impl)
elif initiator.impl is not child_impl or \
- initiator.op not in (OP_APPEND, OP_REPLACE):
+ initiator.op is OP_REMOVE:
child_impl.append(
child_state,
child_dict,
@@ -1200,7 +1207,7 @@ def backref_listeners(attribute, key, uselist):
initiator.parent_token is not child_impl.parent_token:
_acceptable_key_err(state, initiator, child_impl)
elif initiator.impl is not child_impl or \
- initiator.op not in (OP_APPEND, OP_REPLACE):
+ initiator.op is OP_REMOVE:
child_impl.append(
child_state,
child_dict,
@@ -1215,7 +1222,7 @@ def backref_listeners(attribute, key, uselist):
instance_dict(child)
child_impl = child_state.manager[key].impl
if initiator.impl is not child_impl or \
- initiator.op not in (OP_REMOVE, OP_REPLACE):
+ initiator.op is OP_APPEND:
child_impl.pop(
child_state,
child_dict,