]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- repair issue in declared_attr.cascading such that within a
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 24 Feb 2015 20:29:30 +0000 (15:29 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 24 Feb 2015 20:29:30 +0000 (15:29 -0500)
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.

lib/sqlalchemy/ext/declarative/base.py
test/ext/declarative/test_mixin.py

index 6735abf4c2a65868fa69e8bdf4644eb54b508346..d192573667aa80a32c8d1eb42338dac4ce7465e7 100644 (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:
index db86927a1cf0e7b893228bba491b598905fbe3da..6dabfcd22fb5a32d59fe9c53530b829ad28e2c30 100644 (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()