From d1fa8fe3272de79f120d9cf5e9e4a7f69bbcf343 Mon Sep 17 00:00:00 2001 From: Mike Bayer Date: Wed, 19 Apr 2006 19:33:51 +0000 Subject: [PATCH] fixed up expunge() and the continuing circular refs in attributes, added a unit test for the whole thing --- lib/sqlalchemy/attributes.py | 6 +-- lib/sqlalchemy/mapping/objectstore.py | 2 +- lib/sqlalchemy/sql.py | 5 ++- test/attributes.py | 6 +-- test/masscreate.py | 7 ++- test/massload.py | 65 +++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 10 deletions(-) create mode 100644 test/massload.py diff --git a/lib/sqlalchemy/attributes.py b/lib/sqlalchemy/attributes.py index e399e7ed0c..98884df441 100644 --- a/lib/sqlalchemy/attributes.py +++ b/lib/sqlalchemy/attributes.py @@ -78,10 +78,10 @@ class ManagedAttribute(object): which occurs through the SmartProperty property object ultimately calls upon ManagedAttribute objects associated with the instance via this dictionary.""" def __init__(self, obj, key): - #self.__obj = weakref.ref(obj) - self.obj = obj + self.__obj = weakref.ref(obj) + #self.obj = obj self.key = key - #obj = property(lambda s:s.__obj()) + obj = property(lambda s:s.__obj()) def history(self, **kwargs): return self def plain_init(self, *args, **kwargs): diff --git a/lib/sqlalchemy/mapping/objectstore.py b/lib/sqlalchemy/mapping/objectstore.py index faf5ddbd6b..91f9470851 100644 --- a/lib/sqlalchemy/mapping/objectstore.py +++ b/lib/sqlalchemy/mapping/objectstore.py @@ -94,7 +94,7 @@ class Session(object): def expunge(self, *obj): for o in obj: - self.uow.expunge(obj) + self.uow.expunge(o) def register_clean(self, obj): self._bind_to(obj) diff --git a/lib/sqlalchemy/sql.py b/lib/sqlalchemy/sql.py index 02e1619a15..1af02faacc 100644 --- a/lib/sqlalchemy/sql.py +++ b/lib/sqlalchemy/sql.py @@ -131,7 +131,8 @@ def not_(clause): def between_(ctest, cleft, cright): """ returns BETWEEN predicate clause (clausetest BETWEEN clauseleft AND clauseright) """ return BooleanExpression(ctest, and_(cleft, cright), 'BETWEEN') - +between = between_ + def cast(clause, totype, **kwargs): """ returns CAST function CAST(clause AS totype) Use with a sqlalchemy.types.TypeEngine object, i.e @@ -517,6 +518,8 @@ class CompareMixin(object): return Label(name, self, self.type) def distinct(self): return CompoundClause(None,"DISTINCT", self) + def between(self, cleft, cright): + return between_(self, cleft, cright) def op(self, operator): return lambda other: self._compare(operator, other) # and here come the math operators: diff --git a/test/attributes.py b/test/attributes.py index 4c4c7585c5..bff864fa69 100644 --- a/test/attributes.py +++ b/test/attributes.py @@ -43,9 +43,9 @@ class AttributesTest(PersistTest): manager.register_attribute(MyTest, 'user_id', uselist = False) manager.register_attribute(MyTest, 'user_name', uselist = False) manager.register_attribute(MyTest, 'email_address', uselist = False) - x = MyTest() - x.user_id=7 - pickle.dumps(x) + x = MyTest() + x.user_id=7 + pickle.dumps(x) def testlist(self): class User(object):pass diff --git a/test/masscreate.py b/test/masscreate.py index 4321c210fa..885c1f6537 100644 --- a/test/masscreate.py +++ b/test/masscreate.py @@ -2,6 +2,7 @@ from sqlalchemy.attributes import * import time +import gc manage_attributes = True init_attributes = manage_attributes and True @@ -32,7 +33,9 @@ for i in range(0,130): if init_attributes: attr_manager.init_attr(a) a.email = 'foo@bar.com' - u.addresses.append(u) - + u.addresses.append(a) +# gc.collect() + print len(managed_attributes) +# managed_attributes.clear() total = time.time() - now print "Total time", total diff --git a/test/massload.py b/test/massload.py new file mode 100644 index 0000000000..d36746968b --- /dev/null +++ b/test/massload.py @@ -0,0 +1,65 @@ +from testbase import PersistTest, AssertMixin +import unittest, sys, os +from sqlalchemy import * +import sqlalchemy.attributes as attributes +import StringIO +import testbase +import gc + +db = testbase.db + +NUM = 25000 + +""" +we are testing session.expunge() here, also that the attributes and unitofwork packages dont keep dereferenced +stuff hanging around. + +for best results, dont run with sqlite :memory: database, and keep an eye on top while it runs""" + +class LoadTest(AssertMixin): + def setUpAll(self): + db.echo = False + global items + items = Table('items', db, + Column('item_id', Integer, primary_key=True), + Column('value', String(100))) + items.create() + db.echo = testbase.echo + def tearDownAll(self): + db.echo = False + items.drop() + items.deregister() + db.echo = testbase.echo + def setUp(self): + objectstore.clear() + clear_mappers() + for x in range(1,NUM/500+1): + l = [] + for y in range(x*500-500, x*500): + l.append({'item_id':y, 'value':'this is item #%d' % y}) + items.insert().execute(*l) + + def testload(self): + class Item(object):pass + + m = mapper(Item, items) + + for x in range (1,NUM/100): + # this is not needed with cpython which clears non-circular refs immediately + #gc.collect() + l = m.select(items.c.item_id.between(x*100 - 100, x*100 - 1)) + assert len(l) == 100 + print "loaded ", len(l), " items " + # modifying each object will insure that the objects get placed in the "dirty" list + # and will hang around until expunged + for a in l: + a.value = 'changed...' + assert len(objectstore.get_session().dirty) == len(l) + assert len(objectstore.get_session().identity_map) == len(l) + assert len(attributes.managed_attributes) == len(l) + print len(objectstore.get_session().dirty) + print len(objectstore.get_session().identity_map) + #objectstore.expunge(*l) + +if __name__ == "__main__": + testbase.main() -- 2.47.2