~ubuntu-branches/debian/jessie/sqlalchemy/jessie

« back to all changes in this revision

Viewing changes to test/ext/test_mutable.py

  • Committer: Bazaar Package Importer
  • Author(s): Piotr Ożarowski
  • Date: 2011-08-01 23:18:16 UTC
  • mfrom: (1.4.15 upstream) (16.1.14 experimental)
  • Revision ID: james.westby@ubuntu.com-20110801231816-6lx797pi3q1fpqst
Tags: 0.7.2-1
* New upstream release
* Bump minimum required python-mako version to 0.4.1 (closes: 635898)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
from sqlalchemy import Integer, ForeignKey
 
2
from sqlalchemy.types import PickleType, TypeDecorator, VARCHAR
 
3
from sqlalchemy.orm import mapper, Session, composite
 
4
from sqlalchemy.orm.mapper import Mapper
 
5
from sqlalchemy.orm.instrumentation import ClassManager
 
6
from test.lib.schema import Table, Column
 
7
from test.lib.testing import eq_, assert_raises_message
 
8
from test.lib.util import picklers
 
9
from test.lib import testing
 
10
from test.lib import fixtures
 
11
import sys
 
12
import pickle
 
13
 
 
14
class Foo(fixtures.BasicEntity):
 
15
    pass
 
16
 
 
17
class SubFoo(Foo):
 
18
    pass
 
19
 
 
20
class _MutableDictTestBase(object):
 
21
    run_define_tables = 'each'
 
22
 
 
23
    @classmethod
 
24
    def _type_fixture(cls):
 
25
        from sqlalchemy.ext.mutable import Mutable
 
26
 
 
27
        # needed for pickle support
 
28
        global MutationDict
 
29
 
 
30
        class MutationDict(Mutable, dict):
 
31
            @classmethod
 
32
            def coerce(cls, key, value):
 
33
                if not isinstance(value, MutationDict):
 
34
                    if isinstance(value, dict):
 
35
                        return MutationDict(value)
 
36
                    return Mutable.coerce(key, value)
 
37
                else:
 
38
                    return value
 
39
 
 
40
            def __getstate__(self):
 
41
                return dict(self)
 
42
 
 
43
            def __setstate__(self, state):
 
44
                self.update(state)
 
45
 
 
46
            def __setitem__(self, key, value):
 
47
                dict.__setitem__(self, key, value)
 
48
                self.changed()
 
49
 
 
50
            def __delitem__(self, key):
 
51
                dict.__delitem__(self, key)
 
52
                self.changed()
 
53
        return MutationDict
 
54
 
 
55
    def setup_mappers(cls):
 
56
        foo = cls.tables.foo
 
57
 
 
58
        mapper(Foo, foo)
 
59
 
 
60
    def teardown(self):
 
61
        # clear out mapper events
 
62
        Mapper.dispatch._clear()
 
63
        ClassManager.dispatch._clear()
 
64
        super(_MutableDictTestBase, self).teardown()
 
65
 
 
66
    def test_coerce_none(self):
 
67
        sess = Session()
 
68
        f1 = Foo(data=None)
 
69
        sess.add(f1)
 
70
        sess.commit()
 
71
        eq_(f1.data, None)
 
72
 
 
73
    def test_coerce_raise(self):
 
74
        assert_raises_message(
 
75
            ValueError,
 
76
            "Attribute 'data' does not accept objects of type",
 
77
            Foo, data=set([1,2,3])
 
78
        )
 
79
 
 
80
    def test_in_place_mutation(self):
 
81
        sess = Session()
 
82
 
 
83
        f1 = Foo(data={'a':'b'})
 
84
        sess.add(f1)
 
85
        sess.commit()
 
86
 
 
87
        f1.data['a'] = 'c'
 
88
        sess.commit()
 
89
 
 
90
        eq_(f1.data, {'a':'c'})
 
91
 
 
92
    def test_replace(self):
 
93
        sess = Session()
 
94
        f1 = Foo(data={'a':'b'})
 
95
        sess.add(f1)
 
96
        sess.flush()
 
