diff options
author | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-02-24 15:29:30 -0500 |
---|---|---|
committer | Mike Bayer <mike_mp@zzzcomputing.com> | 2015-02-24 15:29:30 -0500 |
commit | 3a56c4f019052c5dcd4e1cb5fd01a5680e6fc80e (patch) | |
tree | 69f60beb51f311400b41ed031fd3f2458d7a5391 | |
parent | 305ea84004fe604f461cd3c9438fbc84e3d790b2 (diff) | |
download | sqlalchemy-3a56c4f019052c5dcd4e1cb5fd01a5680e6fc80e.tar.gz |
- repair issue in declared_attr.cascading such that within a
subclass, the value returned by the descriptor is not available
because the superclass is already mapped with the InstrumentedAttribute,
until the subclass is mapped. We add a setattr() to set up that
attribute so that the __mapper_args__ hook and possibly others
have access to the "cascaded" version of the attribute within
the call.
-rw-r--r-- | lib/sqlalchemy/ext/declarative/base.py | 2 | ||||
-rw-r--r-- | test/ext/declarative/test_mixin.py | 53 |
2 files changed, 55 insertions, 0 deletions
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index 6735abf4c..d19257366 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -202,6 +202,7 @@ class _MapperConfig(object): if not oldclassprop and obj._cascading: dict_[name] = column_copies[obj] = \ ret = obj.__get__(obj, cls) + setattr(cls, name, ret) else: if oldclassprop: util.warn_deprecated( @@ -439,6 +440,7 @@ class _MapperConfig(object): def _prepare_mapper_arguments(self): properties = self.properties + if self.mapper_args_fn: mapper_args = self.mapper_args_fn() else: diff --git a/test/ext/declarative/test_mixin.py b/test/ext/declarative/test_mixin.py index db86927a1..6dabfcd22 100644 --- a/test/ext/declarative/test_mixin.py +++ b/test/ext/declarative/test_mixin.py @@ -1432,6 +1432,59 @@ class DeclaredAttrTest(DeclarativeTestBase, testing.AssertsCompiledSQL): eq_(counter.mock_calls, [mock.call(A), mock.call(B)]) + def test_col_prop_attrs_associated_w_class_for_mapper_args(self): + from sqlalchemy import Column + import collections + + asserted = collections.defaultdict(set) + + class Mixin(object): + @declared_attr.cascading + def my_attr(cls): + if decl.has_inherited_table(cls): + id = Column(ForeignKey('a.my_attr'), primary_key=True) + asserted['b'].add(id) + else: + id = Column(Integer, primary_key=True) + asserted['a'].add(id) + return id + + class A(Base, Mixin): + __tablename__ = 'a' + + @declared_attr + def __mapper_args__(cls): + asserted['a'].add(cls.my_attr) + return {} + + # here: + # 1. A is mapped. so A.my_attr is now the InstrumentedAttribute. + # 2. B wants to call my_attr also. Due to .cascading, it has been + # invoked specific to B, and is present in the dict_ that will + # be used when we map the class. But except for the + # special setattr() we do in _scan_attributes() in this case, would + # otherwise not been set on the class as anything from this call; + # the usual mechanics of calling it from the descriptor also do not + # work because A is fully mapped and because A set it up, is currently + # that non-expected InstrumentedAttribute and replaces the + # descriptor from being invoked. + + class B(A): + __tablename__ = 'b' + + @declared_attr + def __mapper_args__(cls): + asserted['b'].add(cls.my_attr) + return {} + + eq_( + asserted, + { + 'a': set([A.my_attr.property.columns[0]]), + 'b': set([B.my_attr.property.columns[0]]) + } + ) + def test_column_pre_map(self): counter = mock.Mock() |