]> git.ipfire.org Git - thirdparty/sqlalchemy/alembic.git/commitdiff
Support DROP CONSTRAINT IF EXISTS
authorAaron Griffin <aaron@growtherapy.com>
Wed, 30 Apr 2025 12:42:49 +0000 (08:42 -0400)
committerMike Bayer <mike_mp@zzzcomputing.com>
Fri, 2 May 2025 17:11:00 +0000 (13:11 -0400)
Added :paramref:`.op.drop_constraint.if_exists` parameter to
:func:`.op.drop_constraint` which will render "DROP CONSTRAINT IF EXISTS".
Pull request courtesy Aaron Griffin.

Also attempting to fix unrelated issue w/ test_stubs and python
versions which may be in a separate patch if it can't work here

Fixes: #1650
Closes: #1651
Pull-request: https://github.com/sqlalchemy/alembic/pull/1651
Pull-request-sha: 1c3c0d388d36fa60b9aaa013873a415488ed7796

Change-Id: I96378d45dd00898975f450299e6f1344bb65ccff

alembic/ddl/impl.py
alembic/ddl/mysql.py
alembic/ddl/sqlite.py
alembic/op.pyi
alembic/operations/base.py
alembic/operations/ops.py
alembic/operations/toimpl.py
alembic/util/compat.py
docs/build/unreleased/1650.rst [new file with mode: 0644]
tests/requirements.py
tests/test_op.py

index 9331f363a15c924926a85b8607f51e27ede123e6..a10702231f1803129d4d11c558509d63b5ca99f9 100644 (file)
@@ -387,8 +387,8 @@ class DefaultImpl(metaclass=ImplMeta):
         if const._create_rule is None or const._create_rule(self):
             self._exec(schema.AddConstraint(const))
 
-    def drop_constraint(self, const: Constraint) -> None:
-        self._exec(schema.DropConstraint(const))
+    def drop_constraint(self, const: Constraint, **kw: Any) -> None:
+        self._exec(schema.DropConstraint(const, **kw))
 
     def rename_table(
         self,
index 37afe4577686da383bb3bd874510c684f7a245fe..d92e3cd7d76be7d7df75e51a2a767ca141a94719 100644 (file)
@@ -167,6 +167,7 @@ class MySQLImpl(DefaultImpl):
     def drop_constraint(
         self,
         const: Constraint,
+        **kw: Any,
     ) -> None:
         if isinstance(const, schema.CheckConstraint) and _is_type_bound(const):
             return
index 7c6fb20c7a02e3358d45ec1335b550b05d2aa52f..5f141330fb8bb32d5c2c01996824ccaa2151a39a 100644 (file)
@@ -91,7 +91,7 @@ class SQLiteImpl(DefaultImpl):
                 "SQLite migrations using a copy-and-move strategy."
             )
 
-    def drop_constraint(self, const: Constraint):
+    def drop_constraint(self, const: Constraint, **kw: Any):
         if const._create_rule is None:
             raise NotImplementedError(
                 "No support for ALTER of constraints in SQLite dialect. "
index 14e6c39ae7fea1308bd14f2899b475b699451f93..8292a724c45a9b6152a2077dbae455fba3d5bc76 100644 (file)
@@ -957,6 +957,7 @@ def drop_constraint(
     type_: Optional[str] = None,
     *,
     schema: Optional[str] = None,
+    if_exists: Optional[bool] = None,
 ) -> None:
     r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
 
@@ -968,6 +969,10 @@ def drop_constraint(
      quoting of the schema outside of the default behavior, use
      the SQLAlchemy construct
      :class:`~sqlalchemy.sql.elements.quoted_name`.
+    :param if_exists: If True, adds IF EXISTS operator when
+     dropping the constraint
+
+     .. versionadded:: 1.15.3
 
     """
 
index d7823a5bfe677924fbb74dbbc3b83608f04d46e3..8ce0c45a8a48e5c575c5cffebd4d6bb6dfc3c989 100644 (file)
@@ -1393,6 +1393,7 @@ class Operations(AbstractOperations):
             type_: Optional[str] = None,
             *,
             schema: Optional[str] = None,
+            if_exists: Optional[bool] = None,
         ) -> None:
             r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
 
@@ -1404,6 +1405,10 @@ class Operations(AbstractOperations):
              quoting of the schema outside of the default behavior, use
              the SQLAlchemy construct
              :class:`~sqlalchemy.sql.elements.quoted_name`.
+            :param if_exists: If True, adds IF EXISTS operator when
+             dropping the constraint
+
+             .. versionadded:: 1.15.3
 
             """  # noqa: E501
             ...
index 8c0bdf854af352aa0d5b2adfc8d200ad73eab74b..633defe8ffabea877fb5bf4c0096afabf7d2dc14 100644 (file)
@@ -140,12 +140,14 @@ class DropConstraintOp(MigrateOperation):
         type_: Optional[str] = None,
         *,
         schema: Optional[str] = None,
+        if_exists: Optional[bool] = None,
         _reverse: Optional[AddConstraintOp] = None,
     ) -> None:
         self.constraint_name = constraint_name
         self.table_name = table_name
         self.constraint_type = type_
         self.schema = schema
+        self.if_exists = if_exists
         self._reverse = _reverse
 
     def reverse(self) -> AddConstraintOp:
@@ -203,6 +205,7 @@ class DropConstraintOp(MigrateOperation):
         type_: Optional[str] = None,
         *,
         schema: Optional[str] = None,
+        if_exists: Optional[bool] = None,
     ) -> None:
         r"""Drop a constraint of the given name, typically via DROP CONSTRAINT.
 
@@ -214,10 +217,20 @@ class DropConstraintOp(MigrateOperation):
          quoting of the schema outside of the default behavior, use
          the SQLAlchemy construct
          :class:`~sqlalchemy.sql.elements.quoted_name`.
+        :param if_exists: If True, adds IF EXISTS operator when
+         dropping the constraint
+
+         .. versionadded:: 1.15.3
 
         """
 
