root / elixir / trunk / tests / test_inherit.py

Revision 534, 7.3 kB (checked in by ged, 16 months ago)

- Fixed bad foreign key constraint generated for classes inheriting from a

class with multiple primary keys when using the "multi" inheritance.
Patch from & closes #114.

Line 
1"""
2test inheritance
3"""
4
5from elixir import *
6import elixir
7
8def setup():
9    metadata.bind = 'sqlite://'
10#    metadata.bind = 'postgresql://@/test'
11#    metadata.bind.echo = True
12    elixir.options_defaults['shortnames'] = True
13
14def teardown():
15    elixir.options_defaults['shortnames'] = False
16
17def do_tst(inheritance, polymorphic, expected_res):
18    class A(Entity):
19        using_options(inheritance=inheritance, polymorphic=polymorphic)
20        data1 = Field(String(20))
21
22    class B(A):
23        using_options(inheritance=inheritance, polymorphic=polymorphic)
24        data2 = Field(String(20))
25        some_e = ManyToOne('E')
26
27    class C(B):
28        using_options(inheritance=inheritance, polymorphic=polymorphic)
29        data3 = Field(String(20))
30
31    class D(A):
32        using_options(inheritance=inheritance, polymorphic=polymorphic)
33        data4 = Field(String(20))
34
35    class E(A):
36        using_options(inheritance=inheritance, polymorphic=polymorphic)
37        many_b = OneToMany('B')
38
39    setup_all(True)
40
41    e1 = E(data1='e1')
42    a1 = A(data1='a1')
43    b1 = B(data1='b1', data2='b2', some_e=e1)
44    if polymorphic:
45        c1 = C(data1='c1', data2='c2', data3='c3', some_e=e1)
46    else:
47        c1 = C(data1='c1', data2='c2', data3='c3')
48    d1 = D(data1='d1', data4='d4')
49
50    session.commit()
51    session.expunge_all()
52
53    res = {}
54    for class_ in (A, B, C, D, E):
55        res[class_.__name__] = class_.query.all()
56        res[class_.__name__].sort(key=lambda o: o.__class__.__name__)
57
58    for query_class in ('A', 'B', 'C', 'D', 'E'):
59#        print res[query_class], expected_res[query_class]
60        assert len(res[query_class]) == len(expected_res[query_class])
61        for real, expected in zip(res[query_class], expected_res[query_class]):
62            assert real.__class__.__name__ == expected
63
64
65class TestInheritance(object):
66    def teardown(self):
67        cleanup_all(True)
68
69    # this is related to SA ticket 866
70    # http://www.sqlalchemy.org/trac/ticket/866
71    # the problem was caused by the fact that the attribute-based syntax left
72    # the class-attributes in place after initialization (in Elixir 0.4).
73    def test_missing_value(self):
74        class A(Entity):
75            pass
76
77        class B(A):
78            name = Field(String(30))
79            other = Field(Text)
80
81        setup_all()
82        create_all()
83
84        b1 = B(name="b1") # no value for other
85
86        session.commit()
87
88    def test_delete_parent(self):
89        class A(Entity):
90            using_options(inheritance='multi')
91
92        class B(A):
93            using_options(inheritance='multi')
94            name = Field(String(30))
95
96        setup_all(True)
97
98        b1 = B(name='b1')
99
100        session.commit()
101
102        A.table.delete().execute()
103
104        # this doesn't work on sqlite (because it relies on the database
105        # enforcing the foreign key constraint cascade rule).
106#        assert not B.table.select().execute().fetchall()
107
108    def test_inheritance_wh_schema(self):
109        # I can only test schema stuff on postgres
110        if metadata.bind.name != 'postgres':
111            print "schema test skipped"
112            return
113
114        class A(Entity):
115            using_options(inheritance="multi")
116            using_table_options(schema="test")
117
118            row_id = Field(Integer, primary_key=True)
119            thing1 = Field(String(20))
120
121        class B(A):
122            using_options(inheritance="multi")
123            using_table_options(schema="test")
124
125            thing2 = Field(String(20))
126
127        setup_all(True)
128
129    def test_inverse_matching_on_parent(self):
130        class Person(Entity):
131            using_options(inheritance='multi')
132            name = Field(UnicodeText)
133
134        class Parent(Person):
135            using_options(inheritance='multi')
136            childs = ManyToMany('Child', tablename='child_parent',
137                                inverse='parents')
138
139        class Child(Person):
140            using_options(inheritance='multi')
141            parents = ManyToMany('Parent', tablename='child_parent',
142                                 inverse='childs')
143
144        setup_all()
145
146    def test_multi_pk(self):
147        class A(Entity):
148            using_options(inheritance='multi')
149            firstname = Field(String(50), primary_key=True)
150            lastname = Field(String(50), primary_key=True)
151
152        class B(A):
153            using_options(inheritance='multi')
154
155        setup_all(True)
156
157        b1 = B(firstname='1', lastname='b')
158        b2 = B(firstname='2', lastname='b')
159
160        session.commit()
161
162        b1.delete()
163
164        session.commit()
165
166        assert len(B.query.all()) == 1
167
168    def test_multitable_polymorphic_load(self):
169        class A(Entity):
170            using_options(inheritance='multi')
171            # we want to load children's specific data along the parent (A)
172            # data when querying the parent. If we don't specify this, the
173            # children data is loaded lazily
174            using_mapper_options(with_polymorphic='*')
175            name = Field(String(50))
176
177        class B(A):
178            using_options(inheritance='multi')
179            data = Field(String(50))
180            some_c = ManyToOne('C')
181            some_a = ManyToOne('A')
182
183        class C(A):
184            using_options(inheritance='multi')
185
186            data = Field(String(50))
187            many_b = OneToMany('B')
188        setup_all(True)
189        a1 = A(name='a1')
190        c1 = C(name='c1', data="c")
191        b1 = B(name='b1', data="b", some_c=c1)
192
193        session.commit()
194        session.expunge_all()
195
196        for a in A.query.all():
197            if isinstance(a, (B, C)):
198                # On SA 0.4.x, this test works whether with_polymorphic is
199                # specified or not, because in 0.4.x, without with_polymorphic,
200                # it issues as many queries as necessary to load all data,
201                # while in 0.5, columns are "deferred".
202                assert 'data' in a.__dict__
203
204    def test_singletable_inheritance(self):
205        do_tst('single', False, {
206            'A': ('A', 'A', 'A', 'A', 'A'),
207            'B': ('B', 'B', 'B', 'B', 'B'),
208            'C': ('C', 'C', 'C', 'C', 'C'),
209            'D': ('D', 'D', 'D', 'D', 'D'),
210            'E': ('E', 'E', 'E', 'E', 'E')
211        })
212
213    def test_polymorphic_singletable_inheritance(self):
214        do_tst('single', True, {
215            'A': ('A', 'B', 'C', 'D', 'E'),
216            'B': ('B', 'C'),
217            'C': ('C',),
218            'D': ('D',),
219            'E': ('E',),
220        })
221
222    def test_concrete_inheritance(self):
223        do_tst('concrete', False, {
224            'A': ('A',),
225            'B': ('B',),
226            'C': ('C',),
227            'D': ('D',),
228            'E': ('E',),
229        })
230
231#    def test_polymorphic_concrete_inheritance(self):
232#        do_tst('concrete', True, {
233#            'A': ('A', 'B', 'C', 'D', 'E'),
234#            'B': ('B', 'C'),
235#            'C': ('C',),
236#            'D': ('D',),
237#            'E': ('E',),
238#        })
239
240    def test_multitable_inheritance(self):
241        do_tst('multi', False, {
242            'A': ('A', 'A', 'A', 'A', 'A'),
243            'B': ('B', 'B'),
244            'C': ('C',),
245            'D': ('D',),
246            'E': ('E',),
247        })
248
249    def test_polymorphic_multitable_inheritance(self):
250        do_tst('multi', True, {
251            'A': ('A', 'B', 'C', 'D', 'E'),
252            'B': ('B', 'C'),
253            'C': ('C',),
254            'D': ('D',),
255            'E': ('E',),
256        })
Note: See TracBrowser for help on using the browser.