summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2015-03-10 17:21:46 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2015-03-10 17:21:46 -0400
commit95e53d0b6072510c7a687e3bcc92246d9b3d7181 (patch)
tree6e82dbc97b360f670d7ea81b5000e78637719b4a
parenta4a826021fb6d77fadbdac0071616d7e5486e4d1 (diff)
downloadsqlalchemy-95e53d0b6072510c7a687e3bcc92246d9b3d7181.tar.gz
- 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. fixes #3219 fixes #3240
-rw-r--r--doc/build/changelog/changelog_10.rst9
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py21
-rw-r--r--test/ext/declarative/test_mixin.py41
3 files changed, 71 insertions, 0 deletions
diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst
index fb75d4a81..b54a43aae 100644
--- a/doc/build/changelog/changelog_10.rst
+++ b/doc/build/changelog/changelog_10.rst
@@ -24,6 +24,15 @@
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
diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py
index e35ae085a..7d4020b24 100644
--- a/lib/sqlalchemy/ext/declarative/base.py
+++ b/lib/sqlalchemy/ext/declarative/base.py
@@ -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'):
diff --git a/test/ext/declarative/test_mixin.py b/test/ext/declarative/test_mixin.py
index 6dabfcd22..5cefe8d47 100644
--- a/test/ext/declarative/test_mixin.py
+++ b/test/ext/declarative/test_mixin.py
@@ -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__