From: Mike Bayer Date: Tue, 10 Mar 2015 21:21:46 +0000 (-0400) Subject: - Fixed bug where using an ``__abstract__`` mixin in the middle X-Git-Tag: rel_1_0_0b1~17 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=95e53d0b6072510c7a687e3bcc92246d9b3d7181;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - 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 --- diff --git a/doc/build/changelog/changelog_10.rst b/doc/build/changelog/changelog_10.rst index fb75d4a819..b54a43aaea 100644 --- a/doc/build/changelog/changelog_10.rst +++ b/doc/build/changelog/changelog_10.rst @@ -23,6 +23,15 @@ 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 diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index e35ae085a1..7d4020b240 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 6dabfcd22f..5cefe8d476 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__