97
 
 
98
        f1.data = {'b':'c'}
 
99
        sess.commit()
 
100
        eq_(f1.data, {'b':'c'})
 
101
 
 
102
    def test_pickle_parent(self):
 
103
        sess = Session()
 
104
 
 
105
        f1 = Foo(data={'a':'b'})
 
106
        sess.add(f1)
 
107
        sess.commit()
 
108
        f1.data
 
109
        sess.close()
 
110
 
 
111
        for loads, dumps in picklers():
 
112
            sess = Session()
 
113
            f2 = loads(dumps(f1))
 
114
            sess.add(f2)
 
115
            f2.data['a'] = 'c'
 
116
            assert f2 in sess.dirty
 
117
 
 
118
    def _test_non_mutable(self):
 
119
        sess = Session()
 
120
 
 
121
        f1 = Foo(non_mutable_data={'a':'b'})
 
122
        sess.add(f1)
 
123
        sess.commit()
 
124
 
 
125
        f1.non_mutable_data['a'] = 'c'
 
126
        sess.commit()
 
127
 
 
128
        eq_(f1.non_mutable_data, {'a':'b'})
 
129
 
 
130
class MutableWithScalarPickleTest(_MutableDictTestBase, fixtures.MappedTest):
 
131
    @classmethod
 
132
    def define_tables(cls, metadata):
 
133
        MutationDict = cls._type_fixture()
 
134
 
 
135
        mutable_pickle = MutationDict.as_mutable(PickleType)
 
136
        Table('foo', metadata,
 
137
            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 
138
            Column('skip', mutable_pickle),
 
139
            Column('data', mutable_pickle),
 
140
            Column('non_mutable_data', PickleType)
 
141
        )
 
142
 
 
143
    def test_non_mutable(self):
 
144
        self._test_non_mutable()
 
145
 
 
146
class MutableWithScalarJSONTest(_MutableDictTestBase, fixtures.MappedTest):
 
147
    # json introduced in 2.6
 
148
    __skip_if__ = lambda : sys.version_info < (2, 6),
 
149
 
 
150
    @classmethod
 
151
    def define_tables(cls, metadata):
 
152
        import json
 
153
 
 
154
        class JSONEncodedDict(TypeDecorator):
 
155
            impl = VARCHAR(50)
 
156
 
 
157
            def process_bind_param(self, value, dialect):
 
158
                if value is not None:
 
159
                    value = json.dumps(value)
 
160
 
 
161
                return value
 
162
 
 
163
            def process_result_value(self, value, dialect):
 
164
                if value is not None:
 
165
                    value = json.loads(value)
 
166
                return value
 
167
 
 
168
        MutationDict = cls._type_fixture()
 
169
 
 
170
        Table('foo', metadata,
 
171
            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 
172
            Column('data', MutationDict.as_mutable(JSONEncodedDict)),
 
173
            Column('non_mutable_data', JSONEncodedDict)
 
174
        )
 
175
 
 
176
    def test_non_mutable(self):
 
177
        self._test_non_mutable()
 
178
 
 
179
class MutableAssocWithAttrInheritTest(_MutableDictTestBase, fixtures.MappedTest):
 
180
    @classmethod
 
181
    def define_tables(cls, metadata):
 
182
        MutationDict = cls._type_fixture()
 
183
 
 
184
        Table('foo', metadata,
 
185
            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 
186
            Column('data', PickleType),
 
187
            Column('non_mutable_data', PickleType)
 
188
        )
 
189
 
 
190
        Table('subfoo', metadata,
 
191
            Column('id', Integer, ForeignKey('foo.id'), primary_key=True),
 
192
        )
 
193
 
 
194
    def setup_mappers(cls):
 
195
        foo = cls.tables.foo
 
196
        subfoo = cls.tables.subfoo
 
197
 
 
198
        mapper(Foo, foo)
 
199
        mapper(SubFoo, subfoo, inherits=Foo)
 
200
        MutationDict.associate_with_attribute(Foo.data)
 
201
 
 
202
    def test_in_place_mutation(self):
 
203
        sess = Session()
 
