]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
- Fixed bug where using an ``__abstract__`` mixin in the middle
authorMike Bayer <mike_mp@zzzcomputing.com>
Tue, 10 Mar 2015 21:21:46 +0000 (17:21 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Tue, 10 Mar 2015 21:21:46 +0000 (17:21 -0400)
of a declarative inheritance hierarchy would prevent attributes
and configuration being correctly propagated from the base class
to the inheriting class.
fixes #3219 fixes #3240

doc/build/changelog/changelog_10.rst
lib/sqlalchemy/ext/declarative/base.py
test/ext/declarative/test_mixin.py

index fb75d4a8196ec9e5bc1e036f9ed21095511fb4b6..b54a43aaea8e82368c920578e7d5e3059b7f0e0f 100644 (file)
     series as well.  For changes that are specific to 1.0 with an emphasis
     on compatibility concerns, see :doc:`/changelog/migration_10`.
 
+    .. change::
+        :tags: bug, ext
+        :tickets: 3219, 3240
+
+        Fixed bug where using an ``__abstract__`` mixin in the middle
+        of a declarative inheritance hierarchy would prevent attributes
+        and configuration being correctly propagated from the base class
+        to the inheriting class.
+
     .. change::
         :tags: feature, sql
         :tickets: 918
index e35ae085a185c6e30bd41dcce8a38497b3be8ddb..7d4020b240bd514a54b91a09fc548103f67b2cef 100644 (file)
@@ -35,6 +35,21 @@ def _declared_mapping_info(cls):
         return None
 
 
+def _resolve_for_abstract(cls):
+    if cls is object:
+        return None
+
+    if _get_immediate_cls_attr(cls, '__abstract__'):
+        for sup in cls.__bases__:
+            sup = _resolve_for_abstract(sup)
+            if sup is not None:
+                return sup
+        else:
+            return None
+    else:
+        return cls
+
+
 def _get_immediate_cls_attr(cls, attrname):
     """return an attribute of the class that is either present directly
     on the class, e.g. not on a superclass, or is from a superclass but
@@ -46,6 +61,9 @@ def _get_immediate_cls_attr(cls, attrname):
     inherit from.
 
     """
+    if not issubclass(cls, object):
+        return None
+
     for base in cls.__mro__:
         _is_declarative_inherits = hasattr(base, '_decl_class_registry')
         if attrname in base.__dict__:
@@ -389,6 +407,9 @@ class _MapperConfig(object):
         table_args = self.table_args
         declared_columns = self.declared_columns
         for c in cls.__bases__:
+            c = _resolve_for_abstract(c)
+            if c is None:
+                continue
             if _declared_mapping_info(c) is not None and \
                     not _get_immediate_cls_attr(
                         c, '_sa_decl_prepare_nocascade'):
index 6dabfcd22fb5a32d59fe9c53530b829ad28e2c30..5cefe8d4767bc2de93ce2b846ac48ee559494d95 100644 (file)
@@ -1570,3 +1570,44 @@ class AbstractTest(DeclarativeTestBase):
             id = Column(Integer, primary_key=True)
 
         eq_(set(Base.metadata.tables), set(['y', 'z', 'q']))
+
+    def test_middle_abstract_attributes(self):
+        # test for [ticket:3219]
+        class A(Base):
+            __tablename__ = 'a'
+
+            id = Column(Integer, primary_key=True)
+            name = Column(String)
+
+        class B(A):
+            __abstract__ = True
+            data = Column(String)
+
+        class C(B):
+            c_value = Column(String)
+
+        eq_(
+            sa.inspect(C).attrs.keys(), ['id', 'name', 'data', 'c_value']
+        )
+
+    def test_middle_abstract_inherits(self):
+        # test for [ticket:3240]
+
+        class A(Base):
+            __tablename__ = 'a'
+            id = Column(Integer, primary_key=True)
+
+        class AAbs(A):
+            __abstract__ = True
+
+        class B1(A):
+            __tablename__ = 'b1'
+            id = Column(ForeignKey('a.id'), primary_key=True)
+
+        class B2(AAbs):
+            __tablename__ = 'b2'
+            id = Column(ForeignKey('a.id'), primary_key=True)
+
+        assert B1.__mapper__.inherits is A.__mapper__
+
+        assert B2.__mapper__.inherits is A.__mapper__