From: Aaron Griffin Date: Wed, 30 Apr 2025 12:42:49 +0000 (-0400) Subject: Support DROP CONSTRAINT IF EXISTS X-Git-Tag: rel_1_16_0~23 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=132a2325afc6329ab55b2f20a3b5b47b9394c816;p=thirdparty%2Fsqlalchemy%2Falembic.git Support DROP CONSTRAINT IF EXISTS 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 --- diff --git a/alembic/ddl/impl.py b/alembic/ddl/impl.py index 9331f363..a1070223 100644 --- a/alembic/ddl/impl.py +++ b/alembic/ddl/impl.py @@ -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, diff --git a/alembic/ddl/mysql.py b/alembic/ddl/mysql.py index 37afe457..d92e3cd7 100644 --- a/alembic/ddl/mysql.py +++ b/alembic/ddl/mysql.py @@ -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 diff --git a/alembic/ddl/sqlite.py b/alembic/ddl/sqlite.py index 7c6fb20c..5f141330 100644 --- a/alembic/ddl/sqlite.py +++ b/alembic/ddl/sqlite.py @@ -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. " diff --git a/alembic/op.pyi b/alembic/op.pyi index 14e6c39a..8292a724 100644 --- a/alembic/op.pyi +++ b/alembic/op.pyi @@ -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 """ diff --git a/alembic/operations/base.py b/alembic/operations/base.py index d7823a5b..8ce0c45a 100644 --- a/alembic/operations/base.py +++ b/alembic/operations/base.py @@ -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 ... diff --git a/alembic/operations/ops.py b/alembic/operations/ops.py index 8c0bdf85..633defe8 100644 --- a/alembic/operations/ops.py +++ b/alembic/operations/ops.py @@ -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 diff --git a/alembic/operations/toimpl.py b/alembic/operations/toimpl.py index 528c0542..bc5e73dd 100644 --- a/alembic/operations/toimpl.py +++ b/alembic/operations/toimpl.py @@ -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, ) diff --git a/alembic/util/compat.py b/alembic/util/compat.py index fa8bc02b..15d49cac 100644 --- a/alembic/util/compat.py +++ b/alembic/util/compat.py @@ -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 index 00000000..b52154eb --- /dev/null +++ b/docs/build/unreleased/1650.rst @@ -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. diff --git a/tests/requirements.py b/tests/requirements.py index eb09dfa7..4db5579e 100644 --- a/tests/requirements.py +++ b/tests/requirements.py @@ -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( diff --git a/tests/test_op.py b/tests/test_op.py index d9e65091..510ec374 100644 --- a/tests/test_op.py +++ b/tests/test_op.py @@ -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"])