diff --git a/backend/druks/alembic_support.py b/backend/druks/alembic_support.py index 13ac92bd..3e68cb59 100644 --- a/backend/druks/alembic_support.py +++ b/backend/druks/alembic_support.py @@ -17,6 +17,10 @@ def _render_item(type_, obj, autogen_context): return "sa.String()" if type_ == "type" and isinstance(obj, EncryptedBytes | EncryptedJson | EncryptedText): return "sa.LargeBinary()" + # The column's type emits its own CHECK. SQLAlchemy 2.1 hides that from Alembic's + # own detection, so without this skip the revision renders the CHECK twice more. + if type_ == "check" and obj._type_bound: + return None return False diff --git a/backend/tests/test_app_migrations.py b/backend/tests/test_app_migrations.py index 71c98ebb..7d72d68d 100644 --- a/backend/tests/test_app_migrations.py +++ b/backend/tests/test_app_migrations.py @@ -1,8 +1,13 @@ +import enum from pathlib import Path +from typing import Literal from unittest.mock import MagicMock from alembic import command +from alembic.autogenerate import render_python_code from alembic.config import Config +from alembic.operations import ops +from druks.alembic_support import _render_item from druks.database import make_app_migration from druks.files import File, FileField from druks.models import Base @@ -24,6 +29,19 @@ class MigrationProbeFile(Base): image: Mapped[File] = FileField() +class ChoiceProbeStatus(enum.StrEnum): + OPEN = "open" + CLOSED = "closed" + + +class ChoiceProbe(Base): + __tablename__ = "choice_probe_rows" + + id: Mapped[int] = mapped_column(Integer, primary_key=True) + status: Mapped[ChoiceProbeStatus] + tone: Mapped[Literal["calm", "loud"]] + + _BASELINE = """\ import sqlalchemy as sa from alembic import op @@ -185,3 +203,9 @@ def test_file_field_autogenerates_and_upgrades_with_the_platform_foreign_key( "DROP TABLE IF EXISTS migration_probe_files, alembic_version_migration_probe" ) engine.dispose() + + +def test_choice_columns_render_no_check_beside_their_type(): + """A StrEnum or Literal column's type emits its own CHECK.""" + upgrade = ops.UpgradeOps([ops.CreateTableOp.from_table(ChoiceProbe.__table__)]) + assert "CheckConstraint" not in render_python_code(upgrade, render_item=_render_item)