Skip to content

alembic autogeneration "false-positively" detects change to comments from the empty string to None #1085

Closed

Description

@fmv1992

Describe the bug
Setting column comment to "" causes autogenerate to create changes even though no changes were made.

Expected behavior
I expected Column("mycol", sa.types.String(), comment="") to be treated as it is, and that sequential autogenerate commands would generate empty migrations.

To Reproduce
Please try to provide a Minimal, Complete, and Verifiable example, with the migration script and/or the SQLAlchemy tables or models involved.
See also Reporting Bugs on the website.

  • Code:

    class MyTable(Base):
        __tablename__ = "my_table"
        mycol = ColumnNonNull(
            sa.types.String(),
            primary_key=True,
            comment="""
    """.strip(),
        )
  • Table generation ok as expected:

    def upgrade() -> None:
        # ### commands auto generated by Alembic - please adjust! ###
        op.create_table(
            "my_table",
            sa.Column("mycol", sa.String(), nullable=False),
            sa.PrimaryKeyConstraint("mycol"),
            schema="public",
        )
        # ### end Alembic commands ###
    
    
    def downgrade() -> None:
        # ### commands auto generated by Alembic - please adjust! ###
        op.drop_table("my_table", schema="public")
        # ### end Alembic commands ###
  • Table generation not ok:

    def upgrade() -> None:
        # ### commands auto generated by Alembic - please adjust! ###
        op.alter_column(
            "my_table",
            "mycol",
            existing_type=sa.VARCHAR(),
            comment="",
            existing_nullable=False,
        )
        # ### end Alembic commands ###
    
    
    def downgrade() -> None:
        # ### commands auto generated by Alembic - please adjust! ###
        op.alter_column(
            "my_table",
            "mycol",
            existing_type=sa.VARCHAR(),
            comment=None,
            existing_comment="",
            existing_nullable=False,
        )
        # ### end Alembic commands ###
    • Expected:

      def upgrade() -> None:
          # ### commands auto generated by Alembic - please adjust! ###
          pass
          # ### end Alembic commands ###
      
      
      def downgrade() -> None:
          # ### commands auto generated by Alembic - please adjust! ###
          pass
          # ### end Alembic commands ###

Error

No errors per se.

Fix
If I set the column to anything other than "" it then starts to work as expected.

Versions.

  • OS: Ubuntu 20.04.5 LTS
  • Python: 3.8
  • Alembic: alembic==1.8.1
  • Database: PostgreSQL

