diff options
| author | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-29 15:04:35 -0500 |
|---|---|---|
| committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2010-12-29 15:04:35 -0500 |
| commit | b1c90de4494369c7c901f9f3e5e21271656024c5 (patch) | |
| tree | 91dade0d060c5f49aedd68271f64a6eb56cf214b /test/ext/test_mutable.py | |
| parent | 3b41b66981d8665282c645178643d273361eb6ad (diff) | |
| download | sqlalchemy-b1c90de4494369c7c901f9f3e5e21271656024c5.tar.gz | |
- mutable examples now move into sqlalchemy.ext.mutable
- streamline interfaces, get Mutable/MutableComposite to be as minimal
in usage as possible
- docs for mutable, warnings regrarding mapper events being global
- move MutableType/mutable=True outwards, move orm tests to its
own module, note in all documentation
- still need more events/tests for correct pickling support of
composites, mutables. in the case of composites its needed
even without mutation. see [ticket:2009]
Diffstat (limited to 'test/ext/test_mutable.py')
| -rw-r--r-- | test/ext/test_mutable.py | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/test/ext/test_mutable.py b/test/ext/test_mutable.py new file mode 100644 index 000000000..e9573f50d --- /dev/null +++ b/test/ext/test_mutable.py @@ -0,0 +1,238 @@ +from sqlalchemy import Integer +from sqlalchemy.types import PickleType, TypeDecorator, VARCHAR +from sqlalchemy.orm import mapper, Session, composite +from sqlalchemy.orm.mapper import Mapper +from sqlalchemy.orm.instrumentation import ClassManager +from test.lib.schema import Table, Column +from test.lib.testing import eq_ +from test.lib import testing +from test.orm import _base +import sys + +class _MutableDictTestBase(object): + @classmethod + def _type_fixture(cls): + from sqlalchemy.ext.mutable import Mutable + + # needed for pickle support + global MutationDict + + class MutationDict(Mutable, dict): + @classmethod + def coerce(cls, key, value): + if not isinstance(value, MutationDict): + if isinstance(value, dict): + return MutationDict(value) + return Mutable.coerce(key, value) + else: + return value + + def __getstate__(self): + return dict(self) + + def __setstate__(self, dict): + self.update(dict) + + def __setitem__(self, key, value): + dict.__setitem__(self, key, value) + self.on_change() + + def __delitem__(self, key): + dict.__delitem__(self, key) + self.on_change() + return MutationDict + + @testing.resolve_artifact_names + def setup_mappers(cls): + class Foo(_base.BasicEntity): + pass + + mapper(Foo, foo) + + def teardown(self): + # clear out mapper events + Mapper.dispatch.clear() + ClassManager.dispatch.clear() + super(_MutableDictTestBase, self).teardown() + + @testing.resolve_artifact_names + def test_in_place_mutation(self): + sess = Session() + + f1 = Foo(data={'a':'b'}) + sess.add(f1) + sess.commit() + + f1.data['a'] = 'c' + sess.commit() + + eq_(f1.data, {'a':'c'}) + + @testing.resolve_artifact_names + def _test_non_mutable(self): + sess = Session() + + f1 = Foo(non_mutable_data={'a':'b'}) + sess.add(f1) + sess.commit() + + f1.non_mutable_data['a'] = 'c' + sess.commit() + + eq_(f1.non_mutable_data, {'a':'b'}) + +class MutableWithScalarPickleTest(_MutableDictTestBase, _base.MappedTest): + @classmethod + def define_tables(cls, metadata): + MutationDict = cls._type_fixture() + + Table('foo', metadata, + Column('id', Integer, primary_key=True, test_needs_pk=True), + Column('data', MutationDict.as_mutable(PickleType)), + Column('non_mutable_data', PickleType) + ) + + def test_non_mutable(self): + self._test_non_mutable() + +class MutableWithScalarJSONTest(_MutableDictTestBase, _base.MappedTest): + # json introduced in 2.6 + __skip_if__ = lambda : sys.version_info < (2, 6), + + @classmethod + def define_tables(cls, metadata): + import json + + class JSONEncodedDict(TypeDecorator): + impl = VARCHAR + + def process_bind_param(self, value, dialect): + if value is not None: + value = json.dumps(value) + + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = json.loads(value) + return value + + MutationDict = cls._type_fixture() + + Table('foo', metadata, + Column('id', Integer, primary_key=True, test_needs_pk=True), + Column('data', MutationDict.as_mutable(JSONEncodedDict)), + Column('non_mutable_data', JSONEncodedDict) + ) + + def test_non_mutable(self): + self._test_non_mutable() + +class MutableAssociationScalarPickleTest(_MutableDictTestBase, _base.MappedTest): + @classmethod + def define_tables(cls, metadata): + MutationDict = cls._type_fixture() + MutationDict.associate_with(PickleType) + + Table('foo', metadata, + Column('id', Integer, primary_key=True, test_needs_pk=True), + Column('data', PickleType) + ) + +class MutableAssociationScalarJSONTest(_MutableDictTestBase, _base.MappedTest): + # json introduced in 2.6 + __skip_if__ = lambda : sys.version_info < (2, 6), + + @classmethod + def define_tables(cls, metadata): + import json + + class JSONEncodedDict(TypeDecorator): + impl = VARCHAR + + def process_bind_param(self, value, dialect): + if value is not None: + value = json.dumps(value) + + return value + + def process_result_value(self, value, dialect): + if value is not None: + value = json.loads(value) + return value + + MutationDict = cls._type_fixture() + MutationDict.associate_with(JSONEncodedDict) + + Table('foo', metadata, + Column('id', Integer, primary_key=True, test_needs_pk=True), + Column('data', JSONEncodedDict) + ) + +class MutableCompositesTest(_base.MappedTest): + @classmethod + def define_tables(cls, metadata): + Table('foo', metadata, + Column('id', Integer, primary_key=True, test_needs_pk=True), + Column('x', Integer), + Column('y', Integer) + ) + + def teardown(self): + # clear out mapper events + Mapper.dispatch.clear() + ClassManager.dispatch.clear() + super(MutableCompositesTest, self).teardown() + + @classmethod + def _type_fixture(cls): + + from sqlalchemy.ext.mutable import Mutable + from sqlalchemy.ext.mutable import MutableComposite + + global Point + + class Point(MutableComposite): + def __init__(self, x, y): + self.x = x + self.y = y + + def __setattr__(self, key, value): + object.__setattr__(self, key, value) + self.on_change() + + def __composite_values__(self): + return self.x, self.y + + def __eq__(self, other): + return isinstance(other, Point) and \ + other.x == self.x and \ + other.y == self.y + return Point + + @classmethod + @testing.resolve_artifact_names + def setup_mappers(cls): + Point = cls._type_fixture() + + class Foo(_base.BasicEntity): + pass + + mapper(Foo, foo, properties={ + 'data':composite(Point, foo.c.x, foo.c.y) + }) + + @testing.resolve_artifact_names + def test_in_place_mutation(self): + sess = Session() + d = Point(3, 4) + f1 = Foo(data=d) + sess.add(f1) + sess.commit() + + f1.data.y = 5 + sess.commit() + + eq_(f1.data, Point(3, 5)) + +
\ No newline at end of file |
