"""Add country_iso to customer_addresses Revision ID: r6f7a8b9c0d1 Revises: q5e6f7a8b9c0 Create Date: 2026-01-02 Adds country_iso field to customer_addresses table and renames country to country_name for clarity. This migration is idempotent - it checks for existing columns before making changes. """ from alembic import op import sqlalchemy as sa # revision identifiers, used by Alembic. revision = "r6f7a8b9c0d1" down_revision = "q5e6f7a8b9c0" branch_labels = None depends_on = None # Country name to ISO code mapping for backfill COUNTRY_ISO_MAP = { "Luxembourg": "LU", "Germany": "DE", "France": "FR", "Belgium": "BE", "Netherlands": "NL", "Austria": "AT", "Italy": "IT", "Spain": "ES", "Portugal": "PT", "Poland": "PL", "Czech Republic": "CZ", "Czechia": "CZ", "Slovakia": "SK", "Hungary": "HU", "Romania": "RO", "Bulgaria": "BG", "Greece": "GR", "Croatia": "HR", "Slovenia": "SI", "Estonia": "EE", "Latvia": "LV", "Lithuania": "LT", "Finland": "FI", "Sweden": "SE", "Denmark": "DK", "Ireland": "IE", "Cyprus": "CY", "Malta": "MT", "United Kingdom": "GB", "Switzerland": "CH", "United States": "US", } def get_column_names(connection, table_name): """Get list of column names for a table.""" result = connection.execute(sa.text(f"PRAGMA table_info({table_name})")) return [row[1] for row in result] def upgrade() -> None: connection = op.get_bind() columns = get_column_names(connection, "customer_addresses") # Check if we need to do anything (idempotent check) has_country = "country" in columns has_country_name = "country_name" in columns has_country_iso = "country_iso" in columns # If already has new columns, nothing to do if has_country_name and has_country_iso: print(" Columns country_name and country_iso already exist, skipping") return # If has old 'country' column, rename it and add country_iso if has_country and not has_country_name: with op.batch_alter_table("customer_addresses") as batch_op: batch_op.alter_column( "country", new_column_name="country_name", ) # Add country_iso if it doesn't exist if not has_country_iso: with op.batch_alter_table("customer_addresses") as batch_op: batch_op.add_column( sa.Column("country_iso", sa.String(5), nullable=True) ) # Backfill country_iso from country_name for country_name, iso_code in COUNTRY_ISO_MAP.items(): connection.execute( sa.text( "UPDATE customer_addresses SET country_iso = :iso " "WHERE country_name = :name" ), {"iso": iso_code, "name": country_name}, ) # Set default for any remaining NULL values connection.execute( sa.text( "UPDATE customer_addresses SET country_iso = 'LU' " "WHERE country_iso IS NULL" ) ) # Make country_iso NOT NULL using batch operation with op.batch_alter_table("customer_addresses") as batch_op: batch_op.alter_column( "country_iso", existing_type=sa.String(5), nullable=False, ) def downgrade() -> None: connection = op.get_bind() columns = get_column_names(connection, "customer_addresses") has_country_name = "country_name" in columns has_country_iso = "country_iso" in columns has_country = "country" in columns # Only downgrade if in the new state if has_country_name and not has_country: with op.batch_alter_table("customer_addresses") as batch_op: batch_op.alter_column( "country_name", new_column_name="country", ) if has_country_iso: with op.batch_alter_table("customer_addresses") as batch_op: batch_op.drop_column("country_iso")