diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-09-07 20:28:19 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2014-09-07 20:28:19 -0400 |
| commit | 390207e533854ab0c3abe6b7ebc45fae1b14eaba (patch) | |
| tree | 435ab97d2cf1656eb5df2984d72fafdf2a364a1b /lib/sqlalchemy | |
| parent | 083d44c082a13761910c761debd17efac75a71b3 (diff) | |
| download | sqlalchemy-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.py | 23 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/collections.py | 23 | ||||
| -rw-r--r-- | lib/sqlalchemy/orm/events.py | 53 |
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. + + """ |
