I faced a similar issue with sqlite. This won't work with postgres, but I'm sure it could be adapted to support postgres.
First, I got a list of tables and made a migration using:
my_tables = ["user", "account", "session"] # etc, I just added these manually conn = op.get_bind() for table in my_tables: # And https://stackoverflow.com/a/5499071/557406 row = conn.execute( f""" SELECT sql FROM ( SELECT sql sql, type type, tbl_name tbl_name, name name FROM sqlite_master UNION ALL SELECT sql, type, tbl_name, name FROM sqlite_temp_master ) WHERE type == 'table' AND sql NOTNULL AND name NOT LIKE 'sqlite_%' AND tbl_name = '{table}' LIMIT 1 """ ).first() lines = [line.strip() for line in row[0].split("\n")] new_create = [line for line in lines if not line.startswith("FOREIGN KEY")] new_create[0] = new_create[0].replace(table, f"{table}_new") if new_create[-2].endswith(","): new_create[-2] = new_create[-2][:-1] # NOTE: If we rename a table first, the existing foreign keys # point to the new table conn.execute("PRAGMA foreign_keys=off;") conn.execute("BEGIN TRANSACTION;") conn.execute("\n".join(new_create)) conn.execute(f"INSERT INTO {table}_new SELECT * FROM {table};") conn.execute(f"DROP TABLE {table}") conn.execute(f"ALTER TABLE {table}_new RENAME TO {table};") conn.execute("COMMIT;") conn.execute("PRAGMA foreign_keys=on;")
Note that I drop lines that start with "FOREIGN KEY". These are the unnamed foreign keys, the named ones mention "FOREIGN KEY" in the create line, but do not start with that text.
I then ran this migration.
I went back to my SQLAlchemy(app) and added a naming convention:
metadata = sa.MetaData( naming_convention={ "ix": "ix_%(column_0_label)s", "uq": "uq_%(table_name)s_%(column_0_name)s", "ck": "ck_%(table_name)s_%(constraint_name)s", "fk": "fk_%(table_name)s_%(column_0_name)s_%(referred_table_name)s", "pk": "pk_%(table_name)s", } ) db = SQLAlchemy(app=app, metadata=metadata)
Then I used alembic to autogenerate a second migration. This detected all the foreign keys that had been dropped and created them following the naming convention. I also had to add conn.execute("PRAGMA foreign_keys=off;") and a corresponding "on" line to the autogenerated migration
Also note, this does not work for other database types, so I used `if conn.engine.name == "sqlite"` to only run this on sqlite databases