summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorJason Kirtland <jek@discorporate.us>2008-04-01 16:38:23 +0000
committerJason Kirtland <jek@discorporate.us>2008-04-01 16:38:23 +0000
commit73a4e9481d893426cedecaba57f06caea1894be1 (patch)
tree283f3beeeee3bff8456607ade95f5ebf3d701746 /lib
parent6f65b002c8e2ff145fff719fe1b1eb50f3418c46 (diff)
downloadsqlalchemy-73a4e9481d893426cedecaba57f06caea1894be1.tar.gz
- Light collections refactor, added public collections.bulk_replace.
- Collection attribs gain some private load-from-iterable flexiblity.
Diffstat (limited to 'lib')
-rw-r--r--lib/sqlalchemy/orm/attributes.py39
-rw-r--r--lib/sqlalchemy/orm/collections.py37
2 files changed, 58 insertions, 18 deletions
diff --git a/lib/sqlalchemy/orm/attributes.py b/lib/sqlalchemy/orm/attributes.py
index 152534d76..f0b1510f9 100644
--- a/lib/sqlalchemy/orm/attributes.py
+++ b/lib/sqlalchemy/orm/attributes.py
@@ -581,18 +581,34 @@ class CollectionAttributeImpl(AttributeImpl):
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:
@@ -600,25 +616,12 @@ class CollectionAttributeImpl(AttributeImpl):
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.
diff --git a/lib/sqlalchemy/orm/collections.py b/lib/sqlalchemy/orm/collections.py
index 7b78c240b..838591d32 100644
--- a/lib/sqlalchemy/orm/collections.py
+++ b/lib/sqlalchemy/orm/collections.py
@@ -612,6 +612,43 @@ class CollectionAdapter(object):
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.