summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/sqlalchemy/ext/assignmapper.py1
-rw-r--r--lib/sqlalchemy/orm/__init__.py10
-rw-r--r--lib/sqlalchemy/orm/mapper.py4
-rw-r--r--lib/sqlalchemy/util.py4
-rw-r--r--test/orm/alltests.py1
-rw-r--r--test/orm/memusage.py79
6 files changed, 96 insertions, 3 deletions
diff --git a/lib/sqlalchemy/ext/assignmapper.py b/lib/sqlalchemy/ext/assignmapper.py
index 89cf962e7..b76f29943 100644
--- a/lib/sqlalchemy/ext/assignmapper.py
+++ b/lib/sqlalchemy/ext/assignmapper.py
@@ -39,3 +39,4 @@ def assign_mapper(ctx, class_, *args, **kwargs):
for name in ['flush', 'delete', 'expire', 'refresh', 'expunge', 'merge', 'save', 'update', 'save_or_update']:
monkeypatch_objectstore_method(ctx, class_, name)
return m
+
diff --git a/lib/sqlalchemy/orm/__init__.py b/lib/sqlalchemy/orm/__init__.py
index 96890272d..1e1a75b63 100644
--- a/lib/sqlalchemy/orm/__init__.py
+++ b/lib/sqlalchemy/orm/__init__.py
@@ -74,8 +74,10 @@ def clear_mappers():
when new mappers are created, they will be assigned to their classes as their primary mapper."""
for mapper in mapper_registry.values():
attribute_manager.reset_class_managed(mapper.class_)
+ mapper.class_key.dispose()
+ if hasattr(mapper.class_, 'c'):
+ del mapper.class_.c
mapper_registry.clear()
- mapperlib.ClassKey.instances.clear()
def clear_mapper(m):
"""remove the given mapper from the storage of mappers.
@@ -83,7 +85,11 @@ def clear_mapper(m):
when a new mapper is created for the previous mapper's class, it will be used as that classes'
new primary mapper."""
del mapper_registry[m.class_key]
-
+ attribute_manager.reset_class_managed(m.class_)
+ if hasattr(m.class_, 'c'):
+ del m.class_.c
+ m.class_key.dispose()
+
def extension(ext):
"""return a MapperOption that will insert the given MapperExtension to the
beginning of the list of extensions that will be called in the context of the Query.
diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py
index d6967934c..1d4ce6bb9 100644
--- a/lib/sqlalchemy/orm/mapper.py
+++ b/lib/sqlalchemy/orm/mapper.py
@@ -1458,7 +1458,9 @@ class ClassKey(object):
return self is other
def __repr__(self):
return "ClassKey(%s, %s)" % (repr(self.class_), repr(self.entity_name))
-
+ def dispose(self):
+ type(self).dispose_static(self.class_, self.entity_name)
+
def has_identity(object):
return hasattr(object, '_instance_key')
diff --git a/lib/sqlalchemy/util.py b/lib/sqlalchemy/util.py
index 706630918..54b1afa9f 100644
--- a/lib/sqlalchemy/util.py
+++ b/lib/sqlalchemy/util.py
@@ -49,6 +49,10 @@ def reversed(seq):
class ArgSingleton(type):
instances = {}
+ def dispose_static(self, *args):
+ hashkey = (self, args)
+ #if hashkey in ArgSingleton.instances:
+ del ArgSingleton.instances[hashkey]
def __call__(self, *args):
hashkey = (self, args)
try:
diff --git a/test/orm/alltests.py b/test/orm/alltests.py
index ffcdd3d21..6d6aba2b2 100644
--- a/test/orm/alltests.py
+++ b/test/orm/alltests.py
@@ -17,6 +17,7 @@ def suite():
'orm.relationships',
'orm.association',
'orm.merge',
+ 'orm.memusage',
'orm.cycles',
'orm.poly_linked_list',
diff --git a/test/orm/memusage.py b/test/orm/memusage.py
new file mode 100644
index 000000000..84d3ebdb2
--- /dev/null
+++ b/test/orm/memusage.py
@@ -0,0 +1,79 @@
+from sqlalchemy import *
+from sqlalchemy.orm import mapperlib, session, unitofwork, attributes
+Mapper = mapperlib.Mapper
+import gc
+import testbase
+import tables
+
+class A(object):pass
+class B(object):pass
+
+class MapperCleanoutTest(testbase.AssertMixin):
+ """test that clear_mappers() removes everything related to the class.
+
+ does not include classes that use the assignmapper extension."""
+ def setUp(self):
+ global engine
+ engine = testbase.db
+
+ def test_mapper_cleanup(self):
+ for x in range(0, 5):
+ self.do_test()
+ gc.collect()
+ for o in gc.get_objects():
+ if isinstance(o, Mapper):
+ # the classes in the 'tables' package have assign_mapper called on them
+ # which is particularly sticky
+ # if getattr(tables, o.class_.__name__, None) is o.class_:
+ # continue
+ # well really we are just testing our own classes here
+ if (o.class_ not in [A,B]):
+ continue
+ assert False
+ assert True
+
+ def do_test(self):
+ metadata = BoundMetaData(engine)
+
+ table1 = Table("mytable", metadata,
+ Column('col1', Integer, primary_key=True),
+ Column('col2', String(30))
+ )
+
+ table2 = Table("mytable2", metadata,
+ Column('col1', Integer, primary_key=True),
+ Column('col2', String(30)),
+ Column('col3', String(30), ForeignKey("mytable.col1"))
+ )
+
+ metadata.create_all()
+
+
+ m1 = mapper(A, table1, properties={
+ "bs":relation(B)
+ })
+ m2 = mapper(B, table2)
+
+ m3 = mapper(A, table1, non_primary=True)
+
+ sess = create_session()
+ a1 = A()
+ a2 = A()
+ a3 = A()
+ a1.bs.append(B())
+ a1.bs.append(B())
+ a3.bs.append(B())
+ for x in [a1,a2,a3]:
+ sess.save(x)
+ sess.flush()
+ sess.clear()
+
+ alist = sess.query(A).select()
+ for a in alist:
+ print "A", a, "BS", [b for b in a.bs]
+
+ metadata.drop_all()
+ clear_mappers()
+
+if __name__ == '__main__':
+ testbase.main() \ No newline at end of file