0.4.0beta4
- fix to bind param processing such that "False" values (like blank strings)
still get processed/encoded
+- added session.prune(), trims away instances cached in a session that are
+ no longer referenced elsewhere. (a utility for for strong-ref identity maps)
0.4.0beta3
- sql types optimization:
for c in [obj] + list(_object_mapper(obj).cascade_iterator('refresh-expire', obj)):
self._expire_impl(c)
+ def prune(self):
+ """Removes unreferenced instances cached in the identity map.
+
+ Removes any object in this Session'sidentity map that is not
+ referenced in user code, modified, new or scheduled for deletion.
+ Returns the number of objects pruned.
+ """
+
+ return self.uow.prune_identity_map()
+
def _expire_impl(self, obj):
self._validate_persistent(obj)
changes at once.
"""
+import gc, StringIO, weakref
from sqlalchemy import util, logging, topological, exceptions
from sqlalchemy.orm import attributes, interfaces
from sqlalchemy.orm import util as mapperutil
from sqlalchemy.orm.mapper import object_mapper
-import StringIO
-import weakref
# Load lazily
object_session = None
if session.extension is not None:
session.extension.after_flush_postexec(session, flush_context)
+ def prune_identity_map(self):
+ """Removes unreferenced instances cached in the identity map.
+
+ Removes any object in the identity map that is not referenced
+ in user code or scheduled for a unit of work operation. Returns
+ the number of objects pruned.
+ """
+
+ if isinstance(self.identity_map, weakref.WeakValueDictionary):
+ return 0
+ ref_count = len(self.identity_map)
+ dirty = self.locate_dirty()
+ keepers = weakref.WeakValueDictionary(self.identity_map)
+ self.identity_map.clear()
+ gc.collect()
+ self.identity_map.update(keepers)
+ return ref_count - len(self.identity_map)
+
class UOWTransaction(object):
"""Handles the details of organizing and executing transaction
tasks during a UnitOfWork object's flush() operation.
import gc
gc.collect()
assert len(s.identity_map) == 1
+
+ def test_prune(self):
+ tables.delete()
+ s = create_session()
+ class User(object):pass
+ mapper(User, users)
+
+ for o in [User() for x in xrange(10)]:
+ s.save(o)
+ # o is still live after this loop...
+
+ self.assert_(len(s.identity_map) == 0)
+ self.assert_(s.prune() == 0)
+ s.flush()
+ self.assert_(s.prune() == 9)
+ self.assert_(len(s.identity_map) == 1)
+
+ user_id = o.user_id
+ del o
+ self.assert_(s.prune() == 1)
+ self.assert_(len(s.identity_map) == 0)
+
+ u = s.query(User).get(user_id)
+ self.assert_(s.prune() == 0)
+ self.assert_(len(s.identity_map) == 1)
+ u.user_name = 'squiznart'
+ del u
+ self.assert_(s.prune() == 0)
+ self.assert_(len(s.identity_map) == 1)
+ s.flush()
+ self.assert_(s.prune() == 1)
+ self.assert_(len(s.identity_map) == 0)
+
+ s.save(User())
+ self.assert_(s.prune() == 0)
+ self.assert_(len(s.identity_map) == 0)
+ s.flush()
+ self.assert_(len(s.identity_map) == 1)
+ self.assert_(s.prune() == 1)
+ self.assert_(len(s.identity_map) == 0)
+
+ u = s.query(User).get(user_id)
+ s.delete(u)
+ del u
+ self.assert_(s.prune() == 0)
+ self.assert_(len(s.identity_map) == 1)
+ s.flush()
+ self.assert_(s.prune() == 0)
+ self.assert_(len(s.identity_map) == 0)
def test_no_save_cascade(self):
mapper(Address, addresses)