From 0b0743700a319b09f638c273c69bd7fc69b3bf94 Mon Sep 17 00:00:00 2001 From: "pavdwest.xps15" Date: Sun, 6 Sep 2026 17:17:47 +0200 Subject: [PATCH] Don't crash when an altered column's old class is a custom Column subclass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `SchemaDiffer.alter_columns` looks up `UniqueGlobalNames.COLUMN_` for both the new and the old column class, to spot an import that would conflict with a name the generated migration already uses. The lookup for the new class passes a `None` default; the one for the old class does not, so altering a column whose previous class was a user-defined `Column` subclass raises AttributeError: type object 'UniqueGlobalNames' has no attribute 'COLUMN_CUSTOMVARCHAR' and `piccolo migrations new --auto` dies before writing anything. `UniqueGlobalNames` only carries the built-in column classes, so a miss is the expected outcome for any subclass a user defines — which is a supported thing to do, and common for projects that wrap a column type to add a default or narrow its type. Five of the six lookups in this module already pass `None`; this makes the sixth behave the same way. Closes #1428 --- piccolo/apps/migrations/auto/schema_differ.py | 1 + .../migrations/auto/test_schema_differ.py | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/piccolo/apps/migrations/auto/schema_differ.py b/piccolo/apps/migrations/auto/schema_differ.py index 58543cbca..cece5334b 100644 --- a/piccolo/apps/migrations/auto/schema_differ.py +++ b/piccolo/apps/migrations/auto/schema_differ.py @@ -516,6 +516,7 @@ def alter_columns(self) -> AlterStatements: expect_conflict_with_global_name=getattr( UniqueGlobalNames, f"COLUMN_{alter_column.old_column_class.__name__.upper()}", # noqa: E501 + None, ), ) ) diff --git a/tests/apps/migrations/auto/test_schema_differ.py b/tests/apps/migrations/auto/test_schema_differ.py index 64a1f2d67..5b72cd3ef 100644 --- a/tests/apps/migrations/auto/test_schema_differ.py +++ b/tests/apps/migrations/auto/test_schema_differ.py @@ -450,6 +450,53 @@ def test_alter_column_precision(self) -> None: "manager.alter_column(table_class_name='Ticket', tablename='ticket', column_name='price', db_column_name='price', params={'digits': (4, 2)}, old_params={'digits': (5, 2)}, column_class=Numeric, old_column_class=Numeric, schema=None)", # noqa ) + def test_alter_column_with_custom_old_column_class(self) -> None: + """ + A column whose *old* class is a user-defined ``Column`` subclass must + not crash the differ. + + ``UniqueGlobalNames`` only knows the built-in column classes, so the + lookup for the old class has to tolerate a miss the same way the lookup + for the new class already does. + + https://github.com/piccolo-orm/piccolo/issues/1428 + + """ + + class CustomVarchar(Varchar): + pass + + name_1 = Varchar(length=20) + name_1._meta.name = "name" + + name_2 = CustomVarchar(length=10) + name_2._meta.name = "name" + + schema: list[DiffableTable] = [ + DiffableTable( + class_name="Band", + tablename="band", + columns=[name_1], + ) + ] + schema_snapshot: list[DiffableTable] = [ + DiffableTable( + class_name="Band", + tablename="band", + columns=[name_2], + ) + ] + + schema_differ = SchemaDiffer( + schema=schema, schema_snapshot=schema_snapshot, auto_input="y" + ) + + self.assertEqual(len(schema_differ.alter_columns.statements), 1) + self.assertIn( + "old_column_class=CustomVarchar", + schema_differ.alter_columns.statements[0], + ) + def test_db_column_name(self) -> None: """ Make sure alter statements use the ``db_column_name`` if provided.