]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Guard against mapped classes in map_imperatively.
authorFederico Caselli <cfederico87@gmail.com>
Wed, 31 May 2023 20:36:00 +0000 (22:36 +0200)
committerFederico Caselli <cfederico87@gmail.com>
Tue, 6 Jun 2023 19:10:57 +0000 (21:10 +0200)
Added a check to prevent invocation of
meth:`_orm.registry.map_imperatively` using a mapped class as
paramref:`_orm.registry.map_imperatively.local_table`.

Fixes: #9869
Change-Id: Id404f75bf0af69eea942ad71662593bdafbd92ce

doc/build/changelog/unreleased_20/9869.rst [new file with mode: 0644]
lib/sqlalchemy/orm/mapper.py
lib/sqlalchemy/sql/coercions.py
test/orm/declarative/test_basic.py

diff --git a/doc/build/changelog/unreleased_20/9869.rst b/doc/build/changelog/unreleased_20/9869.rst
new file mode 100644 (file)
index 0000000..8dd258a
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: bug, orm
+    :tickets: 9869
+
+    Added a check to prevent invocation of
+    :meth:`_orm.registry.map_imperatively` using a mapped class as
+    :paramref:`_orm.registry.map_imperatively.local_table`.
index 7bc5de449d0445e1938913f904c2dbb57395621f..a54c9e9707ca7c0aaecf15b5f28d174adaff5c5b 100644 (file)
@@ -754,7 +754,10 @@ class Mapper(
 
         if local_table is not None:
             self.local_table = coercions.expect(
-                roles.StrictFromClauseRole, local_table
+                roles.StrictFromClauseRole,
+                local_table,
+                disable_inspection=True,
+                argname="local_table",
             )
         elif self.inherits:
             # note this is a new flow as of 2.0 so that
index ead564ea30219f15ae7ac9ba439756d842012817..c4d340713baa42caeaeb39686a0fa45bac4ea575 100644 (file)
@@ -335,6 +335,7 @@ def expect(
     apply_propagate_attrs: Optional[ClauseElement] = None,
     argname: Optional[str] = None,
     post_inspect: bool = False,
+    disable_inspection: bool = False,
     **kw: Any,
 ) -> Any:
     if (
@@ -398,7 +399,7 @@ def expect(
                         break
 
             if not is_clause_element:
-                if impl._use_inspection:
+                if impl._use_inspection and not disable_inspection:
                     insp = inspection.inspect(element, raiseerr=False)
                     if insp is not None:
                         if post_inspect:
index a76e375b22b4f39e350badc2f8b68e02b15d024d..5ba10345433de884f9990c9872c3b4b0eeca5d04 100644 (file)
@@ -3227,3 +3227,92 @@ class NamedAttrOrderingTest(fixtures.TestBase):
 
         stmt = select(new_cls)
         eq_(stmt.selected_columns.keys(), col_names_only)
+
+    @testing.variation(
+        "mapping_style",
+        [
+            "decl_base_fn",
+            "decl_base_base",
+            "decl_base_no_meta",
+            "map_declaratively",
+            "decorator",
+            "mapped_as_dataclass",
+        ],
+    )
+    def test_no_imperative_with_declarative_table(self, mapping_style):
+        if mapping_style.decl_base_fn:
+            Base = declarative_base()
+
+            class DecModel(Base):
+                __tablename__ = "foo"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[str]
+
+        elif mapping_style.decl_base_base:
+
+            class Base(DeclarativeBase):
+                pass
+
+            class DecModel(Base):
+                __tablename__ = "foo"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[str]
+
+        elif mapping_style.decl_base_no_meta:
+
+            class Base(DeclarativeBaseNoMeta):
+                pass
+
+            class DecModel(Base):
+                __tablename__ = "foo"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[str]
+
+        elif mapping_style.decorator:
+            r = registry()
+
+            @r.mapped
+            class DecModel:
+                __tablename__ = "foo"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[str]
+
+        elif mapping_style.map_declaratively:
+
+            class DecModel:
+                __tablename__ = "foo"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[str]
+
+            registry().map_declaratively(DecModel)
+        elif mapping_style.decorator:
+            r = registry()
+
+            @r.mapped
+            class DecModel:
+                __tablename__ = "foo"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[str]
+
+        elif mapping_style.mapped_as_dataclass:
+            r = registry()
+
+            @r.mapped_as_dataclass
+            class DecModel:
+                __tablename__ = "foo"
+                id: Mapped[int] = mapped_column(primary_key=True)
+                data: Mapped[str]
+
+        else:
+            assert False
+
+        class ImpModel:
+            id: int
+            data: str
+
+        with expect_raises_message(
+            exc.ArgumentError,
+            "FROM expression, such as a Table or alias.. object expected "
+            "for argument 'local_table'; got",
+        ):
+            registry().map_imperatively(ImpModel, DecModel)