-        op = cls(constraint_name, table_name, type_=type_, schema=schema)
+        op = cls(
+            constraint_name,
+            table_name,
+            type_=type_,
+            schema=schema,
+            if_exists=if_exists,
+        )
         return operations.invoke(op)
 
     @classmethod
index 528c05428c910aba18639a9a60ac0f6dcd7377c2..bc5e73dddb89c9d4e9c95bb3eb39da710d988f05 100644 (file)
@@ -8,6 +8,7 @@ from sqlalchemy import schema as sa_schema
 from . import ops
 from .base import Operations
 from ..util.sqla_compat import _copy
+from ..util.sqla_compat import sqla_2
 
 if TYPE_CHECKING:
     from sqlalchemy.sql.schema import Table
@@ -197,13 +198,19 @@ def create_constraint(
 def drop_constraint(
     operations: "Operations", operation: "ops.DropConstraintOp"
 ) -> None:
+    kw = {}
+    if operation.if_exists is not None:
+        if not sqla_2:
+            raise NotImplementedError("SQLAlchemy 2.0 required")
+        kw["if_exists"] = operation.if_exists
     operations.impl.drop_constraint(
         operations.schema_obj.generic_constraint(
             operation.constraint_name,
             operation.table_name,
             operation.constraint_type,
             schema=operation.schema,
-        )
+        ),
+        **kw,
     )
 
 
index fa8bc02b607affc449095df29273af37d2d3fb4d..15d49cac18424a2d7782f8fecf895391e8a71450 100644 (file)
@@ -24,6 +24,7 @@ if True:
 
 is_posix = os.name == "posix"
 
+py314 = sys.version_info >= (3, 14)
 py313 = sys.version_info >= (3, 13)
 py311 = sys.version_info >= (3, 11)
 py310 = sys.version_info >= (3, 10)
diff --git a/docs/build/unreleased/1650.rst b/docs/build/unreleased/1650.rst
new file mode 100644 (file)
index 0000000..b52154e
--- /dev/null
@@ -0,0 +1,7 @@
+.. change::
+    :tags: usecase, operations
+    :tickets: 1650
+
+    Added :paramref:`.op.drop_constraint.if_exists` parameter to
+    :func:`.op.drop_constraint` which will render "DROP CONSTRAINT IF EXISTS".
+    Pull request courtesy Aaron Griffin.
index eb09dfa7b9db3b184d46273c169c7d170d15936e..4db5579edbdd40565b8a72571a49f770d83d519a 100644 (file)
@@ -374,7 +374,7 @@ class DefaultRequirements(SuiteRequirements):
         )
 
         version_high = exclusions.only_if(
-            lambda _: not compat.py313, "python 3.13 does not work right now"
+            lambda _: not compat.py314, "python 3.14 does not work right now"
         )
 
         sqlalchemy = exclusions.only_if(
index d9e65091fa5d62fd8131d88da73e8c2dbd10585c..510ec374de18d259b8f5c4d24c36423f4a188db2 100644 (file)
@@ -821,6 +821,19 @@ class OpTest(TestBase):
         op.drop_constraint("foo_bar_bat", "t1", schema="foo")
         context.assert_("ALTER TABLE foo.t1 DROP CONSTRAINT foo_bar_bat")
 
+    def test_drop_constraint_if_exists(self):
+        context = op_fixture()
+        if sqla_compat.sqla_2:
+            op.drop_constraint("foo_bar_bat", "t1", if_exists=True)
+            context.assert_(
+                "ALTER TABLE t1 DROP CONSTRAINT IF EXISTS foo_bar_bat"
+            )
+        else:
+            with expect_raises_message(
+                NotImplementedError, "SQLAlchemy 2.0 required"
+            ):
+                op.drop_constraint("foo_bar_bat", "t1", if_exists=True)
+
     def test_create_index(self):
         context = op_fixture()
         op.create_index("ik_test", "t1", ["foo", "bar"])