BarisSari · GitHub

Describe the bug
I have a migration file which basically creates a Check Constraint in upgrade() and drops it in downgrade(). When I define the constraint in the column, the downgrade throws ValueError: No such constraint: 'ck_Tasks_name'. When I define it in __table_args__, the downgrade works fine. upgrade() works in both definitions.

Expected behavior
I think Alembic should detect the per-column Check Constraints.

To Reproduce

# Model that throws an error during the downgrade
sqlite_naming_convention = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(column_0_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}
Metadata = MetaData(naming_convention=sqlite_naming_convention)
BaseSpatiaLite = declarative_base(metadata=Metadata)
class Task(BaseSpatiaLite):
    __tablename__ = "Tasks"
    task_id = Column(Integer, primary_key=True)
    # per-column constraint is below
    name = Column(String(150), CheckConstraint("name <> ''", name="ck_Tasks_name"), nullable=False)
# Model that works in both upgrade and downgrade
sqlite_naming_convention = {
    "ix": "ix_%(column_0_label)s",
    "uq": "uq_%(table_name)s_%(column_0_name)s",
    "ck": "ck_%(table_name)s_%(column_0_name)s",
    "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s",
    "pk": "pk_%(table_name)s",
}
Metadata = MetaData(naming_convention=sqlite_naming_convention)
BaseSpatiaLite = declarative_base(metadata=Metadata)
class Task(BaseSpatiaLite):
    __tablename__ = "Tasks"
    task_id = Column(Integer, primary_key=True)
    # per-column constraint moved to __table_args
    name = Column(String(150), nullable=False)
    __table_args__ = (CheckConstraint("name <> ''", name="ck_Tasks_name"), )
# migration file
"""Add CheckConstraints for non-nullable string cols

Revision ID: b7fc1ab24c92
Revises: d5d740c76aa3
Create Date: 2020-07-10 13:24:56.007611

"""
from models import Task
# revision identifiers, used by Alembic.
revision = "b7fc1ab24c92"
down_revision = "d5d740c76aa3"
branch_labels = None
depends_on = None
def upgrade():
    with op.batch_alter_table("Tasks", schema=None, copy_from=Task.__table__) as batch_op:
        batch_op.create_check_constraint("ck_Tasks_name", condition="name <> ''")
def downgrade():
    with op.batch_alter_table("Tasks", schema=None, copy_from=Task.__table__) as batch_op:
        batch_op.drop_constraint("ck_Tasks_name", type_="check")

Error

INFO  [alembic.runtime.migration] Running downgrade b7fc1ab24c92 -> d5d740c76aa3, Add CheckConstraints for non-nullable string cols
-- Running downgrade b7fc1ab24c92 -> d5d740c76aa3
Traceback (most recent call last):
  File .../lib/python3.8/site-packages/alembic/operations/batch.py", line 508, in drop_constraint
    const = self.named_constraints.pop(const.name)
KeyError: 'ck_Tasks_name'
...
File ".../migrations/sqlite_versions/2020-07-10_b7fc1ab24c92_add_checkconstraints_for_non_nullable_.py", line 451, in downgrade
    batch_op.drop_constraint("ck_Tasks_name", type_="check")
  File "/Users/baris/.pyenv/versions/3.8.1/lib/python3.8/contextlib.py", line 120, in __exit__
    next(self.gen)
  File ".../lib/python3.8/site-packages/alembic/operations/base.py", line 354, in batch_alter_table
    impl.flush()
  File ".../lib/python3.8/site-packages/alembic/operations/batch.py", line 114, in flush
    fn(*arg, **kw)
  File ".../lib/python3.8/site-packages/alembic/operations/batch.py", line 516, in drop_constraint
    raise ValueError("No such constraint: '%s'" % const.name)
ValueError: No such constraint: 'ck_Tasks_name'

Versions.

  • OS: Mac OS 10.15
  • Python: 3.8.1
  • Alembic: 1.4.2
  • SQLAlchemy: 1.3.10
  • Database: SQLite
  • DBAPI:

Additional context
If this is reported before or is explained in the Alembic's documentation, I'm sorry that I couldn't find any information.

Have a nice day!

Read the original on github.com ↗