summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-09-07 20:28:19 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2014-09-07 20:28:19 -0400
commit390207e533854ab0c3abe6b7ebc45fae1b14eaba (patch)
tree435ab97d2cf1656eb5df2984d72fafdf2a364a1b /lib/sqlalchemy
parent083d44c082a13761910c761debd17efac75a71b3 (diff)
downloadsqlalchemy-390207e533854ab0c3abe6b7ebc45fae1b14eaba.tar.gz
- Added new event handlers :meth:`.AttributeEvents.init_collection`
and :meth:`.AttributeEvents.dispose_collection`, which track when a collection is first associated with an instance and when it is replaced. These handlers supersede the :meth:`.collection.linker` annotation. The old hook remains supported through an event adapter.
Diffstat (limited to 'lib/sqlalchemy')
-rw-r--r--lib/sqlalchemy/orm/attributes.py23
-rw-r--r--lib/sqlalchemy/orm/collections.py23
-rw-r--r--lib/sqlalchemy/orm/events.py53
3 files changed, 79 insertions, 20 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 66197ba0e..459a52539 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -863,6 +863,16 @@ class CollectionAttributeImpl(AttributeImpl):
self.copy = copy_function
self.collection_factory = typecallable
+ if hasattr(self.collection_factory, "_sa_linker"):
+
+ @event.listens_for(self, "init_collection")
+ def link(target, collection, collection_adapter):
+ collection._sa_linker(collection_adapter)
+
+ @event.listens_for(self, "dispose_collection")
+ def unlink(target, collection, collection_adapter):
+ collection._sa_linker(None)
+
def __copy(self, item):
return [y for y in collections.collection_adapter(item)]
@@ -955,9 +965,14 @@ class CollectionAttributeImpl(AttributeImpl):
return user_data
def _initialize_collection(self, state):
- return state.manager.initialize_collection(
+
+ adapter, collection = state.manager.initialize_collection(
self.key, state, self.collection_factory)
+ self.dispatch.init_collection(state, collection, adapter)
+
+ return adapter, collection
+
def append(self, state, dict_, value, initiator, passive=PASSIVE_OFF):
collection = self.get_collection(state, dict_, passive=passive)
if collection is PASSIVE_NO_RESULT:
@@ -1026,12 +1041,14 @@ class CollectionAttributeImpl(AttributeImpl):
# place a copy of "old" in state.committed_state
state._modified_event(dict_, self, old, True)
- old_collection = getattr(old, '_sa_adapter')
+ old_collection = old._sa_adapter
dict_[self.key] = user_data
collections.bulk_replace(new_values, old_collection, new_collection)
- old_collection.unlink(old)
+
+ del old._sa_adapter
+ self.dispatch.dispose_collection(state, old, old_collection)
def _invalidate_collection(self, collection):
adapter = getattr(collection, '_sa_adapter')
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index 698677a0b..1fc0873bd 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -429,6 +429,10 @@ class collection(object):
the instance. A single argument is passed: the collection adapter
that has been linked, or None if unlinking.
+ .. deprecated:: 1.0.0 - the :meth:`.collection.linker` handler
+ is superseded by the :meth:`.AttributeEvents.init_collection`
+ and :meth:`.AttributeEvents.dispose_collection` handlers.
+
"""
fn._sa_instrument_role = 'linker'
return fn
@@ -575,7 +579,7 @@ class CollectionAdapter(object):
self._key = attr.key
self._data = weakref.ref(data)
self.owner_state = owner_state
- self.link_to_self(data)
+ data._sa_adapter = self
def _warn_invalidated(self):
util.warn("This collection has been invalidated.")
@@ -589,20 +593,6 @@ class CollectionAdapter(object):
def attr(self):
return self.owner_state.manager[self._key].impl
- def link_to_self(self, data):
- """Link a collection to this adapter"""
-
- data._sa_adapter = self
- if data._sa_linker:
- data._sa_linker(self)
-
- def unlink(self, data):
- """Unlink a collection from any adapter"""
-
- del data._sa_adapter
- if data._sa_linker:
- data._sa_linker(None)
-
def adapt_like_to_iterable(self, obj):
"""Converts collection-compatible objects to an iterable of values.
@@ -945,8 +935,7 @@ def _instrument_class(cls):
setattr(cls, '_sa_%s' % role, getattr(cls, method_name))
cls._sa_adapter = None
- if not hasattr(cls, '_sa_linker'):
- cls._sa_linker = None
+
if not hasattr(cls, '_sa_converter'):
cls._sa_converter = None
cls._sa_instrumented = id(cls)
diff --git a/lib/sqlalchemy/orm/events.py b/lib/sqlalchemy/orm/events.py
index daf705040..c50a7b062 100644
--- a/lib/sqlalchemy/orm/events.py
+++ b/lib/sqlalchemy/orm/events.py
@@ -1593,3 +1593,56 @@ class AttributeEvents(event.Events):
the given value, or a new effective value, should be returned.
"""
+
+ def init_collection(self, target, collection, collection_adapter):
+ """Receive a 'collection init' event.
+
+ This event is triggered for a collection-based attribute, when
+ the initial "empty collection" is first generated for a blank
+ attribute, as well as for when the collection is replaced with
+ a new one, such as via a set event.
+
+ E.g., given that ``User.addresses`` is a relationship-based
+ collection, the event is triggered here::
+
+ u1 = User()
+ u1.addresses.append(a1) # <- new collection
+
+ and also during replace operations::
+
+ u1.addresess = [a2, a3] # <- new collection
+
+ :param target: the object instance receiving the event.
+ If the listener is registered with ``raw=True``, this will
+ be the :class:`.InstanceState` object.
+ :param collection: the new collection. This will always be generated
+ from what was specified as
+ :paramref:`.RelationshipProperty.collection_class`, and will always
+ be empty.
+ :param collection_adpater: the :class:`.CollectionAdapter` that will
+ mediate internal access to the collection.
+
+ .. versionadded:: 1.0.0 the :meth:`.AttributeEvents.init_collection`
+ and :meth:`.AttributeEvents.dispose_collection` events supersede
+ the :class:`.collection.linker` hook.
+
+ """
+
+ def dispose_collection(self, target, collection, collection_adpater):
+ """Receive a 'collection dispose' event.
+
+ This event is triggered for a collection-based attribute when
+ a collection is replaced, that is::
+
+ u1.addresses.append(a1)
+
+ u1.addresses = [a2, a3] # <- old collection is disposed
+
+ The mechanics of the event will typically include that the given
+ collection is empty, even if it stored objects while being replaced.
+
+ .. versionadded:: 1.0.0 the :meth:`.AttributeEvents.init_collection`
+ and :meth:`.AttributeEvents.dispose_collection` events supersede
+ the :class:`.collection.linker` hook.
+
+ """