204
 
 
205
        f1 = SubFoo(data={'a':'b'})
 
206
        sess.add(f1)
 
207
        sess.commit()
 
208
 
 
209
        f1.data['a'] = 'c'
 
210
        sess.commit()
 
211
 
 
212
        eq_(f1.data, {'a':'c'})
 
213
 
 
214
    def test_replace(self):
 
215
        sess = Session()
 
216
        f1 = SubFoo(data={'a':'b'})
 
217
        sess.add(f1)
 
218
        sess.flush()
 
219
 
 
220
        f1.data = {'b':'c'}
 
221
        sess.commit()
 
222
        eq_(f1.data, {'b':'c'})
 
223
 
 
224
class MutableAssociationScalarPickleTest(_MutableDictTestBase, fixtures.MappedTest):
 
225
    @classmethod
 
226
    def define_tables(cls, metadata):
 
227
        MutationDict = cls._type_fixture()
 
228
        MutationDict.associate_with(PickleType)
 
229
 
 
230
        Table('foo', metadata,
 
231
            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 
232
            Column('skip', PickleType),
 
233
            Column('data', PickleType)
 
234
        )
 
235
 
 
236
class MutableAssociationScalarJSONTest(_MutableDictTestBase, fixtures.MappedTest):
 
237
    # json introduced in 2.6
 
238
    __skip_if__ = lambda : sys.version_info < (2, 6),
 
239
 
 
240
    @classmethod
 
241
    def define_tables(cls, metadata):
 
242
        import json
 
243
 
 
244
        class JSONEncodedDict(TypeDecorator):
 
245
            impl = VARCHAR(50)
 
246
 
 
247
            def process_bind_param(self, value, dialect):
 
248
                if value is not None:
 
249
                    value = json.dumps(value)
 
250
 
 
251
                return value
 
252
 
 
253
            def process_result_value(self, value, dialect):
 
254
                if value is not None:
 
255
                    value = json.loads(value)
 
256
                return value
 
257
 
 
258
        MutationDict = cls._type_fixture()
 
259
        MutationDict.associate_with(JSONEncodedDict)
 
260
 
 
261
        Table('foo', metadata,
 
262
            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 
263
            Column('data', JSONEncodedDict)
 
264
        )
 
265
 
 
266
 
 
267
class _CompositeTestBase(object):
 
268
    @classmethod
 
269
    def define_tables(cls, metadata):
 
270
        Table('foo', metadata,
 
271
            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 
272
            Column('x', Integer),
 
273
            Column('y', Integer)
 
274
        )
 
275
 
 
276
    def teardown(self):
 
277
        # clear out mapper events
 
278
        Mapper.dispatch._clear()
 
279
        ClassManager.dispatch._clear()
 
280
        super(_CompositeTestBase, self).teardown()
 
281
 
 
282
    @classmethod
 
283
    def _type_fixture(cls):
 
284
 
 
285
        from sqlalchemy.ext.mutable import Mutable
 
286
        from sqlalchemy.ext.mutable import MutableComposite
 
287
 
 
288
        global Point
 
289
 
 
290
        class Point(MutableComposite):
 
291
            def __init__(self, x, y):
 
292
                self.x = x
 
293
                self.y = y
 
294
 
 
295
            def __setattr__(self, key, value):
 
296
                object.__setattr__(self, key, value)
 
297
                self.changed()
 
298
 
 
299
            def __composite_values__(self):
 
300
                return self.x, self.y
 
301
 
 
302
            def __getstate__(self):
 
303
                d = dict(self.__dict__)
 
304
                d.pop('_parents', None)
 
305
                return d
 
306
 
 
307
            #def __setstate__(self, state):
 
308
            #    self.x, self.y = state
 
309
 
 
310
            def __eq__(self, other):
 
311
                return isinstance(other, Point) and \
 
312
                    other.x == self.x and \
 
313
                    other.y == self.y
 
314
        return Point
 
315
 
 
316
class MutableCompositesTest(_CompositeTestBase, fixtures.MappedTest):
 
317
 
 
318
    @classmethod
 
