declarative reflective base needs to handle "secondary"

Issue #2865 resolved
Mike Bayer repo owner created an issue
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base, DeferredReflection


Base = declarative_base(cls=DeferredReflection)

class A(Base):
    __tablename__ = 'a'
    bs = relationship("B", secondary="atob")

class B(Base):
    __tablename__ = 'b'


e = create_engine("sqlite://", echo=True)
e.execute("""
    create table a(id integer primary key)
""")
e.execute("""
    create table b(id integer primary key)
""")
e.execute("""
    create table atob(
        a_id integer references a(id),
        b_id integer references b(id),
        primary key (a_id, b_id)
    )
""")

Base.prepare(e)

# if this isn't done, failure
#Table('atob', Base.metadata, autoload=True, autoload_with=e)

print A.bs.__clause_element__()

patch:

diff --git a/lib/sqlalchemy/ext/declarative/api.py b/lib/sqlalchemy/ext/declarative/api.py
index b309a78..5a8009f 100644
--- a/lib/sqlalchemy/ext/declarative/api.py
+++ b/lib/sqlalchemy/ext/declarative/api.py
@@ -9,9 +9,10 @@
 from ...schema import Table, MetaData
 from ...orm import synonym as _orm_synonym, mapper,\
                                 comparable_property,\
-                                interfaces
+                                interfaces, properties
 from ...orm.util import polymorphic_union
 from ...orm.base import _mapper_or_none
+from ...util import compat
 from ... import exc
 import weakref

@@ -470,6 +471,18 @@ class DeferredReflection(object):
         for thingy in to_map:
             cls._sa_decl_prepare(thingy.local_table, engine)
             thingy.map()
+            for rel in thingy.cls.__mapper__._props.values():
+                if isinstance(rel, properties.RelationshipProperty) and \
+                    rel.secondary is not None:
+                    if isinstance(rel.secondary, Table):
+                        cls._sa_decl_prepare(rel.secondary, engine)
+                    elif callable(rel.secondary) and \
+                        hasattr(rel.secondary, '_declarative_arg'):
+                        cls._sa_decl_prepare(
+                            Table(
+                                rel.secondary._declarative_arg,
+                                cls.metadata,
+                            ), engine)

     @classmethod
     def _sa_decl_prepare(cls, local_table, engine):
diff --git a/lib/sqlalchemy/ext/declarative/clsregistry.py b/lib/sqlalchemy/ext/declarative/clsregistry.py
index a669e37..f11c7a1 100644
--- a/lib/sqlalchemy/ext/declarative/clsregistry.py
+++ b/lib/sqlalchemy/ext/declarative/clsregistry.py
@@ -225,47 +225,56 @@ def _determine_container(key, value):
     return _GetColumns(value)


-def _resolver(cls, prop):
-    def resolve_arg(arg):
-        import sqlalchemy
-        from sqlalchemy.orm import foreign, remote
-
-        fallback = sqlalchemy.__dict__.copy()
-        fallback.update({'foreign': foreign, 'remote': remote})
-
-        def access_cls(key):
-            if key in cls._decl_class_registry:
-                return _determine_container(key, cls._decl_class_registry[key](key))
-            elif key in cls.metadata.tables:
-                return cls.metadata.tables[key](key)
-            elif key in cls.metadata._schemas:
-                return _GetTable(key, cls.metadata)
-            elif '_sa_module_registry' in cls._decl_class_registry and \
-                key in cls._decl_class_registry['_sa_module_registry']('_sa_module_registry'):
-                registry = cls._decl_class_registry['_sa_module_registry']('_sa_module_registry')
-                return registry.resolve_attr(key)
+class _class_resolver(object):
+    def __init__(self, cls, prop, fallback, arg):
+        self.cls = cls
+        self.prop = prop
+        self.arg = self._declarative_arg = arg
+        self.fallback = fallback
+        self._dict = util.PopulateDict(self._access_cls)
+
+    def _access_cls(self, key):
+        cls = self.cls
+        if key in cls._decl_class_registry:
+            return _determine_container(key, cls._decl_class_registry[key](key))
+        elif key in cls.metadata.tables:
+            return cls.metadata.tables[key](key)
+        elif key in cls.metadata._schemas:
+            return _GetTable(key, cls.metadata)
+        elif '_sa_module_registry' in cls._decl_class_registry and \
+            key in cls._decl_class_registry['_sa_module_registry']('_sa_module_registry'):
+            registry = cls._decl_class_registry['_sa_module_registry']('_sa_module_registry')
+            return registry.resolve_attr(key)
+        else:
+            return self.fallback[key](key)
+
+    def __call__(self):
+        try:
+            x = eval(self.arg, globals(), self._dict)
+
+            if isinstance(x, _GetColumns):
+                return x.cls
             else:
-                return fallback[key](key)
+                return x
+        except NameError as n:
+            raise exc.InvalidRequestError(
+                "When initializing mapper %s, expression %r failed to "
+                "locate a name (%r). If this is a class name, consider "
+                "adding this relationship() to the %r class after "
+                "both dependent classes have been defined." %
+                (self.prop.parent, self.arg, n.args[0](0), self.cls)
+            )

-        d = util.PopulateDict(access_cls)

-        def return_cls():
-            try:
-                x = eval(arg, globals(), d)
+def _resolver(cls, prop):
+    import sqlalchemy
+    from sqlalchemy.orm import foreign, remote

-                if isinstance(x, _GetColumns):
-                    return x.cls
-                else:
-                    return x
-            except NameError as n:
-                raise exc.InvalidRequestError(
-                    "When initializing mapper %s, expression %r failed to "
-                    "locate a name (%r). If this is a class name, consider "
-                    "adding this relationship() to the %r class after "
-                    "both dependent classes have been defined." %
-                    (prop.parent, arg, n.args[0](0), cls)
-                )
-        return return_cls
+    fallback = sqlalchemy.__dict__.copy()
+    fallback.update({'foreign': foreign, 'remote': remote})
+
+    def resolve_arg(arg):
+        return _class_resolver(cls, prop, fallback, arg)
     return resolve_arg

Comments (3)

  1. Mike Bayer reporter

    so it might be nice to figure out if _class_resolver() above could have some kind of hook that deferred reflection latches onto. patch coming

  2. Log in to comment