]> git.ipfire.org Git - thirdparty/sqlalchemy/sqlalchemy.git/commitdiff
Deprecate DropConstraint.isolate_from_table
authorFederico Caselli <cfederico87@gmail.com>
Wed, 26 Nov 2025 21:13:50 +0000 (22:13 +0100)
committerFederico Caselli <cfederico87@gmail.com>
Mon, 12 Jan 2026 21:12:17 +0000 (22:12 +0100)
The the parameter :paramref:`_schema.DropConstraint.isolate_from_table`
was deprecated since it has no effect on the drop table behavior.
Its default values was also changed to ``False``.

Fixes: #13006
Change-Id: Ibaa68e78da301a4f2a200f8c7abb2dc099fef37a

doc/build/changelog/unreleased_21/13006.rst [new file with mode: 0644]
lib/sqlalchemy/dialects/mssql/provision.py
lib/sqlalchemy/sql/ddl.py
lib/sqlalchemy/testing/util.py
test/dialect/postgresql/test_compiler.py
test/engine/test_ddlevents.py
test/sql/test_constraints.py
test/sql/test_metadata.py

diff --git a/doc/build/changelog/unreleased_21/13006.rst b/doc/build/changelog/unreleased_21/13006.rst
new file mode 100644 (file)
index 0000000..fff4e54
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: schema, usecase
+    :tickets: 13006
+
+    The the parameter :paramref:`_schema.DropConstraint.isolate_from_table`
+    was deprecated since it has no effect on the drop table behavior.
+    Its default values was also changed to ``False``.
index 03bdbca113aa7ccb609c84f153db4f3f72c7ab5b..d8950001a327f209ce79939954056a2b9eb25c44 100644 (file)
@@ -173,7 +173,7 @@ def drop_all_schema_objects_pre_tables(cfg, eng):
                         DropConstraint(
                             ForeignKeyConstraint(
                                 [tb.c.x], [tb.c.y], name=fk["name"]
-                            )
+                            ),
                         )
                     )
 
index 2ede92aa746f68f8da4bb869374e35b3025ad225..caaaa32bf5a3ed9a1c4598f526925d5fecd17d02 100644 (file)
@@ -33,8 +33,10 @@ from . import coercions
 from . import roles
 from . import util as sql_util
 from .base import _generative
+from .base import _NoArg
 from .base import DialectKWArgs
 from .base import Executable
+from .base import NO_ARG
 from .base import SchemaVisitor
 from .elements import ClauseElement
 from .selectable import SelectBase
@@ -175,7 +177,9 @@ class ExecutableDDLElement(roles.DDLRole, Executable, BaseDDLElement):
         event.listen(
             users,
             "after_create",
-            AddConstraint(constraint).execute_if(dialect="postgresql"),
+            AddConstraint(constraint, isolate_from_table=True).execute_if(
+                dialect="postgresql"
+            ),
         )
 
     .. seealso::
@@ -1126,19 +1130,22 @@ class AddConstraint(_CreateBase["Constraint"]):
     __visit_name__ = "add_constraint"
 
     def __init__(
-        self,
-        element: Constraint,
-        *,
-        isolate_from_table: bool = True,
+        self, element: Constraint, *, isolate_from_table: bool = True
     ) -> None:
         """Construct a new :class:`.AddConstraint` construct.
 
         :param element: a :class:`.Constraint` object
 
-        :param isolate_from_table: optional boolean, defaults to True.  Has
-         the effect of the incoming constraint being isolated from being
-         included in a CREATE TABLE sequence when associated with a
-         :class:`.Table`.
+        :param isolate_from_table: optional boolean.  Prevents the target
+         :class:`.Constraint` from being rendered inline in a "CONSTRAINT"
+         clause within a CREATE TABLE statement, in the case that the
+         constraint is associated with a :class:`.Table` which is later
+         created using :meth:`.Table.create` or :meth:`.MetaData.create_all`.
+         This occurs by modifying the state of the :class:`.Constraint`
+         object itself such that the CREATE TABLE DDL process will skip it.
+         Used for the case when a separate `ALTER TABLE...ADD CONSTRAINT`
+         call will be emitted after the `CREATE TABLE` has already occurred.
+         ``True`` by default.
 
          .. versionadded:: 2.0.39 - added
             :paramref:`.AddConstraint.isolate_from_table`, defaulting
@@ -1163,32 +1170,48 @@ class DropConstraint(_DropBase["Constraint"]):
         *,
         cascade: bool = False,
         if_exists: bool = False,
-        isolate_from_table: bool = True,
+        isolate_from_table: bool | _NoArg = NO_ARG,
         **kw: Any,
     ) -> None:
         """Construct a new :class:`.DropConstraint` construct.
 
         :param element: a :class:`.Constraint` object
