diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-08-05 15:14:51 -0400 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2012-08-05 15:14:51 -0400 |
| commit | 6bd46945ccd585c494eb7550a0dfea22f17727c0 (patch) | |
| tree | 4412d12bb42af58a1c30f7f961f77acb2fa35386 /test/ext | |
| parent | a4f2db890322a225e6c9754b711f5c16d04f377c (diff) | |
| download | sqlalchemy-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__.py | 0 | ||||
| -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.py | 196 | ||||
| -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): |
