From: Mike Bayer Date: Fri, 24 Feb 2023 15:58:25 +0000 (-0500) Subject: locate automap base in hierarchy directly X-Git-Tag: rel_2_0_5~28^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=60ddd3725600aa121e2e7b4be49dc9bd244ce0c5;p=thirdparty%2Fsqlalchemy%2Fsqlalchemy.git locate automap base in hierarchy directly 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 --- diff --git a/doc/build/changelog/unreleased_20/9367.rst b/doc/build/changelog/unreleased_20/9367.rst new file mode 100644 index 0000000000..9355ffdead --- /dev/null +++ b/doc/build/changelog/unreleased_20/9367.rst @@ -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. + diff --git a/lib/sqlalchemy/ext/automap.py b/lib/sqlalchemy/ext/automap.py index 0300152848..1861791b7e 100644 --- a/lib/sqlalchemy/ext/automap.py +++ b/lib/sqlalchemy/ext/automap.py @@ -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 diff --git a/test/ext/test_automap.py b/test/ext/test_automap.py index 6c4aa02c07..dca0bb063b 100644 --- a/test/ext/test_automap.py +++ b/test/ext/test_automap.py @@ -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()