From: Mike Bayer Date: Fri, 28 Oct 2011 21:46:28 +0000 (-0400) Subject: - [bug] Fixed bug whereby a subclass of a subclass X-Git-Tag: rel_0_7_4~68 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=5d6376fbd5ca4103a26118a6fffd1e95be0d5161;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - [bug] Fixed bug whereby a subclass of a subclass using concrete inheritance in conjunction with the new ConcreteBase or AbstractConcreteBase would fail to apply the subclasses deeper than one level to the "polymorphic loader" of each base [ticket:2312] - [bug] Fixed bug whereby a subclass of a subclass using the new AbstractConcreteBase would fail to acquire the correct "base_mapper" attribute when the "base" mapper was generated, thereby causing failures later on. [ticket:2312] --- diff --git a/CHANGES b/CHANGES index 7a323fe2c9..a2a3f2eb78 100644 --- a/CHANGES +++ b/CHANGES @@ -38,6 +38,19 @@ CHANGES dictionary is up to date, fixes [ticket:2308]. Thanks to Scott Torborg for the test case here. + - [bug] Fixed bug whereby a subclass of a subclass + using concrete inheritance in conjunction with + the new ConcreteBase or AbstractConcreteBase + would fail to apply the subclasses deeper than + one level to the "polymorphic loader" of each + base [ticket:2312] + + - [bug] Fixed bug whereby a subclass of a subclass + using the new AbstractConcreteBase would fail + to acquire the correct "base_mapper" attribute + when the "base" mapper was generated, thereby + causing failures later on. [ticket:2312] + - sql - [feature] Added accessor to types called "python_type", returns the rudimentary Python type object diff --git a/lib/sqlalchemy/ext/declarative.py b/lib/sqlalchemy/ext/declarative.py index acfc09396a..1f082adf14 100755 --- a/lib/sqlalchemy/ext/declarative.py +++ b/lib/sqlalchemy/ext/declarative.py @@ -1616,10 +1616,8 @@ class ConcreteBase(object): m = cls.__mapper__ if m.with_polymorphic: return - mappers = [ sm for sm in [ - _mapper_or_none(klass) - for klass in cls.__subclasses__() - ] if sm is not None] + [m] + + mappers = list(m.self_and_descendants) pjoin = cls._create_polymorphic_union(mappers) m._set_with_polymorphic(("*",pjoin)) m._set_polymorphic_on(pjoin.c.type) @@ -1661,13 +1659,22 @@ class AbstractConcreteBase(ConcreteBase): def __declare_last__(cls): if hasattr(cls, '__mapper__'): return - table = cls._create_polymorphic_union( - m for m in [ - _mapper_or_none(klass) - for klass in cls.__subclasses__() - ] if m is not None - ) - cls.__mapper__ = m = mapper(cls, table, polymorphic_on=table.c.type) + + # can't rely on 'self_and_descendants' here + # since technically an immediate subclass + # might not be mapped, but a subclass + # may be. + mappers = [] + stack = list(cls.__subclasses__()) + while stack: + klass = stack.pop() + stack.extend(klass.__subclasses__()) + mn = _mapper_or_none(klass) + if mn is not None: + mappers.append(mn) + pjoin = cls._create_polymorphic_union(mappers) + cls.__mapper__ = m = mapper(cls, pjoin, polymorphic_on=pjoin.c.type) + for scls in cls.__subclasses__(): sm = _mapper_or_none(scls) if sm.concrete and cls in scls.__bases__: diff --git a/lib/sqlalchemy/orm/mapper.py b/lib/sqlalchemy/orm/mapper.py index e6ec422b06..3a363e7313 100644 --- a/lib/sqlalchemy/orm/mapper.py +++ b/lib/sqlalchemy/orm/mapper.py @@ -577,7 +577,8 @@ class Mapper(object): if mapper.polymorphic_on is not None: mapper._requires_row_aliasing = True self.batch = self.inherits.batch - self.base_mapper = self.inherits.base_mapper + for mp in self.self_and_descendants: + mp.base_mapper = self.inherits.base_mapper self.inherits._inheriting_mappers.add(self) self.passive_updates = self.inherits.passive_updates self._all_tables = self.inherits._all_tables diff --git a/test/ext/test_declarative.py b/test/ext/test_declarative.py index ec59fb86b0..af7f4e5c44 100644 --- a/test/ext/test_declarative.py +++ b/test/ext/test_declarative.py @@ -2037,25 +2037,27 @@ class DeclarativeInheritanceTest(DeclarativeTestBase): from test.orm.test_events import _RemoveListeners class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase): - def _roundtrip(self, Employee, Manager, Engineer, polymorphic=True): + def _roundtrip(self, Employee, Manager, Engineer, Boss, polymorphic=True): Base.metadata.create_all() sess = create_session() e1 = Engineer(name='dilbert', primary_language='java') e2 = Engineer(name='wally', primary_language='c++') m1 = Manager(name='dogbert', golf_swing='fore!') e3 = Engineer(name='vlad', primary_language='cobol') - sess.add_all([e1, e2, m1, e3]) + b1 = Boss(name="pointy haired") + sess.add_all([e1, e2, m1, e3, b1]) sess.flush() sess.expunge_all() if polymorphic: eq_(sess.query(Employee).order_by(Employee.name).all(), [Engineer(name='dilbert'), Manager(name='dogbert'), - Engineer(name='vlad'), Engineer(name='wally')]) + Boss(name='pointy haired'), Engineer(name='vlad'), Engineer(name='wally')]) else: eq_(sess.query(Engineer).order_by(Engineer.name).all(), [Engineer(name='dilbert'), Engineer(name='vlad'), Engineer(name='wally')]) eq_(sess.query(Manager).all(), [Manager(name='dogbert')]) + eq_(sess.query(Boss).all(), [Boss(name='pointy haired')]) def test_explicit(self): @@ -2064,12 +2066,20 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase): test_needs_autoincrement=True), Column('name' , String(50)), Column('primary_language', String(50))) - managers = Table('managers', Base.metadata, Column('id', - Integer, primary_key=True, - test_needs_autoincrement=True), Column('name', - String(50)), Column('golf_swing', String(50))) - punion = polymorphic_union({'engineer': engineers, 'manager' - : managers}, 'type', 'punion') + managers = Table('managers', Base.metadata, + Column('id',Integer, primary_key=True, test_needs_autoincrement=True), + Column('name', String(50)), + Column('golf_swing', String(50)) + ) + boss = Table('boss', Base.metadata, + Column('id',Integer, primary_key=True, test_needs_autoincrement=True), + Column('name', String(50)), + Column('golf_swing', String(50)) + ) + punion = polymorphic_union({ + 'engineer': engineers, + 'manager' : managers, + 'boss': boss}, 'type', 'punion') class Employee(Base, fixtures.ComparableEntity): @@ -2087,7 +2097,13 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase): __table__ = managers __mapper_args__ = {'polymorphic_identity': 'manager', 'concrete': True} - self._roundtrip(Employee, Manager, Engineer) + + class Boss(Manager): + __table__ = boss + __mapper_args__ = {'polymorphic_identity': 'boss', + 'concrete': True} + + self._roundtrip(Employee, Manager, Engineer, Boss) def test_concrete_inline_non_polymorphic(self): """test the example from the declarative docs.""" @@ -2116,7 +2132,16 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase): test_needs_autoincrement=True) golf_swing = Column(String(50)) name = Column(String(50)) - self._roundtrip(Employee, Manager, Engineer, polymorphic=False) + + class Boss(Manager): + __tablename__ = 'boss' + __mapper_args__ = {'concrete': True} + id = Column(Integer, primary_key=True, + test_needs_autoincrement=True) + golf_swing = Column(String(50)) + name = Column(String(50)) + + self._roundtrip(Employee, Manager, Engineer, Boss, polymorphic=False) def test_abstract_concrete_extension(self): class Employee(AbstractConcreteBase, Base, fixtures.ComparableEntity): @@ -2132,6 +2157,16 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase): 'polymorphic_identity':'manager', 'concrete':True} + class Boss(Manager): + __tablename__ = 'boss' + employee_id = Column(Integer, primary_key=True, + test_needs_autoincrement=True) + name = Column(String(50)) + golf_swing = Column(String(40)) + __mapper_args__ = { + 'polymorphic_identity':'boss', + 'concrete':True} + class Engineer(Employee): __tablename__ = 'engineer' employee_id = Column(Integer, primary_key=True, @@ -2141,7 +2176,7 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase): __mapper_args__ = {'polymorphic_identity':'engineer', 'concrete':True} - self._roundtrip(Employee, Manager, Engineer) + self._roundtrip(Employee, Manager, Engineer, Boss) def test_concrete_extension(self): class Employee(ConcreteBase, Base, fixtures.ComparableEntity): @@ -2162,6 +2197,16 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase): 'polymorphic_identity':'manager', 'concrete':True} + class Boss(Manager): + __tablename__ = 'boss' + employee_id = Column(Integer, primary_key=True, + test_needs_autoincrement=True) + name = Column(String(50)) + golf_swing = Column(String(40)) + __mapper_args__ = { + 'polymorphic_identity':'boss', + 'concrete':True} + class Engineer(Employee): __tablename__ = 'engineer' employee_id = Column(Integer, primary_key=True, @@ -2170,7 +2215,7 @@ class ConcreteInhTest(_RemoveListeners, DeclarativeTestBase): primary_language = Column(String(40)) __mapper_args__ = {'polymorphic_identity':'engineer', 'concrete':True} - self._roundtrip(Employee, Manager, Engineer) + self._roundtrip(Employee, Manager, Engineer, Boss) def _produce_test(inline, stringbased):