summaryrefslogtreecommitdiff
path: root/lib/sqlalchemy/orm/dynamic.py
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2007-07-27 23:02:20 +0000
committerMike Bayer <mike_mp@zzzcomputing.com>2007-07-27 23:02:20 +0000
commit3f1c33cdc715624dd7ee2f88e04872401e0d9c61 (patch)
treed0a9b8852620467f0ba788fdc01936adc8728e5f /lib/sqlalchemy/orm/dynamic.py
parentd43fbfb93a4a8d09370b222169169c1d510b9c4b (diff)
downloadsqlalchemy-3f1c33cdc715624dd7ee2f88e04872401e0d9c61.tar.gz
- an experimental feature that combines a Query with an InstrumentedAttribute, to provide
"always live" results in conjunction with mutator capability
Diffstat (limited to 'lib/sqlalchemy/orm/dynamic.py')
-rw-r--r--lib/sqlalchemy/orm/dynamic.py123
1 files changed, 123 insertions, 0 deletions
diff --git a/lib/sqlalchemy/orm/dynamic.py b/lib/sqlalchemy/orm/dynamic.py
new file mode 100644
index 000000000..5b6136867
--- /dev/null
+++ b/lib/sqlalchemy/orm/dynamic.py
@@ -0,0 +1,123 @@
+"""'dynamic' collection API. returns Query() objects on the 'read' side, alters
+a special AttributeHistory on the 'write' side."""
+
+from sqlalchemy import exceptions
+from sqlalchemy.orm import attributes, Query, object_session
+from sqlalchemy.orm.mapper import has_identity
+
+class DynamicCollectionAttribute(attributes.InstrumentedAttribute):
+ def __init__(self, class_, attribute_manager, key, typecallable, target_mapper, **kwargs):
+ super(DynamicCollectionAttribute, self).__init__(class_, attribute_manager, key, typecallable, **kwargs)
+ self.target_mapper = target_mapper
+
+ def get(self, obj, passive=False):
+ if passive:
+ return self.get_history(obj, passive=True).added_items()
+ else:
+ return AppenderQuery(self, obj)
+
+ def commit_to_state(self, state, obj, value=attributes.NO_VALUE):
+ # we have our own AttributeHistory therefore dont need CommittedState
+ pass
+
+ def set(self, obj, value, initiator):
+ if initiator is self:
+ return
+
+ state = obj._state
+
+ old_collection = self.get(obj).assign(value)
+
+ # TODO: emit events ???
+ state['modified'] = True
+
+ def delete(self, *args, **kwargs):
+ raise NotImplementedError()
+
+ def get_history(self, obj, passive=False):
+ try:
+ return obj.__dict__[self.key]
+ except KeyError:
+ obj.__dict__[self.key] = c = CollectionHistory(self, obj)
+ return c
+
+class AppenderQuery(Query):
+ def __init__(self, attr, instance):
+ super(AppenderQuery, self).__init__(attr.target_mapper, None)
+ self.instance = instance
+ self.attr = attr
+
+ def __len__(self):
+ if not has_identity(self.instance):
+ # TODO: all these various calls to _added_items should be more
+ # intelligently calculated from the CollectionHistory object
+ # (i.e. account for deletes too)
+ return len(self.attr.get_history(self.instance)._added_items)
+ else:
+ return self._clone().count()
+
+ def __iter__(self):
+ if not has_identity(self.instance):
+ return iter(self.attr.get_history(self.instance)._added_items)
+ else:
+ return iter(self._clone())
+
+ def __getitem__(self, index):
+ if not has_identity(self.instance):
+ return iter(self.attr.get_history(self.instance)._added_items.__getitem__(index))
+ else:
+ return self._clone().__getitem__(index)
+
+ def _clone(self):
+ # note we're returning an entirely new query class here
+ # without any assignment capabilities;
+ # the class of this query is determined by the session.
+ sess = object_session(self.instance)
+ if sess is None:
+ try:
+ sess = mapper.object_mapper(instance).get_session()
+ except exceptions.InvalidRequestError:
+ raise exceptions.InvalidRequestError("Parent instance %s is not bound to a Session, and no contextual session is established; lazy load operation of attribute '%s' cannot proceed" % (instance.__class__, self.key))
+
+ return sess.query(self.attr.target_mapper).with_parent(self.instance)
+
+ def assign(self, collection):
+ if has_identity(self.instance):
+ oldlist = list(self)
+ else:
+ oldlist = []
+ self.attr.get_history(self.instance).replace(oldlist, collection)
+ return oldlist
+
+ def append(self, item):
+ self.attr.get_history(self.instance)._added_items.append(item)
+ self.attr.fire_append_event(self.instance, item, self.attr)
+
+ def remove(self, item):
+ self.attr.get_history(self.instance)._deleted_items.append(item)
+ self.attr.fire_remove_event(self.instance, item, self.attr)
+
+class CollectionHistory(attributes.AttributeHistory):
+ """override AttributeHistory to receive append/remove events directly"""
+ def __init__(self, attr, obj):
+ self._deleted_items = []
+ self._added_items = []
+ self._unchanged_items = []
+ self._obj = obj
+
+ def replace(self, olditems, newitems):
+ self._added_items = newitems
+ self._deleted_items = olditems
+
+ def is_modified(self):
+ return len(self._deleted_items) > 0 or len(self._added_items) > 0
+
+ def added_items(self):
+ return self._added_items
+
+ def unchanged_items(self):
+ return self._unchanged_items
+
+ def deleted_items(self):
+ return self._deleted_items
+ \ No newline at end of file