+
         :param cascade: optional boolean, indicates backend-specific
          "CASCADE CONSTRAINT" directive should be rendered if available
+
         :param if_exists: optional boolean, indicates backend-specific
          "IF EXISTS" directive should be rendered if available
-        :param isolate_from_table: optional boolean, defaults to True.  Has
-         the effect of the incoming constraint being isolated from being
-         included in a CREATE TABLE sequence when associated with a
-         :class:`.Table`.
+
+        :param isolate_from_table: optional boolean. This is a deprecated
+         setting that when ``True``, does the same thing that
+         :paramref:`.AddConstraint.isolate_from_table` does, which is prevents
+         the constraint from being associated with an inline ``CREATE TABLE``
+         statement. It does not have any effect on the DROP process for a
+         table and is an artifact of older SQLAlchemy versions,
+         and will be removed in a future release.
 
          .. versionadded:: 2.0.39 - added
             :paramref:`.DropConstraint.isolate_from_table`, defaulting
             to True.  Previously, the behavior of this parameter was implicitly
             turned on in all cases.
 
+         .. versionchanged:: 2.1 - This parameter has been deprecated and
+            the default value of the flag was changed to ``False``.
+
         """
         self.cascade = cascade
         super().__init__(element, if_exists=if_exists, **kw)
 
-        if isolate_from_table:
-            element._create_rule = self._create_rule_disable
+        if isolate_from_table is not NO_ARG:
+            util.warn_deprecated(
+                "The ``isolate_from_table`` is deprecated and it be removed "
+                "in a future release.",
+                "2.1",
+            )
+
+            if isolate_from_table:
+                element._create_rule = self._create_rule_disable
 
 
 class SetTableComment(_CreateDropBase["Table"]):
@@ -1480,7 +1503,9 @@ class SchemaGenerator(InvokeCreateDDLBase):
             return
 
         with self.with_ddl_events(constraint):
-            AddConstraint(constraint)._invoke_with(self.connection)
+            AddConstraint(constraint, isolate_from_table=True)._invoke_with(
+                self.connection
+            )
 
     def visit_sequence(self, sequence, create_ok=False):
         if not create_ok and not self._can_create_sequence(sequence):
index 21dddfa2ec17bd6526af25be65f1576d5cb0362c..43e3f071bc98b0cbfb69c6d7f386299d652656a0 100644 (file)
@@ -445,7 +445,7 @@ def drop_all_tables(
                     conn.execute(
                         DropConstraint(
                             ForeignKeyConstraint([tb.c.x], [tb.c.y], name=fkc)
-                        )
+                        ),
                     )
 
 
index cb240a74a57a18cfdb702ad24c87c2da5dadc105..40b652383595d136db87be82b78e2de5e2f267e6 100644 (file)
@@ -797,7 +797,7 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
                     tbl.c.data,
                     name="uq_data1",
                     postgresql_nulls_not_distinct=True,
-                )
+                ),
             ),
             (
                 "ALTER TABLE test_tbl ADD CONSTRAINT uq_data1 UNIQUE "
@@ -810,7 +810,7 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
                     tbl.c.data2,
                     name="uq_data2",
                     postgresql_nulls_not_distinct=False,
-                )
+                ),
             ),
             (
                 "ALTER TABLE test_tbl ADD CONSTRAINT uq_data2 UNIQUE "
@@ -819,10 +819,7 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
         ),
         (
             lambda tbl: schema.AddConstraint(
-                schema.UniqueConstraint(
-                    tbl.c.data3,
-                    name="uq_data3",
-                )
+                schema.UniqueConstraint(tbl.c.data3, name="uq_data3"),
             ),
             "ALTER TABLE test_tbl ADD CONSTRAINT uq_data3 UNIQUE (data3)",
         ),
@@ -844,7 +841,7 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
     @testing.combinations(
         (
             lambda tbl: schema.AddConstraint(
-                UniqueConstraint(tbl.c.id, postgresql_include=[tbl.c.value])
+                UniqueConstraint(tbl.c.id, postgresql_include=[tbl.c.value]),
             ),
             "ALTER TABLE foo ADD UNIQUE (id) INCLUDE (value)",
         ),
@@ -852,7 +849,7 @@ class CompileTest(fixtures.TestBase, AssertsCompiledSQL):
             lambda tbl: schema.AddConstraint(
                 PrimaryKeyConstraint(
                     tbl.c.id, postgresql_include=[tbl.c.value, "misc"]
-                )
+                ),
             ),
             "ALTER TABLE foo ADD PRIMARY KEY (id) INCLUDE (value, misc)",
         ),
index 16880f5260c89c90ddf506d6aca0ea4ed52ecb8a..8b1bd4cf78540f3e892198b1543c468419bb275b 100644 (file)
@@ -673,7 +673,8 @@ class DDLExecutionTest(AssertsCompiledSQL, fixtures.TestBase):
         assert "xyzzy" in strings
         assert "fnord" in strings
 
-    def test_conditional_constraint(self):
+    @testing.variation("flag", [True, False, "nope"])
+    def test_conditional_constraint(self, flag):
         metadata, users = self.metadata, self.users
         nonpg_mock = engines.mock_engine(dialect_name="sqlite")
         pg_mock = engines.mock_engine(dialect_name="postgresql")
@@ -682,12 +683,15 @@ class DDLExecutionTest(AssertsCompiledSQL, fixtures.TestBase):
         )
 
         # by placing the constraint in an Add/Drop construct, the
-        # 'inline_ddl' flag is set to False
+        # 'inline_ddl' flag is set to False if isolate_from_table != False
+        kw = {}
+        if not flag.nope:
+            kw["isolate_from_table"] = bool(flag)
 
         event.listen(
             users,
             "after_create",
-            AddConstraint(constraint).execute_if(dialect="postgresql"),
+            AddConstraint(constraint, **kw).execute_if(dialect="postgresql"),
         )
 
         event.listen(
@@ -698,10 +702,10 @@ class DDLExecutionTest(AssertsCompiledSQL, fixtures.TestBase):
 
         metadata.create_all(bind=nonpg_mock)
         strings = " ".join(str(x) for x in nonpg_mock.mock)
-        assert "my_test_constraint" not in strings
+        eq_("my_test_constraint" not in strings, not flag.not_flag)
         metadata.drop_all(bind=nonpg_mock)
         strings = " ".join(str(x) for x in nonpg_mock.mock)
-        assert "my_test_constraint" not in strings
+        eq_("my_test_constraint" not in strings, not flag.not_flag)
         metadata.create_all(bind=pg_mock)
         strings = " ".join(str(x) for x in pg_mock.mock)
         assert "my_test_constraint" in strings
index 70055ace1e5991d015617ba97e76602f92158236..7d89b80f119b1d3ac8e692ff964a44b87b183280 100644 (file)
@@ -1,3 +1,5 @@
+from contextlib import nullcontext
+
 from sqlalchemy import CheckConstraint
 from sqlalchemy import Column
 from sqlalchemy import exc
@@ -22,6 +24,7 @@ from sqlalchemy.testing import AssertsExecutionResults
 from sqlalchemy.testing import engines
 from sqlalchemy.testing import eq_
 from sqlalchemy.testing import fixtures
+from sqlalchemy.testing.assertions import expect_deprecated
 from sqlalchemy.testing.assertions import expect_warnings
 from sqlalchemy.testing.assertsql import AllOf
 from sqlalchemy.testing.assertsql import CompiledSQL
@@ -227,6 +230,50 @@ class ConstraintGenTest(fixtures.TestBase, AssertsExecutionResults):
         )
         self._assert_cyclic_constraint(metadata, auto=False)
 
+    @testing.provide_metadata
+    @testing.skip_if(
+        lambda: not testing.db.dialect.supports_alter, "does not support ALTER"
+    )
+    @testing.combinations(True, False)
+    def test_fk_column_use_alter_drop_constraint(self, isolate):
+        metadata = self.metadata
+
+        Table(
+            "a",
+            metadata,
+            Column("id", Integer, primary_key=True),
+            Column("bid", Integer, ForeignKey("b.id", name="afk")),
+        )
+        b = Table(
+            "b",
+            metadata,
+            Column("id", Integer, primary_key=True),
+            Column("aid", Integer, ForeignKey("a.id", name="bfk")),
+        )
+
+        metadata.create_all(testing.db)
+
+        (fk,) = (c for c in b.constraints if c.name == "bfk")
+
+        with expect_deprecated(
+            "The ``isolate_from_table`` is deprecated and it be removed "
+            "in a future release."
+        ):
+            schema.DropConstraint(fk, isolate_from_table=isolate)
+
+        assertions = [
+            AllOf(
+                CompiledSQL("ALTER TABLE b DROP CONSTRAINT bfk"),
+                CompiledSQL("ALTER TABLE a DROP CONSTRAINT afk"),
+            ),
+            CompiledSQL("DROP TABLE b"),
+            CompiledSQL("DROP TABLE a"),
+        ]
+
+        with self.sql_execution_asserter() as asserter:
+            metadata.drop_all(testing.db, checkfirst=False)
+        asserter.assert_(*assertions)
+
     def _assert_cyclic_constraint(
         self, metadata, auto=False, sqlite_warning=False
     ):
@@ -302,7 +349,7 @@ class ConstraintGenTest(fixtures.TestBase, AssertsExecutionResults):
         ]
 
         with self.sql_execution_asserter() as asserter:
-            metadata.drop_all(testing.db, checkfirst=False),
+            metadata.drop_all(testing.db, checkfirst=False)
         asserter.assert_(*assertions)
 
     def _assert_cyclic_constraint_no_alter(
@@ -366,10 +413,10 @@ class ConstraintGenTest(fixtures.TestBase, AssertsExecutionResults):
         if sqlite_warning:
             with expect_warnings("Can't sort tables for DROP; "):
                 with self.sql_execution_asserter() as asserter:
-                    metadata.drop_all(testing.db, checkfirst=False),
+                    metadata.drop_all(testing.db, checkfirst=False)
         else:
             with self.sql_execution_asserter() as asserter:
-                metadata.drop_all(testing.db, checkfirst=False),
+                metadata.drop_all(testing.db, checkfirst=False)
         asserter.assert_(*assertions)
 
     @testing.force_drop_names("a", "b")
@@ -1021,7 +1068,9 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
         )
 
         self.assert_compile(
-            schema.AddConstraint(list(t.foreign_keys)[0].constraint),
+            schema.AddConstraint(
+                list(t.foreign_keys)[0].constraint, isolate_from_table=False
+            ),
             "ALTER TABLE tbl ADD FOREIGN KEY(b) "
             "REFERENCES tbl (a) MATCH SIMPLE",
         )
@@ -1214,12 +1263,12 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
         )
 
         self.assert_compile(
-            schema.AddConstraint(constraint),
+            schema.AddConstraint(constraint, isolate_from_table=False),
             "ALTER TABLE tbl ADD CONSTRAINT my_test_constraint "
             "CHECK (a < b) DEFERRABLE INITIALLY DEFERRED",
         )
 
-    @testing.variation("isolate", [True, False])
+    @testing.combinations(True, False, "default", argnames="isolate")
     @testing.variation("type_", ["add", "drop"])
     def test_external_ck_constraint_cancels_internal(
         self, isolate: testing.Variation, type_: testing.Variation
@@ -1235,14 +1284,23 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
         )
 
         if type_.add:
+            ctx = nullcontext()
             cls = schema.AddConstraint
         elif type_.drop:
+            ctx = expect_deprecated(
+                "The ``isolate_from_table`` is deprecated and it be removed "
+                "in a future release."
+            )
             cls = schema.DropConstraint
         else:
             type_.fail()
 
-        if not isolate:
-            cls(constraint, isolate_from_table=False)
+        if not isolate or (type_.drop and isolate == "default"):
+            if isolate == "default":
+                cls(constraint)
+            else:
+                with ctx:
+                    cls(constraint, isolate_from_table=False)
             self.assert_compile(
                 schema.CreateTable(t),
                 "CREATE TABLE tbl (a INTEGER, b INTEGER, "
@@ -1250,7 +1308,11 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
                 "DEFERRABLE INITIALLY DEFERRED)",
             )
         else:
-            cls(constraint)
+            if isolate == "default":
+                cls(constraint)
+            else:
+                with ctx:
+                    cls(constraint, isolate_from_table=True)
             self.assert_compile(
                 schema.CreateTable(t),
                 "CREATE TABLE tbl (a INTEGER, b INTEGER)",
@@ -1304,7 +1366,7 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
         constraint = ForeignKeyConstraint(["b"], ["t2.a"])
         t.append_constraint(constraint)
         self.assert_compile(
-            schema.AddConstraint(constraint),
+            schema.AddConstraint(constraint, isolate_from_table=False),
             "ALTER TABLE tbl ADD FOREIGN KEY(b) REFERENCES t2 (a)",
         )
 
@@ -1314,7 +1376,7 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
         constraint = ForeignKeyConstraint([t.c.a], [t2.c.b])
         t.append_constraint(constraint)
         self.assert_compile(
-            schema.AddConstraint(constraint),
+            schema.AddConstraint(constraint, isolate_from_table=False),
             "ALTER TABLE tbl ADD FOREIGN KEY(a) REFERENCES t2 (b)",
         )
 
@@ -1324,7 +1386,7 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
         constraint = UniqueConstraint("a", "b", name="uq_cst")
         t2.append_constraint(constraint)
         self.assert_compile(
-            schema.AddConstraint(constraint),
+            schema.AddConstraint(constraint, isolate_from_table=False),
             "ALTER TABLE t2 ADD CONSTRAINT uq_cst UNIQUE (a, b)",
         )
 
@@ -1333,7 +1395,7 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
 
         constraint = UniqueConstraint(t2.c.a, t2.c.b, name="uq_cs2")
         self.assert_compile(
-            schema.AddConstraint(constraint),
+            schema.AddConstraint(constraint, isolate_from_table=False),
             "ALTER TABLE t2 ADD CONSTRAINT uq_cs2 UNIQUE (a, b)",
         )
 
@@ -1344,7 +1406,7 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
         constraint = PrimaryKeyConstraint(t.c.a)
         assert t.c.a.primary_key is True
         self.assert_compile(
-            schema.AddConstraint(constraint),
+            schema.AddConstraint(constraint, isolate_from_table=False),
             "ALTER TABLE tbl ADD PRIMARY KEY (a)",
         )
 
@@ -1354,7 +1416,7 @@ class ConstraintCompilationTest(fixtures.TestBase, AssertsCompiledSQL):
         constraint = CheckConstraint(t.c.a > 5)
 
         self.assert_compile(
-            schema.AddConstraint(constraint),
+            schema.AddConstraint(constraint, isolate_from_table=False),
             "ALTER TABLE tbl ADD CHECK (a > 5)",
         )
 
index 43cbc08ba92c194ea21543ad44caeb699440ccb6..166e1597b9e288dd0f534d61f10d9943359b930a 100644 (file)
@@ -5813,7 +5813,8 @@ class NamingConventionTest(fixtures.TestBase, AssertsCompiledSQL):
         is_(const[0].name, None)
 
         self.assert_compile(
-            AddConstraint(const[0]), "ALTER TABLE foo ADD UNIQUE (id)"
+            AddConstraint(const[0]),
+            "ALTER TABLE foo ADD UNIQUE (id)",
         )
 
     @testing.combinations(