summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2014-02-08 21:24:13 -0500
committerMike Bayer <mike_mp@zzzcomputing.com>2014-02-08 21:24:13 -0500
commitee88e648f7a56ef76426957a5344639ab9954d9d (patch)
treee64a9406e93ab44b937c04568a752d9083a396d7
parent5c188f6c1ce85eaace27f052bfd1a14f74f6a659 (diff)
downloadsqlalchemy-ee88e648f7a56ef76426957a5344639ab9954d9d.tar.gz
- 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.
-rw-r--r--doc/build/changelog/changelog_09.rst10
-rw-r--r--lib/sqlalchemy/ext/automap.py8
-rw-r--r--lib/sqlalchemy/ext/declarative/base.py28
-rw-r--r--test/ext/test_automap.py65
4 files changed, 106 insertions, 5 deletions
diff --git a/doc/build/changelog/changelog_09.rst b/doc/build/changelog/changelog_09.rst
index 87f1b2494..1db54d85c 100644
--- a/doc/build/changelog/changelog_09.rst
+++ b/doc/build/changelog/changelog_09.rst
@@ -15,6 +15,16 @@
: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 7a1512f6a..b98ac1707 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 a764f126b..4fda9c734 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 9db85879d..3a2d4d31c 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__
+
+