From: Mike Bayer Date: Sun, 9 Feb 2014 02:24:13 +0000 (-0500) Subject: - Fixed bug where the :class:`.AutomapBase` class of the X-Git-Tag: rel_0_9_3~49 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ee88e648f7a56ef76426957a5344639ab9954d9d;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git - Fixed bug where the :class:`.AutomapBase` class of the new automap extension would fail if classes were pre-arranged in single or potentially joined inheritance patterns. The repaired joined inheritance issue could also potentially apply when using :class:`.DeferredReflection` as well. --- diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst index 87f1b24949..1db54d85cc 100644 --- a/doc/build/changelog/changelog_09.rst +++ b/doc/build/changelog/changelog_09.rst @@ -14,6 +14,16 @@ .. changelog:: :version: 0.9.3 + .. change:: + :tags: bug, ext + + Fixed bug where the :class:`.AutomapBase` class of the + new automap extension would fail if classes + were pre-arranged in single or potentially joined inheritance patterns. + The repaired joined inheritance issue could also potentially apply when + using :class:`.DeferredReflection` as well. + + .. change:: :tags: bug, sql :pullreq: github:67 diff --git a/lib/sqlalchemy/ext/automap.py b/lib/sqlalchemy/ext/automap.py index 7a1512f6a0..b98ac17073 100644 --- a/lib/sqlalchemy/ext/automap.py +++ b/lib/sqlalchemy/ext/automap.py @@ -598,7 +598,8 @@ class AutomapBase(object): table_to_map_config = dict( (m.local_table, m) - for m in _DeferredMapperConfig.classes_for_base(cls) + for m in _DeferredMapperConfig. + classes_for_base(cls, sort=False) ) many_to_many = [] @@ -635,7 +636,8 @@ class AutomapBase(object): name_for_scalar_relationship, name_for_collection_relationship, generate_relationship) - for map_config in table_to_map_config.values(): + + for map_config in _DeferredMapperConfig.classes_for_base(cls): map_config.map() @@ -718,6 +720,8 @@ def _relationships_for_fks(automap_base, map_config, table_to_map_config, local_table = map_config.local_table local_cls = map_config.cls + if local_table is None: + return for constraint in local_table.constraints: if isinstance(constraint, ForeignKeyConstraint): fks = constraint.elements diff --git a/lib/sqlalchemy/ext/declarative/base.py b/lib/sqlalchemy/ext/declarative/base.py index a764f126b4..4fda9c734e 100644 --- a/lib/sqlalchemy/ext/declarative/base.py +++ b/lib/sqlalchemy/ext/declarative/base.py @@ -12,6 +12,7 @@ from ...orm.properties import ColumnProperty, CompositeProperty from ...orm.attributes import QueryableAttribute from ...orm.base import _is_mapped_class from ... import util, exc +from ...util import topological from ...sql import expression from ... import event from . import clsregistry @@ -432,9 +433,30 @@ class _DeferredMapperConfig(_MapperConfig): @classmethod - def classes_for_base(cls, base_cls): - return [m for m in cls._configs.values() - if issubclass(m.cls, base_cls)] + def classes_for_base(cls, base_cls, sort=True): + classes_for_base = [m for m in cls._configs.values() + if issubclass(m.cls, base_cls)] + if not sort: + return classes_for_base + + all_m_by_cls = dict( + (m.cls, m) + for m in classes_for_base + ) + + tuples = [] + for m_cls in all_m_by_cls: + tuples.extend( + (all_m_by_cls[base_cls], all_m_by_cls[m_cls]) + for base_cls in m_cls.__bases__ + if base_cls in all_m_by_cls + ) + return list( + topological.sort( + tuples, + classes_for_base + ) + ) def map(self): self._configs.pop(self._cls, None) diff --git a/test/ext/test_automap.py b/test/ext/test_automap.py index 9db85879db..3a2d4d31c7 100644 --- a/test/ext/test_automap.py +++ b/test/ext/test_automap.py @@ -4,6 +4,8 @@ from sqlalchemy.ext.automap import automap_base from sqlalchemy.orm import relationship, interfaces, backref from sqlalchemy.ext.automap import generate_relationship from sqlalchemy.testing.mock import Mock, call +from sqlalchemy import Column, String, Table, Integer, ForeignKey +from sqlalchemy import testing class AutomapTest(fixtures.MappedTest): @classmethod @@ -144,3 +146,66 @@ class AutomapTest(fixtures.MappedTest): (Base, interfaces.MANYTOONE, "users"), (Base, interfaces.ONETOMANY, "addresses_collection"), ]) + + +class AutomapInhTest(fixtures.MappedTest): + @classmethod + def define_tables(cls, metadata): + Table('single', metadata, + Column('id', Integer, primary_key=True), + Column('type', String(10)) + ) + + Table('joined_base', metadata, + Column('id', Integer, primary_key=True), + Column('type', String(10)) + ) + + Table('joined_inh', metadata, + Column('id', Integer, ForeignKey('joined_base.id'), primary_key=True), + ) + + FixtureTest.define_tables(metadata) + + def test_single_inheritance_reflect(self): + Base = automap_base() + + class Single(Base): + __tablename__ = 'single' + + type = Column(String) + + __mapper_args__ = {"polymorphic_identity": "u0", + "polymorphic_on": type} + + class SubUser1(Single): + __mapper_args__ = {"polymorphic_identity": "u1"} + + class SubUser2(Single): + __mapper_args__ = {"polymorphic_identity": "u2"} + + Base.prepare(engine=testing.db, reflect=True) + + assert SubUser2.__mapper__.inherits is Single.__mapper__ + + def test_joined_inheritance_reflect(self): + Base = automap_base() + + class Joined(Base): + __tablename__ = 'joined_base' + + type = Column(String) + + __mapper_args__ = {"polymorphic_identity": "u0", + "polymorphic_on": type} + + class SubJoined(Joined): + __tablename__ = 'joined_inh' + __mapper_args__ = {"polymorphic_identity": "u1"} + + + Base.prepare(engine=testing.db, reflect=True) + + assert SubJoined.__mapper__.inherits is Joined.__mapper__ + +