relations that are present on superclasses when using
inheritance.
- - fixed order_by calculation in Query to properly alias
+ - Fixed order_by calculation in Query to properly alias
mapper-config'ed order_by when using select_from()
+ - Refactored the diffing logic that kicks in when replacing
+ one collection with another into collections.bulk_replace,
+ useful to anyone building multi-level collections.
+
- sql
- Schema-qualified tables now will place the schemaname
ahead of the tablename in all column expressions as well
if initiator is self:
return
+ self._set_iterable(
+ state, value,
+ lambda adapter, i: adapter.adapt_like_to_iterable(i))
+
+ def _set_iterable(self, state, iterable, adapter=None):
+ """Set a collection value from an interable.
+
+ ``adapter`` is an optional callable invoked with a CollectionAdapter
+ and the iterable. Should return an iterable of instances suitable for
+ appending via a CollectionAdapter. Can be used for, e.g., adapting an
+ incoming dictionary iterable to a list.
+
+ """
# we need a CollectionAdapter to adapt the incoming value to an
# assignable iterable. pulling a new collection first so that
# an adaptation exception does not trigger a lazy load of the
# old collection.
new_collection, user_data = self._build_collection(state)
- new_values = list(new_collection.adapt_like_to_iterable(value))
+ if adapter:
+ new_values = list(adapter(new_collection, iterable))
+ else:
+ new_values = list(iterable)
old = self.get(state)
# ignore re-assignment of the current collection, as happens
# implicitly with in-place operators (foo.collection |= other)
- if old is value:
+ if old is iterable:
return
if self.key not in state.committed_state:
old_collection = self.get_collection(state, old)
- idset = util.IdentitySet
- constants = idset(old_collection or []).intersection(new_values or [])
- additions = idset(new_values or []).difference(constants)
- removals = idset(old_collection or []).difference(constants)
-
- for member in new_values or ():
- if member in additions:
- new_collection.append_with_event(member)
- elif member in constants:
- new_collection.append_without_event(member)
-
state.dict[self.key] = user_data
state.modified = True
- # mark all the orphaned elements as detached from the parent
- if old_collection:
- for member in removals:
- old_collection.remove_with_event(member)
- old_collection.unlink(old)
+ collections.bulk_replace(new_values, old_collection, new_collection)
+ old_collection.unlink(old)
+
def set_committed_value(self, state, value):
"""Set an attribute value on the given instance and 'commit' it.
self._data = weakref.ref(d['data'])
+def bulk_replace(values, existing_adapter, new_adapter):
+ """Load a new collection, firing events based on prior like membership.
+
+ Appends instances in ``values`` onto the ``new_adapter``. Events will be
+ fired for any instance not present in the ``existing_adapter``. Any
+ instances in ``existing_adapter`` not present in ``values`` will have
+ remove events fired upon them.
+
+ values
+ An iterable of collection member instances
+
+ existing_adapter
+ A CollectionAdapter of instances to be replaced
+
+ new_adapter
+ An empty CollectionAdapter to load with ``values``
+
+
+ """
+ if not isinstance(values, list):
+ values = list(values)
+
+ idset = sautil.IdentitySet
+ constants = idset(existing_adapter or ()).intersection(values or ())
+ additions = idset(values or ()).difference(constants)
+ removals = idset(existing_adapter or ()).difference(constants)
+
+ for member in values or ():
+ if member in additions:
+ new_adapter.append_with_event(member)
+ elif member in constants:
+ new_adapter.append_without_event(member)
+
+ if existing_adapter:
+ for member in removals:
+ existing_adapter.remove_with_event(member)
+
__instrumentation_mutex = sautil.threading.Lock()
def _prepare_instrumentation(factory):
"""Prepare a callable for future use as a collection class factory.