summaryrefslogtreecommitdiff
path: root/test/ext/test_declarative.py
diff options
context:
space:
mode:
Diffstat (limited to 'test/ext/test_declarative.py')
-rw-r--r--test/ext/test_declarative.py286
1 files changed, 246 insertions, 40 deletions
diff --git a/test/ext/test_declarative.py b/test/ext/test_declarative.py
index 26e1563fe..0202aa69f 100644
--- a/test/ext/test_declarative.py
+++ b/test/ext/test_declarative.py
@@ -14,6 +14,7 @@ from sqlalchemy.orm import relationship, create_session, class_mapper, \
from sqlalchemy.test.testing import eq_
from sqlalchemy.util import classproperty
from test.orm._base import ComparableEntity, MappedTest
+from sqlalchemy.ext.declarative import declared_attr
class DeclarativeTestBase(testing.TestBase, testing.AssertsExecutionResults):
def setup(self):
@@ -369,11 +370,14 @@ class DeclarativeTest(DeclarativeTestBase):
hasattr(User.addresses, 'property')
- # the exeption is preserved
-
- assert_raises_message(sa.exc.InvalidRequestError,
- r"suppressed within a hasattr\(\)",
- compile_mappers)
+ # the exception is preserved. Remains the
+ # same through repeated calls.
+ for i in range(3):
+ assert_raises_message(sa.exc.InvalidRequestError,
+ "^One or more mappers failed to initialize - "
+ "can't proceed with initialization of other "
+ "mappers. Original exception was: When initializing.*",
+ compile_mappers)
def test_custom_base(self):
class MyBase(object):
@@ -690,7 +694,7 @@ class DeclarativeTest(DeclarativeTestBase):
eq_(sess.query(User).all(), [User(name='u1', address_count=2,
addresses=[Address(email='one'), Address(email='two')])])
- def test_useless_classproperty(self):
+ def test_useless_declared_attr(self):
class Address(Base, ComparableEntity):
__tablename__ = 'addresses'
@@ -707,7 +711,7 @@ class DeclarativeTest(DeclarativeTestBase):
name = Column('name', String(50))
addresses = relationship('Address', backref='user')
- @classproperty
+ @declared_attr
def address_count(cls):
# this doesn't really gain us anything. but if
# one is used, lets have it function as expected...
@@ -2194,7 +2198,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
def test_table_name_inherited(self):
class MyMixin:
- @classproperty
+ @declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
@@ -2203,11 +2207,23 @@ class DeclarativeMixinTest(DeclarativeTestBase):
pass
eq_(MyModel.__table__.name, 'mymodel')
+
+ def test_classproperty_still_works(self):
+ class MyMixin(object):
+ @classproperty
+ def __tablename__(cls):
+ return cls.__name__.lower()
+ id = Column(Integer, primary_key=True)
+
+ class MyModel(Base, MyMixin):
+ __tablename__ = 'overridden'
+ eq_(MyModel.__table__.name, 'overridden')
+
def test_table_name_not_inherited(self):
class MyMixin:
- @classproperty
+ @declared_attr
def __tablename__(cls):
return cls.__name__.lower()
id = Column(Integer, primary_key=True)
@@ -2220,12 +2236,12 @@ class DeclarativeMixinTest(DeclarativeTestBase):
def test_table_name_inheritance_order(self):
class MyMixin1:
- @classproperty
+ @declared_attr
def __tablename__(cls):
return cls.__name__.lower() + '1'
class MyMixin2:
- @classproperty
+ @declared_attr
def __tablename__(cls):
return cls.__name__.lower() + '2'
@@ -2237,7 +2253,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
def test_table_name_dependent_on_subclass(self):
class MyHistoryMixin:
- @classproperty
+ @declared_attr
def __tablename__(cls):
return cls.parent_name + '_changelog'
@@ -2261,7 +2277,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
def test_table_args_inherited_descriptor(self):
class MyMixin:
- @classproperty
+ @declared_attr
def __table_args__(cls):
return {'info': cls.__name__}
@@ -2288,6 +2304,100 @@ class DeclarativeMixinTest(DeclarativeTestBase):
assert Specific.__table__ is General.__table__
eq_(General.__table__.kwargs, {'mysql_engine': 'InnoDB'})
+ def test_columns_single_table_inheritance(self):
+ """Test a column on a mixin with an alternate attribute name,
+ mapped to a superclass and single-table inheritance subclass.
+ The superclass table gets the column, the subclass shares
+ the MapperProperty.
+
+ """
+
+ class MyMixin(object):
+ foo = Column('foo', Integer)
+ bar = Column('bar_newname', Integer)
+
+ class General(Base, MyMixin):
+ __tablename__ = 'test'
+ id = Column(Integer, primary_key=True)
+ type_ = Column(String(50))
+ __mapper__args = {'polymorphic_on': type_}
+
+ class Specific(General):
+ __mapper_args__ = {'polymorphic_identity': 'specific'}
+
+ assert General.bar.prop.columns[0] is General.__table__.c.bar_newname
+ assert len(General.bar.prop.columns) == 1
+ assert Specific.bar.prop is General.bar.prop
+
+ def test_columns_joined_table_inheritance(self):
+ """Test a column on a mixin with an alternate attribute name,
+ mapped to a superclass and joined-table inheritance subclass.
+ Both tables get the column, in the case of the subclass the two
+ columns are joined under one MapperProperty.
+
+ """
+
+ class MyMixin(object):
+ foo = Column('foo', Integer)
+ bar = Column('bar_newname', Integer)
+
+ class General(Base, MyMixin):
+ __tablename__ = 'test'
+ id = Column(Integer, primary_key=True)
+ type_ = Column(String(50))
+ __mapper__args = {'polymorphic_on': type_}
+
+ class Specific(General):
+ __tablename__ = 'sub'
+ id = Column(Integer, ForeignKey('test.id'), primary_key=True)
+ __mapper_args__ = {'polymorphic_identity': 'specific'}
+
+ assert General.bar.prop.columns[0] is General.__table__.c.bar_newname
+ assert len(General.bar.prop.columns) == 1
+ assert Specific.bar.prop is not General.bar.prop
+ assert len(Specific.bar.prop.columns) == 2
+ assert Specific.bar.prop.columns[0] is General.__table__.c.bar_newname
+ assert Specific.bar.prop.columns[1] is Specific.__table__.c.bar_newname
+
+ def test_column_join_checks_superclass_type(self):
+ """Test that the logic which joins subclass props to those
+ of the superclass checks that the superclass property is a column.
+
+ """
+ class General(Base):
+ __tablename__ = 'test'
+ id = Column(Integer, primary_key=True)
+ general_id = Column(Integer, ForeignKey('test.id'))
+ type_ = relationship("General")
+
+ class Specific(General):
+ __tablename__ = 'sub'
+ id = Column(Integer, ForeignKey('test.id'), primary_key=True)
+ type_ = Column('foob', String(50))
+
+ assert isinstance(General.type_.property, sa.orm.RelationshipProperty)
+ assert Specific.type_.property.columns[0] is Specific.__table__.c.foob
+
+ def test_column_join_checks_subclass_type(self):
+ """Test that the logic which joins subclass props to those
+ of the superclass checks that the subclass property is a column.
+
+ """
+ def go():
+ class General(Base):
+ __tablename__ = 'test'
+ id = Column(Integer, primary_key=True)
+ type_ = Column('foob', Integer)
+
+ class Specific(General):
+ __tablename__ = 'sub'
+ id = Column(Integer, ForeignKey('test.id'), primary_key=True)
+ specific_id = Column(Integer, ForeignKey('sub.id'))
+ type_ = relationship("Specific")
+ assert_raises_message(
+ sa.exc.ArgumentError, "column 'foob' conflicts with property", go
+ )
+
def test_table_args_overridden(self):
class MyMixin:
@@ -2300,10 +2410,10 @@ class DeclarativeMixinTest(DeclarativeTestBase):
eq_(MyModel.__table__.kwargs, {'mysql_engine': 'InnoDB'})
- def test_mapper_args_classproperty(self):
+ def test_mapper_args_declared_attr(self):
class ComputedMapperArgs:
- @classproperty
+ @declared_attr
def __mapper_args__(cls):
if cls.__name__ == 'Person':
return {'polymorphic_on': cls.discriminator}
@@ -2323,13 +2433,13 @@ class DeclarativeMixinTest(DeclarativeTestBase):
is Person.__table__.c.type
eq_(class_mapper(Engineer).polymorphic_identity, 'Engineer')
- def test_mapper_args_classproperty_two(self):
+ def test_mapper_args_declared_attr_two(self):
- # same as test_mapper_args_classproperty, but we repeat
+ # same as test_mapper_args_declared_attr, but we repeat
# ComputedMapperArgs on both classes for no apparent reason.
class ComputedMapperArgs:
- @classproperty
+ @declared_attr
def __mapper_args__(cls):
if cls.__name__ == 'Person':
return {'polymorphic_on': cls.discriminator}
@@ -2364,7 +2474,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
__tablename__ = 'test'
- @classproperty
+ @declared_attr
def __table_args__(self):
info = {}
args = dict(info=info)
@@ -2392,7 +2502,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
class MyMixin:
- @classproperty
+ @declared_attr
def __mapper_args__(cls):
# tenuous, but illustrates the problem!
@@ -2454,19 +2564,93 @@ class DeclarativeMixinTest(DeclarativeTestBase):
__tablename__ = 'test'
- @classproperty
- def __mapper_args__(self):
+ @declared_attr
+ def __mapper_args__(cls):
args = {}
args.update(MyMixin1.__mapper_args__)
args.update(MyMixin2.__mapper_args__)
+ if cls.__name__ != 'MyModel':
+ args.pop('polymorphic_on')
+ args['polymorphic_identity'] = cls.__name__
+
return args
id = Column(Integer, primary_key=True)
-
- col = MyModel.__mapper__.polymorphic_on
- eq_(col.name, 'type_')
- assert col.table is not None
+
+ class MySubModel(MyModel):
+ pass
+
+ eq_(
+ MyModel.__mapper__.polymorphic_on.name,
+ 'type_'
+ )
+ assert MyModel.__mapper__.polymorphic_on.table is not None
eq_(MyModel.__mapper__.always_refresh, True)
+ eq_(MySubModel.__mapper__.always_refresh, True)
+ eq_(MySubModel.__mapper__.polymorphic_identity, 'MySubModel')
+
+ def test_mapper_args_property(self):
+ class MyModel(Base):
+
+ @declared_attr
+ def __tablename__(cls):
+ return cls.__name__.lower()
+
+ @declared_attr
+ def __table_args__(cls):
+ return {'mysql_engine':'InnoDB'}
+
+ @declared_attr
+ def __mapper_args__(cls):
+ args = {}
+ args['polymorphic_identity'] = cls.__name__
+ return args
+ id = Column(Integer, primary_key=True)
+
+ class MySubModel(MyModel):
+ id = Column(Integer, ForeignKey('mymodel.id'), primary_key=True)
+ class MySubModel2(MyModel):
+ __tablename__ = 'sometable'
+ id = Column(Integer, ForeignKey('mymodel.id'), primary_key=True)
+
+ eq_(MyModel.__mapper__.polymorphic_identity, 'MyModel')
+ eq_(MySubModel.__mapper__.polymorphic_identity, 'MySubModel')
+ eq_(MyModel.__table__.kwargs['mysql_engine'], 'InnoDB')
+ eq_(MySubModel.__table__.kwargs['mysql_engine'], 'InnoDB')
+ eq_(MySubModel2.__table__.kwargs['mysql_engine'], 'InnoDB')
+ eq_(MyModel.__table__.name, 'mymodel')
+ eq_(MySubModel.__table__.name, 'mysubmodel')
+
+ def test_mapper_args_custom_base(self):
+ """test the @declared_attr approach from a custom base."""
+
+ class Base(object):
+ @declared_attr
+ def __tablename__(cls):
+ return cls.__name__.lower()
+
+ @declared_attr
+ def __table_args__(cls):
+ return {'mysql_engine':'InnoDB'}
+
+ @declared_attr
+ def id(self):
+ return Column(Integer, primary_key=True)
+
+ Base = decl.declarative_base(cls=Base)
+
+ class MyClass(Base):
+ pass
+
+ class MyOtherClass(Base):
+ pass
+
+ eq_(MyClass.__table__.kwargs['mysql_engine'], 'InnoDB')
+ eq_(MyClass.__table__.name, 'myclass')
+ eq_(MyOtherClass.__table__.name, 'myotherclass')
+ assert MyClass.__table__.c.id.table is MyClass.__table__
+ assert MyOtherClass.__table__.c.id.table is MyOtherClass.__table__
+
def test_single_table_no_propagation(self):
class IdColumn:
@@ -2494,7 +2678,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
class CommonMixin:
- @classproperty
+ @declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {'mysql_engine': 'InnoDB'}
@@ -2524,7 +2708,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
class CommonMixin:
- @classproperty
+ @declared_attr
def __tablename__(cls):
return cls.__name__.lower()
__table_args__ = {'mysql_engine': 'InnoDB'}
@@ -2561,7 +2745,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
class NoJoinedTableNameMixin:
- @classproperty
+ @declared_attr
def __tablename__(cls):
if decl.has_inherited_table(cls):
return None
@@ -2589,7 +2773,7 @@ class DeclarativeMixinTest(DeclarativeTestBase):
class TableNameMixin:
- @classproperty
+ @declared_attr
def __tablename__(cls):
if decl.has_inherited_table(cls) and TableNameMixin \
not in cls.__bases__:
@@ -2656,7 +2840,6 @@ class DeclarativeMixinTest(DeclarativeTestBase):
class ColumnMixin:
tada = Column(Integer)
-
def go():
class Model(Base, ColumnMixin):
@@ -2670,6 +2853,29 @@ class DeclarativeMixinTest(DeclarativeTestBase):
"Can't add additional column 'tada' when "
"specifying __table__", go)
+ def test_table_in_model_and_different_named_alt_key_column_in_mixin(self):
+
+ # here, the __table__ has a column 'tada'. We disallow
+ # the add of the 'foobar' column, even though it's
+ # keyed to 'tada'.
+
+ class ColumnMixin:
+ tada = Column('foobar', Integer)
+
+ def go():
+
+ class Model(Base, ColumnMixin):
+
+ __table__ = Table('foo', Base.metadata,
+ Column('data',Integer),
+ Column('tada', Integer),
+ Column('id', Integer,primary_key=True))
+ foo = relationship("Dest")
+
+ assert_raises_message(sa.exc.ArgumentError,
+ "Can't add additional column 'foobar' when "
+ "specifying __table__", go)
+
def test_table_in_model_overrides_different_typed_column_in_mixin(self):
class ColumnMixin:
@@ -2714,7 +2920,7 @@ class DeclarativeMixinPropertyTest(DeclarativeTestBase):
class MyMixin(object):
- @classproperty
+ @declared_attr
def prop_hoho(cls):
return column_property(Column('prop', String(50)))
@@ -2753,20 +2959,20 @@ class DeclarativeMixinPropertyTest(DeclarativeTestBase):
def test_doc(self):
"""test documentation transfer.
- the documentation situation with @classproperty is problematic.
+ the documentation situation with @declared_attr is problematic.
at least see if mapped subclasses get the doc.
"""
class MyMixin(object):
- @classproperty
+ @declared_attr
def type_(cls):
"""this is a document."""
return Column(String(50))
- @classproperty
+ @declared_attr
def t2(cls):
"""this is another document."""
@@ -2785,7 +2991,7 @@ class DeclarativeMixinPropertyTest(DeclarativeTestBase):
class MyMixin(object):
- @classproperty
+ @declared_attr
def type_(cls):
return Column(String(50))
__mapper_args__ = {'polymorphic_on': type_}
@@ -2804,7 +3010,7 @@ class DeclarativeMixinPropertyTest(DeclarativeTestBase):
class MyMixin(object):
- @classproperty
+ @declared_attr
def data(cls):
return deferred(Column('data', String(50)))
@@ -2828,19 +3034,19 @@ class DeclarativeMixinPropertyTest(DeclarativeTestBase):
class RefTargetMixin(object):
- @classproperty
+ @declared_attr
def target_id(cls):
return Column('target_id', ForeignKey('target.id'))
if usestring:
- @classproperty
+ @declared_attr
def target(cls):
return relationship('Target',
primaryjoin='Target.id==%s.target_id'
% cls.__name__)
else:
- @classproperty
+ @declared_attr
def target(cls):
return relationship('Target')