From a03aed2dca850ed25322d34a26488e853be91fc2 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Sat, 24 Jan 2009 00:22:49 +0000 Subject: [PATCH] - session.expire() and related methods will not expire() unloaded deferred attributes. This prevents them from being needlessly loaded when the instance is refreshed. --- CHANGES | 4 ++++ lib/sqlalchemy/orm/mapper.py | 7 ++++++- lib/sqlalchemy/orm/session.py | 10 +++++++++- test/orm/expire.py | 18 ++++++++++++++++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index dc931ad478..9a95d3b2b5 100644 --- a/CHANGES +++ b/CHANGES @@ -35,6 +35,10 @@ CHANGES loading would prevent other eager loads, self referential or not, from joining to the parent JOIN properly. Thanks to Alex K for creating a great test case. + + - session.expire() and related methods will not expire() unloaded + deferred attributes. This prevents them from being needlessly + loaded when the instance is refreshed. - sql - Further fixes to the "percent signs and spaces in column/table diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index 7cc6bad125..6f125bc841 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -775,7 +775,12 @@ class Mapper(object): if prop is None and raiseerr: raise sa_exc.InvalidRequestError("Mapper '%s' has no property '%s'" % (str(self), key)) return prop - + + @util.memoized_property + def _deferred_props(self): + return [p.key for p in self._props.values() if + isinstance(p, ColumnProperty) and getattr(p, 'deferred', False)] + @property def iterate_properties(self): """return an iterator of all MapperProperty objects.""" diff --git a/lib/sqlalchemy/orm/session.py b/lib/sqlalchemy/orm/session.py index 690286e9b9..ea8174726a 100644 --- a/lib/sqlalchemy/orm/session.py +++ b/lib/sqlalchemy/orm/session.py @@ -1518,7 +1518,15 @@ class Session(object): return util.IdentitySet(self._new.values()) -_expire_state = attributes.InstanceState.expire_attributes +def _expire_state(state, attribute_names): + if attribute_names is None: + state.expired = True + state.modified= False + # dont add unloaded deferred attributes to the list of attrs + attribute_names = set(state.manager.keys()).\ + difference([k for k in state.manager.mapper._deferred_props if k not in state.dict]) + state.expire_attributes(attribute_names) + UOWEventHandler = unitofwork.UOWEventHandler _sessions = weakref.WeakValueDictionary() diff --git a/test/orm/expire.py b/test/orm/expire.py index b18e1ef252..4e8771347e 100644 --- a/test/orm/expire.py +++ b/test/orm/expire.py @@ -4,7 +4,7 @@ import testenv; testenv.configure_for_tests() import gc from testlib import sa, testing from testlib.sa import Table, Column, Integer, String, ForeignKey, exc as sa_exc -from testlib.sa.orm import mapper, relation, create_session, attributes +from testlib.sa.orm import mapper, relation, create_session, attributes, deferred from orm import _base, _fixtures @@ -98,7 +98,21 @@ class ExpireTest(_fixtures.FixtureTest): # but now its back, rollback has occured, the _remove_newly_deleted # is reverted self.assertEquals(u.name, 'chuck') - + + @testing.resolve_artifact_names + def test_deferred(self): + """test that unloaded, deferred attributes aren't included in the expiry list.""" + + mapper(Order, orders, properties={'description':deferred(orders.c.description)}) + + s = create_session() + o1 = s.query(Order).first() + assert 'description' not in o1.__dict__ + s.expire(o1) + assert o1.isopen is not None + assert 'description' not in o1.__dict__ + assert o1.description + @testing.resolve_artifact_names def test_lazyload_autoflushes(self): mapper(User, users, properties={ -- 2.47.3