Activity

  1. added
    bugSomething isn't working
    and removed
    requires triageNew issue that requires categorization
    on Sep 7, 2022
  2. CaselIT commented on Sep 7, 2022

    @CaselIT
    Member

    Hi,

    this is probably because somewhere there is something like an if comment and somewhere else there is a if commnet is not None.
    Thanks for reporting, but I'm not sure this warrants a fix, since the workaround is trivial

  3. IMGillusion commented on Sep 6, 2026

    @IMGillusion

    Full runnable reproduction (no database needed — the sqlite dialect reports supports_comments=False, which short-circuits the whole comparison path, so the comparators have to be exercised directly):

    from types import SimpleNamespace
    from sqlalchemy import Column, String, MetaData, Table
    from alembic.operations import ops
    from alembic.util.langhelpers import PriorityDispatchResult
    from alembic.autogenerate.compare.comments import (
        _compare_column_comment,
        _compare_table_comment,
    )
    
    ctx = SimpleNamespace(dialect=SimpleNamespace(supports_comments=True))
    
    # column: what the DB reflects back (None) vs what the model has ("")
    alter = ops.AlterTableOp("t")
    res = _compare_column_comment(
        ctx, alter, None, "t", "c",
        Column("c", String(), comment=None),
        Column("c", String(), comment=""),
    )
    print("column None vs '' ->", res)   # STOP (spurious) on current main
    
    # table: same, through the ops list
    mops = ops.ModifyTableOps("t", [])
    res = _compare_table_comment(
        ctx, mops, None, "t",
        Table("t", MetaData(), Column("c", String()), comment=None),
        Table("t", MetaData(), Column("c", String()), comment=""),
    )
    print("table None vs '' ->", res, mops.ops)  # STOP + CreateTableCommentOp (spurious)

    On current main both print STOP (false positive). With the fix in #1867 both print CONTINUE, while genuine comment changes ("x" vs None, "a" vs "b") still print STOP.

    PR #1867 implements the fix (normalize "" to None in both comparators, plus a regression test along the lines above) but was auto-closed by the prgate bot because this issue is not marked open for pull requests yet — it is ready to be reopened once the label is added.

  4. vineoy commented on Sep 29, 2026

    @vineoy
    Contributor

    Hi, just reproduced this on current main (Windows, Python 3.12, SQLAlchemy 2.0.55). Both the column and table cases return STOP for None vs '', and the table path even emits a CreateTableCommentOp, so every autogenerate run proposes a phantom migration. Genuine changes (None vs 'hello') still return STOP as expected.

    This bites us because our models use comment='' in a few places and the diff never comes back clean. Would you consider adding the open for pull requests label here? PR #1867 already implements the fix and looks ready to reopen once labeled. Happy to pick it up and carry it forward myself if the original author is away, either by reopening that one or opening a fresh PR, whichever you prefer.

  5. CaselIT commented on Sep 30, 2026

    @CaselIT
    Member

    That pr is wrong though. The fix here is not to treat none and "" as equal since they are not, it's to make create table render the empty comment

  6. vineoy commented on Sep 30, 2026

    @vineoy
    Contributor

    Thanks, agreed, #1867 is the wrong direction, None vs empty string should stay distinct.

    If I read it right, the loop is this: create_table render plus impl.create_table / toimpl.add_column all use if comment: so empty string is never emitted, reflect comes back None, comparator correctly fires STOP every run.

    I have a local fix ready that preserves empty string with is not None in autogenerate/render.py _add_table / _render_column and ddl/impl.py create_table plus operations/toimpl.py add_column and ddl/mysql.py _mysql_colspec, with render now emitting comment=''. Comparators untouched so genuine None to empty string changes still diff. Tests: test_autogen_render, test_op, test_autogen_diffs green.

    Does that match what you meant by make create table render the empty comment? If so, can you add open for pull requests here and I will put it up?

  7. CaselIT commented on Sep 30, 2026

    @CaselIT
    Member

    I think so, yes, I'll open for PR.

    Oracle may need special casing here, since IIRC empty string and None are in fact equal on it

  8. added
    code review in progresscode has been provided that's in review as PR and/or gerrit
    and removed
    open for pull requestsMaintainers have approved this issue for an outside pull request
    on Sep 30, 2026
  9. vineoy commented on Sep 30, 2026

    @vineoy
    Contributor

    Update: opened #1871 with this approach. Empty string is now preserved with is not None in render and DDL paths, comparators untouched except Oracle where empty string is normalized to none since it equals NULL there. Ready for review.

  10. sqla-tester commented on Oct 1, 2026

    @sqla-tester
    Collaborator

    vinayak has proposed a fix for this issue in the main branch:

    Preserve empty string comments on create table and add column https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/7016

  11. sqla-tester commented on Oct 19, 2026

    @sqla-tester
    Collaborator

    vinayak has proposed a fix for this issue in the main branch:

    Preserve empty string comments on create table and add column https://gerrit.sqlalchemy.org/c/sqlalchemy/alembic/+/7016

  12. zzzeek commented on Oct 19, 2026

    @zzzeek
    Member

    7. That pr is wrong though. The fix here is not to treat none and "" as equal since they are not, it's to make create table render the empty comment

    but they are "equal" for PG, MySQL, Oracle, likely lots of others.

  13. CaselIT commented on Oct 19, 2026

    @CaselIT
    Member

    I stand corrected then. I guess the crux of the issue is not that '' and null are the same but that an empty comment is treated as no comment.

    In pg it's even documented https://www.postgresql.org/docs/current/sql-comment.html

    I guess the issue then is to ensure that alembic does the same conditionally on the db impl.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingcode review in progresscode has been provided that's in review as PR and/or gerritlow priority

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions