]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
locate automap base in hierarchy directly
authorMike Bayer <mike_mp@zzzcomputing.com>
Fri, 24 Feb 2023 15:58:25 +0000 (10:58 -0500)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 24 Feb 2023 15:58:25 +0000 (10:58 -0500)
Fixed issue in automap where calling ``.prepare()`` from one of the mapped
classes would not use the correct base class when automap detected new
tables, instead using the given class, leading to mappers trying to
configure inheritance. While one should normally call ``.prepare()`` from
the base in any case, it shouldn't misbehave that badly when called from a
subclass.

Fixes: #9367
Change-Id: I705d4d939d45af52bc58a74e65994205ab791634

doc/build/changelog/unreleased_20/9367.rst [new file with mode: 0644]
lib/sqlalchemy/ext/automap.py
test/ext/test_automap.py

diff --git a/doc/build/changelog/unreleased_20/9367.rst b/doc/build/changelog/unreleased_20/9367.rst
new file mode 100644 (file)
index 0000000..9355ffd
--- /dev/null
@@ -0,0 +1,11 @@
+.. change::
+    :tags: bug, ext
+    :tickets: 9367
+
+    Fixed issue in automap where calling ``.prepare()`` from one of the mapped
+    classes would not use the correct base class when automap detected new
+    tables, instead using the given class, leading to mappers trying to
+    configure inheritance. While one should normally call ``.prepare()`` from
+    the base in any case, it shouldn't misbehave that badly when called from a
+    subclass.
+
index 0300152848a1b803e1e5f28f7a81f7a9dee068c8..1861791b7e804108b5e0d9de50a6173d0dbef428 100644 (file)
@@ -1188,6 +1188,14 @@ class AutomapBase:
          .. versionadded:: 1.4
 
         """
+
+        for mr in cls.__mro__:
+            if "_sa_automapbase_bookkeeping" in mr.__dict__:
+                automap_base = cast("Type[AutomapBase]", mr)
+                break
+        else:
+            assert False, "Can't locate automap base in class hierarchy"
+
         glbls = globals()
         if classname_for_table is None:
             classname_for_table = glbls["classname_for_table"]
@@ -1237,7 +1245,7 @@ class AutomapBase:
             ]
             many_to_many = []
 
-            bookkeeping = cls._sa_automapbase_bookkeeping
+            bookkeeping = automap_base._sa_automapbase_bookkeeping
             metadata_tables = cls.metadata.tables
 
             for table_key in set(metadata_tables).difference(
@@ -1278,7 +1286,7 @@ class AutomapBase:
 
                     mapped_cls = type(
                         newname,
-                        (cls,),
+                        (automap_base,),
                         clsdict,
                     )
                     map_config = _DeferredMapperConfig.config_for_cls(
@@ -1309,7 +1317,7 @@ class AutomapBase:
 
             for map_config in table_to_map_config.values():
                 _relationships_for_fks(
-                    cls,
+                    automap_base,
                     map_config,
                     table_to_map_config,
                     collection_class,
@@ -1320,7 +1328,7 @@ class AutomapBase:
 
             for lcl_m2m, rem_m2m, m2m_const, table in many_to_many:
                 _m2m_relationship(
-                    cls,
+                    automap_base,
                     lcl_m2m,
                     rem_m2m,
                     m2m_const,
@@ -1332,7 +1340,9 @@ class AutomapBase:
                     generate_relationship,
                 )
 
-            for map_config in _DeferredMapperConfig.classes_for_base(cls):
+            for map_config in _DeferredMapperConfig.classes_for_base(
+                automap_base
+            ):
                 map_config.map()
 
     _sa_decl_prepare = True
index 6c4aa02c07ee4637df7a2dc15a37bdf1ec643dc5..dca0bb063b527ab17364a37519f9b7b6ca13083d 100644 (file)
@@ -65,6 +65,18 @@ class AutomapTest(fixtures.MappedTest):
         u1 = User(name="u1", addresses_collection={a1})
         assert a1.user is u1
 
+    def test_prepare_from_subclass(self):
+        """test #9367"""
+        Base = automap_base()
+
+        class User(Base):
+            __tablename__ = "users"
+
+        User.prepare(testing.db)
+
+        assert not hasattr(Base.classes, "users")
+        assert hasattr(Base.classes, "addresses")
+
     def test_prepare_w_only(self):
         Base = automap_base()