1
from sqlalchemy.testing import eq_, assert_raises, \
2
assert_raises_message, is_
3
from sqlalchemy.ext import declarative as decl
4
import sqlalchemy as sa
5
from sqlalchemy import testing
6
from sqlalchemy import Integer, String, ForeignKey
7
from sqlalchemy.testing.schema import Table, Column
8
from sqlalchemy.orm import relationship, create_session, class_mapper, \
9
configure_mappers, clear_mappers, \
10
deferred, column_property, \
12
from sqlalchemy.util import classproperty
13
from sqlalchemy.ext.declarative import declared_attr
14
from sqlalchemy.testing import fixtures
18
class DeclarativeTestBase(fixtures.TestBase, testing.AssertsExecutionResults):
21
Base = decl.declarative_base(testing.db)
26
Base.metadata.drop_all()
28
class DeclarativeMixinTest(DeclarativeTestBase):
30
def test_simple(self):
32
class MyMixin(object):
34
id = Column(Integer, primary_key=True,
35
test_needs_autoincrement=True)
38
return 'bar' + str(self.id)
40
class MyModel(Base, MyMixin):
42
__tablename__ = 'test'
43
name = Column(String(100), nullable=False, index=True)
45
Base.metadata.create_all()
46
session = create_session()
47
session.add(MyModel(name='testing'))
50
obj = session.query(MyModel).one()
52
eq_(obj.name, 'testing')
53
eq_(obj.foo(), 'bar1')
55
def test_unique_column(self):
57
class MyMixin(object):
59
id = Column(Integer, primary_key=True)
60
value = Column(String, unique=True)
62
class MyModel(Base, MyMixin):
64
__tablename__ = 'test'
66
assert MyModel.__table__.c.value.unique
68
def test_hierarchical_bases(self):
72
id = Column(Integer, primary_key=True,
73
test_needs_autoincrement=True)
76
return 'bar' + str(self.id)
78
class MyMixin(MyMixinParent):
80
baz = Column(String(100), nullable=False, index=True)
82
class MyModel(Base, MyMixin):
84
__tablename__ = 'test'
85
name = Column(String(100), nullable=False, index=True)
87
Base.metadata.create_all()
88
session = create_session()
89
session.add(MyModel(name='testing', baz='fu'))
92
obj = session.query(MyModel).one()
94
eq_(obj.name, 'testing')
95
eq_(obj.foo(), 'bar1')
98
def test_mixin_overrides(self):
99
"""test a mixin that overrides a column on a superclass."""
101
class MixinA(object):
102
foo = Column(String(50))
104
class MixinB(MixinA):
105
foo = Column(Integer)
107
class MyModelA(Base, MixinA):
108
__tablename__ = 'testa'
109
id = Column(Integer, primary_key=True)
111
class MyModelB(Base, MixinB):
112
__tablename__ = 'testb'
113
id = Column(Integer, primary_key=True)
115
eq_(MyModelA.__table__.c.foo.type.__class__, String)
116
eq_(MyModelB.__table__.c.foo.type.__class__, Integer)
119
def test_not_allowed(self):
122
foo = Column(Integer, ForeignKey('bar.id'))
125
class MyModel(Base, MyMixin):
126
__tablename__ = 'foo'
128
assert_raises(sa.exc.InvalidRequestError, go)
131
foo = relationship('Bar')
134
class MyModel(Base, MyRelMixin):
136
__tablename__ = 'foo'
138
assert_raises(sa.exc.InvalidRequestError, go)
141
foo = deferred(Column('foo', String))
144
class MyModel(Base, MyDefMixin):
145
__tablename__ = 'foo'
147
assert_raises(sa.exc.InvalidRequestError, go)
150
foo = column_property(Column('foo', String))
153
class MyModel(Base, MyCPropMixin):
154
__tablename__ = 'foo'
156
assert_raises(sa.exc.InvalidRequestError, go)
158
def test_table_name_inherited(self):
162
def __tablename__(cls):
163
return cls.__name__.lower()
164
id = Column(Integer, primary_key=True)
166
class MyModel(Base, MyMixin):
169
eq_(MyModel.__table__.name, 'mymodel')
171
def test_classproperty_still_works(self):
172
class MyMixin(object):
174
def __tablename__(cls):
175
return cls.__name__.lower()
176
id = Column(Integer, primary_key=True)
178
class MyModel(Base, MyMixin):
179
__tablename__ = 'overridden'
181
eq_(MyModel.__table__.name, 'overridden')
183
def test_table_name_not_inherited(self):
187
def __tablename__(cls):
188
return cls.__name__.lower()
189
id = Column(Integer, primary_key=True)
191
class MyModel(Base, MyMixin):
192
__tablename__ = 'overridden'
194
eq_(MyModel.__table__.name, 'overridden')
196
def test_table_name_inheritance_order(self):
200
def __tablename__(cls):
201
return cls.__name__.lower() + '1'
205
def __tablename__(cls):
206
return cls.__name__.lower() + '2'
208
class MyModel(Base, MyMixin1, MyMixin2):
209
id = Column(Integer, primary_key=True)
211
eq_(MyModel.__table__.name, 'mymodel1')
213
def test_table_name_dependent_on_subclass(self):
215
class MyHistoryMixin:
217
def __tablename__(cls):
218
return cls.parent_name + '_changelog'
220
class MyModel(Base, MyHistoryMixin):
222
id = Column(Integer, primary_key=True)
224
eq_(MyModel.__table__.name, 'foo_changelog')
226
def test_table_args_inherited(self):
229
__table_args__ = {'mysql_engine': 'InnoDB'}
231
class MyModel(Base, MyMixin):
232
__tablename__ = 'test'
233
id = Column(Integer, primary_key=True)
235
eq_(MyModel.__table__.kwargs, {'mysql_engine': 'InnoDB'})
237
def test_table_args_inherited_descriptor(self):
241
def __table_args__(cls):
242
return {'info': cls.__name__}
244
class MyModel(Base, MyMixin):
245
__tablename__ = 'test'
246
id = Column(Integer, primary_key=True)
248
eq_(MyModel.__table__.info, 'MyModel')
250
def test_table_args_inherited_single_table_inheritance(self):
253
__table_args__ = {'mysql_engine': 'InnoDB'}
255
class General(Base, MyMixin):
256
__tablename__ = 'test'
257
id = Column(Integer, primary_key=True)
258
type_ = Column(String(50))
259
__mapper__args = {'polymorphic_on': type_}
261
class Specific(General):
262
__mapper_args__ = {'polymorphic_identity': 'specific'}
264
assert Specific.__table__ is General.__table__
265
eq_(General.__table__.kwargs, {'mysql_engine': 'InnoDB'})
267
def test_columns_single_table_inheritance(self):
268
"""Test a column on a mixin with an alternate attribute name,
269
mapped to a superclass and single-table inheritance subclass.
270
The superclass table gets the column, the subclass shares
275
class MyMixin(object):
276
foo = Column('foo', Integer)
277
bar = Column('bar_newname', Integer)
279
class General(Base, MyMixin):
280
__tablename__ = 'test'
281
id = Column(Integer, primary_key=True)
282
type_ = Column(String(50))
283
__mapper__args = {'polymorphic_on': type_}
285
class Specific(General):
286
__mapper_args__ = {'polymorphic_identity': 'specific'}
288
assert General.bar.prop.columns[0] is General.__table__.c.bar_newname
289
assert len(General.bar.prop.columns) == 1
290
assert Specific.bar.prop is General.bar.prop
292
@testing.skip_if(lambda: testing.against('oracle'),
293
"Test has an empty insert in it at the moment")
294
def test_columns_single_inheritance_conflict_resolution(self):
295
"""Test that a declared_attr can return the existing column and it will
296
be ignored. this allows conditional columns to be added.
302
__tablename__ = 'person'
303
id = Column(Integer, primary_key=True)
308
return cls.__table__.c.get('target_id',
309
Column(Integer, ForeignKey('other.id'))
314
return relationship("Other")
316
class Engineer(Mixin, Person):
317
"""single table inheritance"""
319
class Manager(Mixin, Person):
320
"""single table inheritance"""
323
__tablename__ = 'other'
324
id = Column(Integer, primary_key=True)
327
Engineer.target_id.property.columns[0],
328
Person.__table__.c.target_id
331
Manager.target_id.property.columns[0],
332
Person.__table__.c.target_id
334
# do a brief round trip on this
335
Base.metadata.create_all()
337
o1, o2 = Other(), Other()
344
eq_(session.query(Engineer).first().target, o1)
347
def test_columns_joined_table_inheritance(self):
348
"""Test a column on a mixin with an alternate attribute name,
349
mapped to a superclass and joined-table inheritance subclass.
350
Both tables get the column, in the case of the subclass the two
351
columns are joined under one MapperProperty.
355
class MyMixin(object):
356
foo = Column('foo', Integer)
357
bar = Column('bar_newname', Integer)
359
class General(Base, MyMixin):
360
__tablename__ = 'test'
361
id = Column(Integer, primary_key=True)
362
type_ = Column(String(50))
363
__mapper__args = {'polymorphic_on': type_}
365
class Specific(General):
366
__tablename__ = 'sub'
367
id = Column(Integer, ForeignKey('test.id'), primary_key=True)
368
__mapper_args__ = {'polymorphic_identity': 'specific'}
370
assert General.bar.prop.columns[0] is General.__table__.c.bar_newname
371
assert len(General.bar.prop.columns) == 1
372
assert Specific.bar.prop is General.bar.prop
373
eq_(len(Specific.bar.prop.columns), 1)
374
assert Specific.bar.prop.columns[0] is General.__table__.c.bar_newname
376
def test_column_join_checks_superclass_type(self):
377
"""Test that the logic which joins subclass props to those
378
of the superclass checks that the superclass property is a column.
383
__tablename__ = 'test'
384
id = Column(Integer, primary_key=True)
385
general_id = Column(Integer, ForeignKey('test.id'))
386
type_ = relationship("General")
388
class Specific(General):
389
__tablename__ = 'sub'
390
id = Column(Integer, ForeignKey('test.id'), primary_key=True)
391
type_ = Column('foob', String(50))
393
assert isinstance(General.type_.property, sa.orm.RelationshipProperty)
394
assert Specific.type_.property.columns[0] is Specific.__table__.c.foob
396
def test_column_join_checks_subclass_type(self):
397
"""Test that the logic which joins subclass props to those
398
of the superclass checks that the subclass property is a column.
404
__tablename__ = 'test'
405
id = Column(Integer, primary_key=True)
406
type_ = Column('foob', Integer)
408
class Specific(General):
409
__tablename__ = 'sub'
410
id = Column(Integer, ForeignKey('test.id'), primary_key=True)
411
specific_id = Column(Integer, ForeignKey('sub.id'))
412
type_ = relationship("Specific")
413
assert_raises_message(
414
sa.exc.ArgumentError, "column 'foob' conflicts with property", go
417
def test_table_args_overridden(self):
420
__table_args__ = {'mysql_engine': 'Foo'}
422
class MyModel(Base, MyMixin):
423
__tablename__ = 'test'
424
__table_args__ = {'mysql_engine': 'InnoDB'}
425
id = Column(Integer, primary_key=True)
427
eq_(MyModel.__table__.kwargs, {'mysql_engine': 'InnoDB'})
429
def test_mapper_args_declared_attr(self):
431
class ComputedMapperArgs:
433
def __mapper_args__(cls):
434
if cls.__name__ == 'Person':
435
return {'polymorphic_on': cls.discriminator}
437
return {'polymorphic_identity': cls.__name__}
439
class Person(Base, ComputedMapperArgs):
440
__tablename__ = 'people'
441
id = Column(Integer, primary_key=True)
442
discriminator = Column('type', String(50))
444
class Engineer(Person):
448
assert class_mapper(Person).polymorphic_on \
449
is Person.__table__.c.type
450
eq_(class_mapper(Engineer).polymorphic_identity, 'Engineer')
452
def test_mapper_args_declared_attr_two(self):
454
# same as test_mapper_args_declared_attr, but we repeat
455
# ComputedMapperArgs on both classes for no apparent reason.
457
class ComputedMapperArgs:
459
def __mapper_args__(cls):
460
if cls.__name__ == 'Person':
461
return {'polymorphic_on': cls.discriminator}
463
return {'polymorphic_identity': cls.__name__}
465
class Person(Base, ComputedMapperArgs):
467
__tablename__ = 'people'
468
id = Column(Integer, primary_key=True)
469
discriminator = Column('type', String(50))
471
class Engineer(Person, ComputedMapperArgs):
475
assert class_mapper(Person).polymorphic_on \
476
is Person.__table__.c.type
477
eq_(class_mapper(Engineer).polymorphic_identity, 'Engineer')
479
def test_table_args_composite(self):
483
__table_args__ = {'info': {'baz': 'bob'}}
487
__table_args__ = {'info': {'foo': 'bar'}}
489
class MyModel(Base, MyMixin1, MyMixin2):
491
__tablename__ = 'test'
494
def __table_args__(self):
496
args = dict(info=info)
497
info.update(MyMixin1.__table_args__['info'])
498
info.update(MyMixin2.__table_args__['info'])
500
id = Column(Integer, primary_key=True)
502
eq_(MyModel.__table__.info, {'foo': 'bar', 'baz': 'bob'})
504
def test_mapper_args_inherited(self):
508
__mapper_args__ = {'always_refresh': True}
510
class MyModel(Base, MyMixin):
512
__tablename__ = 'test'
513
id = Column(Integer, primary_key=True)
515
eq_(MyModel.__mapper__.always_refresh, True)
517
def test_mapper_args_inherited_descriptor(self):
522
def __mapper_args__(cls):
524
# tenuous, but illustrates the problem!
526
if cls.__name__ == 'MyModel':
527
return dict(always_refresh=True)
529
return dict(always_refresh=False)
531
class MyModel(Base, MyMixin):
533
__tablename__ = 'test'
534
id = Column(Integer, primary_key=True)
536
eq_(MyModel.__mapper__.always_refresh, True)
538
def test_mapper_args_polymorphic_on_inherited(self):
542
type_ = Column(String(50))
543
__mapper_args__ = {'polymorphic_on': type_}
545
class MyModel(Base, MyMixin):
547
__tablename__ = 'test'
548
id = Column(Integer, primary_key=True)
550
col = MyModel.__mapper__.polymorphic_on
551
eq_(col.name, 'type_')
552
assert col.table is not None
554
def test_mapper_args_overridden(self):
558
__mapper_args__ = dict(always_refresh=True)
560
class MyModel(Base, MyMixin):
562
__tablename__ = 'test'
563
__mapper_args__ = dict(always_refresh=False)
564
id = Column(Integer, primary_key=True)
566
eq_(MyModel.__mapper__.always_refresh, False)
568
def test_mapper_args_composite(self):
572
type_ = Column(String(50))
573
__mapper_args__ = {'polymorphic_on': type_}
577
__mapper_args__ = {'always_refresh': True}
579
class MyModel(Base, MyMixin1, MyMixin2):
581
__tablename__ = 'test'
584
def __mapper_args__(cls):
586
args.update(MyMixin1.__mapper_args__)
587
args.update(MyMixin2.__mapper_args__)
588
if cls.__name__ != 'MyModel':
589
args.pop('polymorphic_on')
590
args['polymorphic_identity'] = cls.__name__
593
id = Column(Integer, primary_key=True)
595
class MySubModel(MyModel):
599
MyModel.__mapper__.polymorphic_on.name,
602
assert MyModel.__mapper__.polymorphic_on.table is not None
603
eq_(MyModel.__mapper__.always_refresh, True)
604
eq_(MySubModel.__mapper__.always_refresh, True)
605
eq_(MySubModel.__mapper__.polymorphic_identity, 'MySubModel')
607
def test_mapper_args_property(self):
611
def __tablename__(cls):
612
return cls.__name__.lower()
615
def __table_args__(cls):
616
return {'mysql_engine':'InnoDB'}
619
def __mapper_args__(cls):
621
args['polymorphic_identity'] = cls.__name__
623
id = Column(Integer, primary_key=True)
625
class MySubModel(MyModel):
626
id = Column(Integer, ForeignKey('mymodel.id'), primary_key=True)
628
class MySubModel2(MyModel):
629
__tablename__ = 'sometable'
630
id = Column(Integer, ForeignKey('mymodel.id'), primary_key=True)
632
eq_(MyModel.__mapper__.polymorphic_identity, 'MyModel')
633
eq_(MySubModel.__mapper__.polymorphic_identity, 'MySubModel')
634
eq_(MyModel.__table__.kwargs['mysql_engine'], 'InnoDB')
635
eq_(MySubModel.__table__.kwargs['mysql_engine'], 'InnoDB')
636
eq_(MySubModel2.__table__.kwargs['mysql_engine'], 'InnoDB')
637
eq_(MyModel.__table__.name, 'mymodel')
638
eq_(MySubModel.__table__.name, 'mysubmodel')
640
def test_mapper_args_custom_base(self):
641
"""test the @declared_attr approach from a custom base."""
645
def __tablename__(cls):
646
return cls.__name__.lower()
649
def __table_args__(cls):
650
return {'mysql_engine':'InnoDB'}
654
return Column(Integer, primary_key=True)
656
Base = decl.declarative_base(cls=Base)
661
class MyOtherClass(Base):
664
eq_(MyClass.__table__.kwargs['mysql_engine'], 'InnoDB')
665
eq_(MyClass.__table__.name, 'myclass')
666
eq_(MyOtherClass.__table__.name, 'myotherclass')
667
assert MyClass.__table__.c.id.table is MyClass.__table__
668
assert MyOtherClass.__table__.c.id.table is MyOtherClass.__table__
670
def test_single_table_no_propagation(self):
674
id = Column(Integer, primary_key=True)
676
class Generic(Base, IdColumn):
678
__tablename__ = 'base'
679
discriminator = Column('type', String(50))
680
__mapper_args__ = dict(polymorphic_on=discriminator)
681
value = Column(Integer())
683
class Specific(Generic):
685
__mapper_args__ = dict(polymorphic_identity='specific')
687
assert Specific.__table__ is Generic.__table__
688
eq_(Generic.__table__.c.keys(), ['id', 'type', 'value'])
689
assert class_mapper(Specific).polymorphic_on \
690
is Generic.__table__.c.type
691
eq_(class_mapper(Specific).polymorphic_identity, 'specific')
693
def test_joined_table_propagation(self):
698
def __tablename__(cls):
699
return cls.__name__.lower()
700
__table_args__ = {'mysql_engine': 'InnoDB'}
701
timestamp = Column(Integer)
702
id = Column(Integer, primary_key=True)
704
class Generic(Base, CommonMixin):
706
discriminator = Column('python_type', String(50))
707
__mapper_args__ = dict(polymorphic_on=discriminator)
709
class Specific(Generic):
711
__mapper_args__ = dict(polymorphic_identity='specific')
712
id = Column(Integer, ForeignKey('generic.id'),
715
eq_(Generic.__table__.name, 'generic')
716
eq_(Specific.__table__.name, 'specific')
717
eq_(Generic.__table__.c.keys(), ['timestamp', 'id',
719
eq_(Specific.__table__.c.keys(), ['id'])
720
eq_(Generic.__table__.kwargs, {'mysql_engine': 'InnoDB'})
721
eq_(Specific.__table__.kwargs, {'mysql_engine': 'InnoDB'})
723
def test_some_propagation(self):
728
def __tablename__(cls):
729
return cls.__name__.lower()
730
__table_args__ = {'mysql_engine': 'InnoDB'}
731
timestamp = Column(Integer)
733
class BaseType(Base, CommonMixin):
735
discriminator = Column('type', String(50))
736
__mapper_args__ = dict(polymorphic_on=discriminator)
737
id = Column(Integer, primary_key=True)
738
value = Column(Integer())
740
class Single(BaseType):
743
__mapper_args__ = dict(polymorphic_identity='type1')
745
class Joined(BaseType):
747
__mapper_args__ = dict(polymorphic_identity='type2')
748
id = Column(Integer, ForeignKey('basetype.id'),
751
eq_(BaseType.__table__.name, 'basetype')
752
eq_(BaseType.__table__.c.keys(), ['timestamp', 'type', 'id',
754
eq_(BaseType.__table__.kwargs, {'mysql_engine': 'InnoDB'})
755
assert Single.__table__ is BaseType.__table__
756
eq_(Joined.__table__.name, 'joined')
757
eq_(Joined.__table__.c.keys(), ['id'])
758
eq_(Joined.__table__.kwargs, {'mysql_engine': 'InnoDB'})
760
def test_col_copy_vs_declared_attr_joined_propagation(self):
766
return Column(Integer)
768
class A(Mixin, Base):
770
id = Column(Integer, primary_key=True)
774
id = Column(Integer, ForeignKey('a.id'), primary_key=True)
776
assert 'a' in A.__table__.c
777
assert 'b' in A.__table__.c
778
assert 'a' not in B.__table__.c
779
assert 'b' not in B.__table__.c
781
def test_col_copy_vs_declared_attr_joined_propagation_newname(self):
783
a = Column('a1', Integer)
787
return Column('b1', Integer)
789
class A(Mixin, Base):
791
id = Column(Integer, primary_key=True)
795
id = Column(Integer, ForeignKey('a.id'), primary_key=True)
797
assert 'a1' in A.__table__.c
798
assert 'b1' in A.__table__.c
799
assert 'a1' not in B.__table__.c
800
assert 'b1' not in B.__table__.c
802
def test_col_copy_vs_declared_attr_single_propagation(self):
808
return Column(Integer)
810
class A(Mixin, Base):
812
id = Column(Integer, primary_key=True)
817
assert 'a' in A.__table__.c
818
assert 'b' in A.__table__.c
820
def test_non_propagating_mixin(self):
822
class NoJoinedTableNameMixin:
825
def __tablename__(cls):
826
if decl.has_inherited_table(cls):
828
return cls.__name__.lower()
830
class BaseType(Base, NoJoinedTableNameMixin):
832
discriminator = Column('type', String(50))
833
__mapper_args__ = dict(polymorphic_on=discriminator)
834
id = Column(Integer, primary_key=True)
835
value = Column(Integer())
837
class Specific(BaseType):
839
__mapper_args__ = dict(polymorphic_identity='specific')
841
eq_(BaseType.__table__.name, 'basetype')
842
eq_(BaseType.__table__.c.keys(), ['type', 'id', 'value'])
843
assert Specific.__table__ is BaseType.__table__
844
assert class_mapper(Specific).polymorphic_on \
845
is BaseType.__table__.c.type
846
eq_(class_mapper(Specific).polymorphic_identity, 'specific')
848
def test_non_propagating_mixin_used_for_joined(self):
850
class TableNameMixin:
853
def __tablename__(cls):
854
if decl.has_inherited_table(cls) and TableNameMixin \
855
not in cls.__bases__:
857
return cls.__name__.lower()
859
class BaseType(Base, TableNameMixin):
861
discriminator = Column('type', String(50))
862
__mapper_args__ = dict(polymorphic_on=discriminator)
863
id = Column(Integer, primary_key=True)
864
value = Column(Integer())
866
class Specific(BaseType, TableNameMixin):
868
__mapper_args__ = dict(polymorphic_identity='specific')
869
id = Column(Integer, ForeignKey('basetype.id'),
872
eq_(BaseType.__table__.name, 'basetype')
873
eq_(BaseType.__table__.c.keys(), ['type', 'id', 'value'])
874
eq_(Specific.__table__.name, 'specific')
875
eq_(Specific.__table__.c.keys(), ['id'])
877
def test_single_back_propagate(self):
881
timestamp = Column(Integer)
883
class BaseType(Base):
885
__tablename__ = 'foo'
886
discriminator = Column('type', String(50))
887
__mapper_args__ = dict(polymorphic_on=discriminator)
888
id = Column(Integer, primary_key=True)
890
class Specific(BaseType, ColumnMixin):
892
__mapper_args__ = dict(polymorphic_identity='specific')
894
eq_(BaseType.__table__.c.keys(), ['type', 'id', 'timestamp'])
896
def test_table_in_model_and_same_column_in_mixin(self):
900
data = Column(Integer)
902
class Model(Base, ColumnMixin):
904
__table__ = Table('foo', Base.metadata, Column('data',
905
Integer), Column('id', Integer,
908
model_col = Model.__table__.c.data
909
mixin_col = ColumnMixin.data
910
assert model_col is not mixin_col
911
eq_(model_col.name, 'data')
912
assert model_col.type.__class__ is mixin_col.type.__class__
914
def test_table_in_model_and_different_named_column_in_mixin(self):
917
tada = Column(Integer)
921
class Model(Base, ColumnMixin):
923
__table__ = Table('foo', Base.metadata,
924
Column('data',Integer),
925
Column('id', Integer,primary_key=True))
926
foo = relationship("Dest")
928
assert_raises_message(sa.exc.ArgumentError,
929
"Can't add additional column 'tada' when "
930
"specifying __table__", go)
932
def test_table_in_model_and_different_named_alt_key_column_in_mixin(self):
934
# here, the __table__ has a column 'tada'. We disallow
935
# the add of the 'foobar' column, even though it's
939
tada = Column('foobar', Integer)
943
class Model(Base, ColumnMixin):
945
__table__ = Table('foo', Base.metadata,
946
Column('data',Integer),
947
Column('tada', Integer),
948
Column('id', Integer,primary_key=True))
949
foo = relationship("Dest")
951
assert_raises_message(sa.exc.ArgumentError,
952
"Can't add additional column 'foobar' when "
953
"specifying __table__", go)
955
def test_table_in_model_overrides_different_typed_column_in_mixin(self):
959
data = Column(String)
961
class Model(Base, ColumnMixin):
963
__table__ = Table('foo', Base.metadata, Column('data',
964
Integer), Column('id', Integer,
967
model_col = Model.__table__.c.data
968
mixin_col = ColumnMixin.data
969
assert model_col is not mixin_col
970
eq_(model_col.name, 'data')
971
assert model_col.type.__class__ is Integer
973
def test_mixin_column_ordering(self):
977
col1 = Column(Integer)
978
col3 = Column(Integer)
982
col2 = Column(Integer)
983
col4 = Column(Integer)
985
class Model(Base, Foo, Bar):
987
id = Column(Integer, primary_key=True)
988
__tablename__ = 'model'
990
eq_(Model.__table__.c.keys(), ['col1', 'col3', 'col2', 'col4',
993
def test_honor_class_mro_one(self):
994
class HasXMixin(object):
997
return Column(Integer)
999
class Parent(HasXMixin, Base):
1000
__tablename__ = 'parent'
1001
id = Column(Integer, primary_key=True)
1003
class Child(Parent):
1004
__tablename__ = 'child'
1005
id = Column(Integer, ForeignKey('parent.id'), primary_key=True)
1007
assert "x" not in Child.__table__.c
1009
def test_honor_class_mro_two(self):
1010
class HasXMixin(object):
1013
return Column(Integer)
1015
class Parent(HasXMixin, Base):
1016
__tablename__ = 'parent'
1017
id = Column(Integer, primary_key=True)
1023
id = Column(Integer, ForeignKey('parent.id'), primary_key=True)
1025
assert C().x() == 'hi'
1027
def test_arbitrary_attrs_one(self):
1028
class HasMixin(object):
1031
return cls.__name__ + "SOME ATTR"
1033
class Mapped(HasMixin, Base):
1035
id = Column(Integer, primary_key=True)
1037
eq_(Mapped.some_attr, "MappedSOME ATTR")
1038
eq_(Mapped.__dict__['some_attr'], "MappedSOME ATTR")
1040
def test_arbitrary_attrs_two(self):
1041
from sqlalchemy.ext.associationproxy import association_proxy
1043
class FilterA(Base):
1044
__tablename__ = 'filter_a'
1045
id = Column(Integer(), primary_key=True)
1046
parent_id = Column(Integer(),
1047
ForeignKey('type_a.id'))
1048
filter = Column(String())
1049
def __init__(self, filter_, **kw):
1050
self.filter = filter_
1052
class FilterB(Base):
1053
__tablename__ = 'filter_b'
1054
id = Column(Integer(), primary_key=True)
1055
parent_id = Column(Integer(),
1056
ForeignKey('type_b.id'))
1057
filter = Column(String())
1058
def __init__(self, filter_, **kw):
1059
self.filter = filter_
1061
class FilterMixin(object):
1064
return relationship(cls.filter_class,
1065
cascade='all,delete,delete-orphan')
1069
return association_proxy('_filters', 'filter')
1071
class TypeA(Base, FilterMixin):
1072
__tablename__ = 'type_a'
1073
filter_class = FilterA
1074
id = Column(Integer(), primary_key=True)
1076
class TypeB(Base, FilterMixin):
1077
__tablename__ = 'type_b'
1078
filter_class = FilterB
1079
id = Column(Integer(), primary_key=True)
1081
TypeA(filters=[u'foo'])
1082
TypeB(filters=[u'foo'])
1084
class DeclarativeMixinPropertyTest(DeclarativeTestBase):
1086
def test_column_property(self):
1088
class MyMixin(object):
1092
return column_property(Column('prop', String(50)))
1094
class MyModel(Base, MyMixin):
1096
__tablename__ = 'test'
1097
id = Column(Integer, primary_key=True,
1098
test_needs_autoincrement=True)
1100
class MyOtherModel(Base, MyMixin):
1102
__tablename__ = 'othertest'
1103
id = Column(Integer, primary_key=True,
1104
test_needs_autoincrement=True)
1106
assert MyModel.__table__.c.prop is not None
1107
assert MyOtherModel.__table__.c.prop is not None
1108
assert MyModel.__table__.c.prop \
1109
is not MyOtherModel.__table__.c.prop
1110
assert MyModel.prop_hoho.property.columns \
1111
== [MyModel.__table__.c.prop]
1112
assert MyOtherModel.prop_hoho.property.columns \
1113
== [MyOtherModel.__table__.c.prop]
1114
assert MyModel.prop_hoho.property \
1115
is not MyOtherModel.prop_hoho.property
1116
Base.metadata.create_all()
1117
sess = create_session()
1118
m1, m2 = MyModel(prop_hoho='foo'), MyOtherModel(prop_hoho='bar')
1119
sess.add_all([m1, m2])
1121
eq_(sess.query(MyModel).filter(MyModel.prop_hoho == 'foo'
1123
eq_(sess.query(MyOtherModel).filter(MyOtherModel.prop_hoho
1124
== 'bar').one(), m2)
1127
"""test documentation transfer.
1129
the documentation situation with @declared_attr is problematic.
1130
at least see if mapped subclasses get the doc.
1134
class MyMixin(object):
1138
"""this is a document."""
1140
return Column(String(50))
1144
"""this is another document."""
1146
return column_property(Column(String(50)))
1148
class MyModel(Base, MyMixin):
1150
__tablename__ = 'test'
1151
id = Column(Integer, primary_key=True)
1154
eq_(MyModel.type_.__doc__, """this is a document.""")
1155
eq_(MyModel.t2.__doc__, """this is another document.""")
1157
def test_column_in_mapper_args(self):
1159
class MyMixin(object):
1163
return Column(String(50))
1164
__mapper_args__ = {'polymorphic_on': type_}
1166
class MyModel(Base, MyMixin):
1168
__tablename__ = 'test'
1169
id = Column(Integer, primary_key=True)
1172
col = MyModel.__mapper__.polymorphic_on
1173
eq_(col.name, 'type_')
1174
assert col.table is not None
1176
def test_deferred(self):
1178
class MyMixin(object):
1182
return deferred(Column('data', String(50)))
1184
class MyModel(Base, MyMixin):
1186
__tablename__ = 'test'
1187
id = Column(Integer, primary_key=True,
1188
test_needs_autoincrement=True)
1190
Base.metadata.create_all()
1191
sess = create_session()
1192
sess.add_all([MyModel(data='d1'), MyModel(data='d2')])
1195
d1, d2 = sess.query(MyModel).order_by(MyModel.data)
1196
assert 'data' not in d1.__dict__
1197
assert d1.data == 'd1'
1198
assert 'data' in d1.__dict__
1200
def _test_relationship(self, usestring):
1202
class RefTargetMixin(object):
1206
return Column('target_id', ForeignKey('target.id'))
1211
return relationship('Target',
1212
primaryjoin='Target.id==%s.target_id'
1218
return relationship('Target')
1220
class Foo(Base, RefTargetMixin):
1222
__tablename__ = 'foo'
1223
id = Column(Integer, primary_key=True,
1224
test_needs_autoincrement=True)
1226
class Bar(Base, RefTargetMixin):
1228
__tablename__ = 'bar'
1229
id = Column(Integer, primary_key=True,
1230
test_needs_autoincrement=True)
1234
__tablename__ = 'target'
1235
id = Column(Integer, primary_key=True,
1236
test_needs_autoincrement=True)
1238
Base.metadata.create_all()
1239
sess = create_session()
1240
t1, t2 = Target(), Target()
1241
f1, f2, b1 = Foo(target=t1), Foo(target=t2), Bar(target=t1)
1242
sess.add_all([f1, f2, b1])
1244
eq_(sess.query(Foo).filter(Foo.target == t2).one(), f2)
1245
eq_(sess.query(Bar).filter(Bar.target == t2).first(), None)
1249
def test_relationship(self):
1250
self._test_relationship(False)
1252
def test_relationship_primryjoin(self):
1253
self._test_relationship(True)