diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-07-27 23:02:20 +0000 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2007-07-27 23:02:20 +0000 |
commit | 3f1c33cdc715624dd7ee2f88e04872401e0d9c61 (patch) | |
tree | d0a9b8852620467f0ba788fdc01936adc8728e5f /lib/sqlalchemy/orm/dynamic.py | |
parent | d43fbfb93a4a8d09370b222169169c1d510b9c4b (diff) | |
download | sqlalchemy-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.py | 123 |
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 |