summaryrefslogtreecommitdiff
path: root/test/ext
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2012-08-05 15:14:51 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2012-08-05 15:14:51 -0400
commit6bd46945ccd585c494eb7550a0dfea22f17727c0 (patch)
tree4412d12bb42af58a1c30f7f961f77acb2fa35386 /test/ext
parenta4f2db890322a225e6c9754b711f5c16d04f377c (diff)
downloadsqlalchemy-6bd46945ccd585c494eb7550a0dfea22f17727c0.tar.gz
- reorganization of declarative such that file sizes are managable again.
the vast majority of file lines are spent on documentation, which moves into package __init__. The core declarative idea lives in base and is back down to its originally low size of under 500 lines. The various helpers and such move into api.py, and the full span of string lookup moves into a new module clsregistry. the rest of declarative only refers to two functions in clsregistry in three places inside of base. - [feature] Declarative now maintains a registry of classes by string name as well as by full module-qualified name. Multiple classes with the same name can now be looked up based on a module-qualified string within relationship(). Simple class name lookups where more than one class shares the same name now raises an informative error message. [ticket:2338] - lots of tests to ensure the new weak referencing memory management is maintained by the new class registry system. this ticket was served very well by waiting to do #2526 first, else this would have needed to be rewritten anyway.
Diffstat (limited to 'test/ext')
-rw-r--r--test/ext/declarative/__init__.py0
-rw-r--r--test/ext/declarative/test_basic.py (renamed from test/ext/test_declarative.py)46
-rw-r--r--test/ext/declarative/test_clsregistry.py196
-rw-r--r--test/ext/declarative/test_inheritance.py (renamed from test/ext/test_declarative_inheritance.py)4
-rw-r--r--test/ext/declarative/test_mixin.py (renamed from test/ext/test_declarative_mixin.py)0
-rw-r--r--test/ext/declarative/test_reflection.py (renamed from test/ext/test_declarative_reflection.py)4
6 files changed, 236 insertions, 14 deletions
diff --git a/test/ext/declarative/__init__.py b/test/ext/declarative/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/test/ext/declarative/__init__.py
diff --git a/test/ext/test_declarative.py b/test/ext/declarative/test_basic.py
index e9494b295..b580558af 100644
--- a/test/ext/test_declarative.py
+++ b/test/ext/declarative/test_basic.py
@@ -10,12 +10,15 @@ from sqlalchemy import MetaData, Integer, String, ForeignKey, \
from test.lib.schema import Table, Column
from sqlalchemy.orm import relationship, create_session, class_mapper, \
joinedload, configure_mappers, backref, clear_mappers, \
- polymorphic_union, deferred, column_property, composite,\
+ deferred, column_property, composite,\
Session
from test.lib.testing import eq_
from sqlalchemy.util import classproperty
from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase, ConcreteBase
from test.lib import fixtures
+from test.lib.util import gc_collect
+
+Base = None
class DeclarativeTestBase(fixtures.TestBase, testing.AssertsExecutionResults):
def setup(self):
@@ -171,7 +174,7 @@ class DeclarativeTest(DeclarativeTestBase):
eq_(str(foo), '(no name)')
eq_(foo.key, None)
eq_(foo.name, None)
- decl._undefer_column_name('foo', foo)
+ decl.base._undefer_column_name('foo', foo)
eq_(str(foo), 'foo')
eq_(foo.key, 'foo')
eq_(foo.name, 'foo')
@@ -310,6 +313,25 @@ class DeclarativeTest(DeclarativeTestBase):
eq_(str(User.addresses.prop.primaryjoin),
'users.id = addresses.user_id')
+ def test_string_dependency_resolution_module_qualified(self):
+ class User(Base, fixtures.ComparableEntity):
+
+ __tablename__ = 'users'
+ id = Column(Integer, primary_key=True)
+ addresses = relationship('%s.Address' % __name__,
+ primaryjoin='%s.User.id==%s.Address.user_id.prop.columns['
+ '0]' % (__name__, __name__))
+
+ class Address(Base, fixtures.ComparableEntity):
+
+ __tablename__ = 'addresses'
+ id = Column(Integer, primary_key=True)
+ user_id = Column(Integer, ForeignKey('users.id'))
+
+ configure_mappers()
+ eq_(str(User.addresses.prop.primaryjoin),
+ 'users.id = addresses.user_id')
+
def test_string_dependency_resolution_in_backref(self):
class User(Base, fixtures.ComparableEntity):
@@ -890,9 +912,9 @@ class DeclarativeTest(DeclarativeTestBase):
sa.exc.SAWarning,
r"Regular \(i.e. not __special__\) attribute 'MyBase.somecol' "
"uses @declared_attr, but owning class "
- "<class 'test.ext.test_declarative.MyBase'> is "
+ "<class 'test.ext.declarative.test_basic.MyBase'> is "
"mapped - not applying to subclass <class "
- "'test.ext.test_declarative.MyClass'>.",
+ "'test.ext.declarative.test_basic.MyClass'>.",
go
)
@@ -1337,20 +1359,20 @@ class DeclarativeTest(DeclarativeTestBase):
)).one()
eq_(rt, u1)
- @testing.emits_warning(
- "The classname 'Test' is already in the registry "
- "of this declarative base, mapped to "
- "<class 'test.ext.test_declarative.Test'>"
- )
def test_duplicate_classes_in_base(self):
class Test(Base):
__tablename__ = 'a'
id = Column(Integer, primary_key=True)
- class Test(Base):
- __tablename__ = 'b'
- id = Column(Integer, primary_key=True)
+ assert_raises_message(
+ sa.exc.SAWarning,
+ "This declarative base already contains a class with ",
+ lambda: type(Base)("Test", (Base,), dict(
+ __tablename__='b',
+ id=Column(Integer, primary_key=True)
+ ))
+ )
diff --git a/test/ext/declarative/test_clsregistry.py b/test/ext/declarative/test_clsregistry.py
new file mode 100644
index 000000000..2bf691a6e
--- /dev/null
+++ b/test/ext/declarative/test_clsregistry.py
@@ -0,0 +1,196 @@
+from test.lib import fixtures
+from test.lib.util import gc_collect
+from test.lib.testing import assert_raises_message, is_, eq_
+from sqlalchemy import exc, MetaData
+from sqlalchemy.ext.declarative import clsregistry
+import weakref
+
+class MockClass(object):
+ def __init__(self, base, name):
+ self._decl_class_registry = base
+ tokens = name.split(".")
+ self.__module__ = ".".join(tokens[0:-1])
+ self.name = tokens[-1]
+ self.metadata = MetaData()
+
+
+class MockProp(object):
+ parent = "some_parent"
+
+
+class ClsRegistryTest(fixtures.TestBase):
+ def test_same_module_same_name(self):
+ base = weakref.WeakValueDictionary()
+ f1 = MockClass(base, "foo.bar.Foo")
+ f2 = MockClass(base, "foo.bar.Foo")
+ clsregistry.add_class("Foo", f1)
+ gc_collect()
+
+ assert_raises_message(
+ exc.SAWarning,
+ "This declarative base already contains a class ",
+ clsregistry.add_class, "Foo", f2
+ )
+
+ def test_resolve(self):
+ base = weakref.WeakValueDictionary()
+ f1 = MockClass(base, "foo.bar.Foo")
+ f2 = MockClass(base, "foo.alt.Foo")
+ clsregistry.add_class("Foo", f1)
+ clsregistry.add_class("Foo", f2)
+ resolver = clsregistry._resolver(f1, MockProp())
+
+ gc_collect()
+
+ is_(resolver("foo.bar.Foo")(), f1)
+ is_(resolver("foo.alt.Foo")(), f2)
+
+ def test_resolve_dupe_by_name(self):
+ base = weakref.WeakValueDictionary()
+ f1 = MockClass(base, "foo.bar.Foo")
+ f2 = MockClass(base, "foo.alt.Foo")
+ clsregistry.add_class("Foo", f1)
+ clsregistry.add_class("Foo", f2)
+
+ gc_collect()
+
+ resolver = clsregistry._resolver(f1, MockProp())
+ resolver = resolver("Foo")
+ assert_raises_message(
+ exc.InvalidRequestError,
+ "Multiple classes with the classname 'Foo' "
+ "are in the registry of this declarative "
+ "base. Please use a fully module-qualified path.",
+ resolver
+ )
+
+ def test_dupe_classes_back_to_one(self):
+ base = weakref.WeakValueDictionary()
+ f1 = MockClass(base, "foo.bar.Foo")
+ f2 = MockClass(base, "foo.alt.Foo")
+ clsregistry.add_class("Foo", f1)
+ clsregistry.add_class("Foo", f2)
+
+ del f2
+ gc_collect()
+
+ # registry restores itself to just the one class
+ resolver = clsregistry._resolver(f1, MockProp())
+ resolver = resolver("Foo")
+ is_(resolver(), f1)
+
+ def test_dupe_classes_cleanout(self):
+ # force this to maintain isolation between tests
+ clsregistry._registries.clear()
+
+ base = weakref.WeakValueDictionary()
+
+ for i in xrange(3):
+ f1 = MockClass(base, "foo.bar.Foo")
+ f2 = MockClass(base, "foo.alt.Foo")
+ clsregistry.add_class("Foo", f1)
+ clsregistry.add_class("Foo", f2)
+
+ eq_(len(clsregistry._registries), 5)
+
+ del f1
+ del f2
+ gc_collect()
+
+ eq_(len(clsregistry._registries), 1)
+
+ def test_dupe_classes_name_race(self):
+ """test the race condition that the class was garbage "
+ "collected while being resolved from a dupe class."""
+ base = weakref.WeakValueDictionary()
+ f1 = MockClass(base, "foo.bar.Foo")
+ f2 = MockClass(base, "foo.alt.Foo")
+ clsregistry.add_class("Foo", f1)
+ clsregistry.add_class("Foo", f2)
+
+ dupe_reg = base['Foo']
+ dupe_reg.contents = [lambda: None]
+ resolver = clsregistry._resolver(f1, MockProp())
+ resolver = resolver("Foo")
+ assert_raises_message(
+ exc.InvalidRequestError,
+ "When initializing mapper some_parent, expression "
+ "'Foo' failed to locate a name \('Foo'\).",
+ resolver
+ )
+
+ def test_module_reg_cleanout_race(self):
+ """test the race condition that a class was gc'ed as we tried
+ to look it up by module name."""
+
+ base = weakref.WeakValueDictionary()
+ f1 = MockClass(base, "foo.bar.Foo")
+ clsregistry.add_class("Foo", f1)
+ reg = base['_sa_module_registry']
+
+ mod_entry = reg['foo']['bar']
+ resolver = clsregistry._resolver(f1, MockProp())
+ resolver = resolver("foo")
+ mod_entry.contents.update({"Foo": lambda: None})
+ assert_raises_message(
+ AttributeError,
+ "Module 'bar' has no mapped classes registered "
+ "under the name 'Foo'",
+ lambda: resolver().bar.Foo
+ )
+
+ def test_module_reg_no_class(self):
+ base = weakref.WeakValueDictionary()
+ f1 = MockClass(base, "foo.bar.Foo")
+ clsregistry.add_class("Foo", f1)
+ reg = base['_sa_module_registry']
+ mod_entry = reg['foo']['bar']
+ resolver = clsregistry._resolver(f1, MockProp())
+ resolver = resolver("foo")
+ assert_raises_message(
+ AttributeError,
+ "Module 'bar' has no mapped classes registered "
+ "under the name 'Bat'",
+ lambda: resolver().bar.Bat
+ )
+
+ def test_module_reg_cleanout_two_sub(self):
+ base = weakref.WeakValueDictionary()
+ f1 = MockClass(base, "foo.bar.Foo")
+ clsregistry.add_class("Foo", f1)
+ reg = base['_sa_module_registry']
+
+ f2 = MockClass(base, "foo.alt.Bar")
+ clsregistry.add_class("Bar", f2)
+ assert reg['foo']['bar']
+ del f1
+ gc_collect()
+ assert 'bar' not in \
+ reg['foo']
+ assert 'alt' in reg['foo']
+
+ del f2
+ gc_collect()
+ assert 'foo' not in reg.contents
+
+ def test_module_reg_cleanout_sub_to_base(self):
+ base = weakref.WeakValueDictionary()
+ f3 = MockClass(base, "bat.bar.Hoho")
+ clsregistry.add_class("Hoho", f3)
+ reg = base['_sa_module_registry']
+
+ assert reg['bat']['bar']
+ del f3
+ gc_collect()
+ assert 'bat' not in reg
+
+ def test_module_reg_cleanout_cls_to_base(self):
+ base = weakref.WeakValueDictionary()
+ f4 = MockClass(base, "single.Blat")
+ clsregistry.add_class("Blat", f4)
+ reg = base['_sa_module_registry']
+ assert reg['single']
+ del f4
+ gc_collect()
+ assert 'single' not in reg
+
diff --git a/test/ext/test_declarative_inheritance.py b/test/ext/declarative/test_inheritance.py
index 9384aa03d..5a8c8e23e 100644
--- a/test/ext/test_declarative_inheritance.py
+++ b/test/ext/declarative/test_inheritance.py
@@ -17,6 +17,8 @@ from sqlalchemy.util import classproperty
from sqlalchemy.ext.declarative import declared_attr, AbstractConcreteBase, ConcreteBase
from test.lib import fixtures
+Base = None
+
class DeclarativeTestBase(fixtures.TestBase, testing.AssertsExecutionResults):
def setup(self):
global Base
@@ -792,7 +794,7 @@ class DeclarativeInheritanceTest(DeclarativeTestBase):
assert_raises_message(sa.exc.ArgumentError,
'place __table_args__', go)
- @testing.emits_warning("The classname")
+ @testing.emits_warning("This declarative")
def test_dupe_name_in_hierarchy(self):
class A(Base):
__tablename__ = "a"
diff --git a/test/ext/test_declarative_mixin.py b/test/ext/declarative/test_mixin.py
index 0876ebe63..0876ebe63 100644
--- a/test/ext/test_declarative_mixin.py
+++ b/test/ext/declarative/test_mixin.py
diff --git a/test/ext/test_declarative_reflection.py b/test/ext/declarative/test_reflection.py
index 6efc6e64e..d5fd8e787 100644
--- a/test/ext/test_declarative_reflection.py
+++ b/test/ext/declarative/test_reflection.py
@@ -152,9 +152,11 @@ class DeclarativeReflectionTest(DeclarativeReflectionBase):
class DeferredReflectBase(DeclarativeReflectionBase):
def teardown(self):
super(DeferredReflectBase,self).teardown()
- from sqlalchemy.ext.declarative import _MapperConfig
+ from sqlalchemy.ext.declarative.base import _MapperConfig
_MapperConfig.configs.clear()
+Base = None
+
class DeferredReflectPKFKTest(DeferredReflectBase):
@classmethod
def define_tables(cls, metadata):