319
    def setup_mappers(cls):
 
320
        foo = cls.tables.foo
 
321
 
 
322
        Point = cls._type_fixture()
 
323
 
 
324
        mapper(Foo, foo, properties={
 
325
            'data':composite(Point, foo.c.x, foo.c.y)
 
326
        })
 
327
 
 
328
    def test_in_place_mutation(self):
 
329
        sess = Session()
 
330
        d = Point(3, 4)
 
331
        f1 = Foo(data=d)
 
332
        sess.add(f1)
 
333
        sess.commit()
 
334
 
 
335
        f1.data.y = 5
 
336
        sess.commit()
 
337
 
 
338
        eq_(f1.data, Point(3, 5))
 
339
 
 
340
    def test_pickle_of_parent(self):
 
341
        sess = Session()
 
342
        d = Point(3, 4)
 
343
        f1 = Foo(data=d)
 
344
        sess.add(f1)
 
345
        sess.commit()
 
346
 
 
347
        f1.data
 
348
        assert 'data' in f1.__dict__
 
349
        sess.close()
 
350
 
 
351
        for loads, dumps in picklers():
 
352
            sess = Session()
 
353
            f2 = loads(dumps(f1))
 
354
            sess.add(f2)
 
355
            f2.data.y = 12
 
356
            assert f2 in sess.dirty
 
357
 
 
358
    def test_set_none(self):
 
359
        sess = Session()
 
360
        f1 = Foo(data=None)
 
361
        sess.add(f1)
 
362
        sess.commit()
 
363
        eq_(f1.data, Point(None, None))
 
364
 
 
365
        f1.data.y = 5
 
366
        sess.commit()
 
367
        eq_(f1.data, Point(None, 5))
 
368
 
 
369
    def test_set_illegal(self):
 
370
        f1 = Foo()
 
371
        assert_raises_message(
 
372
            ValueError,
 
373
            "Attribute 'data' does not accept objects",
 
374
            setattr, f1, 'data', 'foo'
 
375
        )
 
376
 
 
377
class MutableInheritedCompositesTest(_CompositeTestBase, fixtures.MappedTest):
 
378
    @classmethod
 
379
    def define_tables(cls, metadata):
 
380
        Table('foo', metadata,
 
381
            Column('id', Integer, primary_key=True, test_needs_autoincrement=True),
 
382
            Column('x', Integer),
 
383
            Column('y', Integer)
 
384
        )
 
385
        Table('subfoo', metadata,
 
386
            Column('id', Integer, ForeignKey('foo.id'), primary_key=True),
 
387
        )
 
388
 
 
389
    @classmethod
 
390
    def setup_mappers(cls):
 
391
        foo = cls.tables.foo
 
392
        subfoo = cls.tables.subfoo
 
393
 
 
394
        Point = cls._type_fixture()
 
395
 
 
396
        mapper(Foo, foo, properties={
 
397
            'data':composite(Point, foo.c.x, foo.c.y)
 
398
        })
 
399
        mapper(SubFoo, subfoo, inherits=Foo)
 
400
 
 
401
    def test_in_place_mutation_subclass(self):
 
402
        sess = Session()
 
403
        d = Point(3, 4)
 
404
        f1 = SubFoo(data=d)
 
405
        sess.add(f1)
 
406
        sess.commit()
 
407
 
 
408
        f1.data.y = 5
 
409
        sess.commit()
 
410
 
 
411
        eq_(f1.data, Point(3, 5))
 
412
 
 
413
    def test_pickle_of_parent_subclass(self):
 
414
        sess = Session()
 
415
        d = Point(3, 4)
 
416
        f1 = SubFoo(data=d)
 
417
        sess.add(f1)
 
418
        sess.commit()
 
419
 
 
420
        f1.data
 
421
        assert 'data' in f1.__dict__
 
422
        sess.close()
 
423
 
 
424
        for loads, dumps in picklers():
 
425
            sess = Session()
 
426
            f2 = loads(dumps(f1))
 
427
            sess.add(f2)
 
428
            f2.data.y = 12
 
429
            assert f2 in sess.dirty
 
430