- 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.
This commit is contained in:
Mike Bayer
2015-02-24 15:29:30 -05:00
parent 305ea84004
commit 3a56c4f019
2 changed files with 55 additions and 0 deletions
+2
View File
@@ -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:
+53
View File
@@ -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()