From: Mike Bayer Date: Sun, 26 Mar 2006 21:44:22 +0000 (+0000) Subject: rework to expire() to make it smarter. when you expire(), history is immediately... X-Git-Tag: rel_0_1_5~6 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=cb3be7803bd2d8071ead35d5f053c90f706a678b;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git rework to expire() to make it smarter. when you expire(), history is immediately removed as well as explicit from dirty/deleted lists. this also changes uow.rollback_object() to remove from those lists, which is strange that it didnt do that before. anyway the mapper, when selecting and creating instances, asks the uow if this already identity-mapped instance is expired, and if so refreshes it on the fly, saving the need for the re-_get() operation, if some other query happens to touch upon the expired object. unit test added to confirm this. --- diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py index cd01faddd7..a5661ac5aa 100644 --- a/lib/sqlalchemy/attributes.py +++ b/lib/sqlalchemy/attributes.py @@ -398,7 +398,13 @@ class AttributeManager(object): except KeyError: pass obj.__dict__['_managed_trigger'] = callable + + def untrigger_history(self, obj): + del obj.__dict__['_managed_trigger'] + def has_trigger(self, obj): + return obj.__dict__.has_key('_managed_trigger') + def reset_history(self, obj, key): """removes the history object for the given attribute on the given object. When the attribute is next accessed, a new container will be created via the diff --git a/lib/sqlalchemy/mapping/mapper.py b/lib/sqlalchemy/mapping/mapper.py index 1efdb649a3..8ff28cf56d 100644 --- a/lib/sqlalchemy/mapping/mapper.py +++ b/lib/sqlalchemy/mapping/mapper.py @@ -816,16 +816,16 @@ class Mapper(object): # including modifying any of its related items lists, as its already # been exposed to being modified by the application. identitykey = self._identity_key(row) - if objectstore.get_session().has_key(identitykey): - instance = objectstore.get_session()._get(identitykey) + sess = objectstore.get_session() + if sess.has_key(identitykey): + instance = sess._get(identitykey) isnew = False - if populate_existing: + if populate_existing or sess.is_expired(instance, unexpire=True): if not imap.has_key(identitykey): imap[identitykey] = instance for prop in self.props.values(): prop.execute(instance, row, identitykey, imap, True) - if self.extension.append_result(self, row, imap, result, instance, isnew, populate_existing=populate_existing): if result is not None: result.append_nohistory(instance) diff --git a/lib/sqlalchemy/mapping/objectstore.py b/lib/sqlalchemy/mapping/objectstore.py index d6b4765629..7827c1b787 100644 --- a/lib/sqlalchemy/mapping/objectstore.py +++ b/lib/sqlalchemy/mapping/objectstore.py @@ -166,7 +166,7 @@ class Session(object): """invalidates the data in the given objects and sets them to refresh themselves the next time they are requested.""" for o in obj: - global_attributes.trigger_history(o, lambda: refresh(o)) + self.uow.expire(o) def expunge(self, *obj): for o in obj: diff --git a/lib/sqlalchemy/mapping/unitofwork.py b/lib/sqlalchemy/mapping/unitofwork.py index fc589f65bb..0b392b4470 100644 --- a/lib/sqlalchemy/mapping/unitofwork.py +++ b/lib/sqlalchemy/mapping/unitofwork.py @@ -100,6 +100,18 @@ class UnitOfWork(object): self.rollback_object(obj) object_mapper(obj)._get(obj._instance_key, reload=True) + def expire(self, obj): + self.rollback_object(obj) + def exp(): + object_mapper(obj)._get(obj._instance_key, reload=True) + global_attributes.trigger_history(obj, exp) + + def is_expired(self, obj, unexpire=False): + ret = global_attributes.has_trigger(obj) + if ret and unexpire: + global_attributes.untrigger_history(obj) + return ret + def has_key(self, key): """returns True if the given key is present in this UnitOfWork's identity map.""" return self.identity_map.has_key(key) @@ -247,6 +259,14 @@ class UnitOfWork(object): def rollback_object(self, obj): """'rolls back' the attributes that have been changed on an object instance.""" self.attributes.rollback(obj) + try: + del self.dirty[obj] + except KeyError: + pass + try: + del self.deleted[obj] + except KeyError: + pass class UOWTransaction(object): """handles the details of organizing and executing transaction tasks diff --git a/test/mapper.py b/test/mapper.py index 4ce698852d..4a8edd0974 100644 --- a/test/mapper.py +++ b/test/mapper.py @@ -111,14 +111,36 @@ class MapperTest(MapperSuperTest): self.assert_(a in u.addresses) objectstore.expire(u) - # expired, but not refreshed yet. still dirty - self.assert_(u in objectstore.get_session().uow.dirty) # get the attribute, it refreshes self.assert_(u.user_name == 'jack') self.assert_(a not in u.addresses) - # not dirty anymore - self.assert_(u not in objectstore.get_session().uow.dirty) - + + def testexpire(self): + m = mapper(User, users, properties={'addresses':relation(mapper(Address, addresses))}) + u = m.get(7) + u.user_name = 'foo' + objectstore.expire(u) + # test plain expire + self.assert_(u.user_name =='jack') + + # we're changing the database here, so if this test fails in the middle, + # it'll screw up the other tests which are hardcoded to 7/'jack' + u.user_name = 'foo' + objectstore.commit() + # change the value in the DB + users.update(users.c.user_id==7, values=dict(user_name='jack')).execute() + objectstore.expire(u) + # object isnt refreshed yet, using dict to bypass trigger + self.assert_(u.__dict__['user_name'] != 'jack') + # do a select + m.select() + # test that it refreshed + self.assert_(u.__dict__['user_name'] == 'jack') + + # object should be back to normal now, + # this should *not* produce a SELECT statement (not tested here though....) + self.assert_(u.user_name =='jack') + def testrefresh2(self): assign_mapper(Address, addresses)