I'm using Alembic (1.1.3) with SQLAlchemy and MySQL (5.7) in a Python (3.9) project to manage database migrations. Despite no changes to my ORM models or the database schema, every time I run alembic revision --autogenerate, the generated migration script includes redundant operations to drop and then recreate the same foreign key constraints, creating 100+ more rows that are deleting foreign keys and adding them again.
I am using single schema in my DB.
i saw `alembic revision --autogenerate` produces redundant foreign key migrations and tried to implement it but still no luck and it did not solve my issue.
I have made sure all the points made in that questions are not happening for me and still i am getting redundent migrations added.
- Not directly naming any db schemas in my sqlalchemy classes (always table_name.id, not schema_name.table_name.id)
- I tried adding the solution that sifts through all availabe schemas (include_scemas=true) with all the relevant parameters, it skipped the db schema i was referencing and instead went through all the other schemas and just outputted empty migration
- made sure my Base is using declarative correctly.
for example removing and adding foreign keys :
example model :
class Company(Base):
__tablename__ = 'company'
id = Column(Integer, primary_key=True, autoincrement=True)
created_at = Column(DateTime, default=datetime.datetime.now)
updated_at = Column(DateTime, default=datetime.datetime.now)
organization_id = Column(Integer, ForeignKey('organization.id'))
owner_id = Column(Integer, ForeignKey("users.id"))
name = Column(String(255))
slug = Column(String(255))
website = Column(String(255))
description = Column(String(255))
active_templates = relationship("ActiveTemplate", back_populates="company")
branches = relationship("Branch", back_populates="company")
my baseClass
from sqlalchemy import Integer, Column, MetaData
from sqlalchemy.ext.declarative import as_declarative, declared_attr
# # Define the naming convention
# 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"
# }
#
# # Apply the naming convention to the MetaData instance
# metadata = MetaData(naming_convention=convention)
@as_declarative()
class Base:
id: Column(Integer, primary_key=True, autoincrement=True)
__name__: str
# Generate __tablename__ automatically
@declared_attr
def __tablename__(cls) -> str:
return cls.__name__.lower()
def to_dict(self):
"""returns a dictionary representation of the object"""
return {c.name: getattr(self, c.name) for c in self.__table__.columns}
op.drop_index('fk_company_1_idx', table_name='company')
op.drop_constraint('fk_company_1', 'company', type_='foreignkey')
op.drop_constraint('company_ibfk_1', 'company', type_='foreignkey')
op.create_foreign_key(None, 'company', 'users', ['owner_id'], ['id'])
op.create_foreign_key(None, 'company', 'organization', ['organization_id'], ['id'])
my env.py
from __future__ import with_statement
import os
import sys
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
from dotenv import load_dotenv
load_dotenv()
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
# target_metadata = None
sys.path = ['', '..'] + sys.path[1:] # noqa
from app.db.base import * # noqa
from app.db.base_class import Base # noqa
target_metadata = Base.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def get_url():
user = os.getenv("DB_USER")
password = os.getenv("DB_PASSWORD")
server = os.getenv("DB_HOST")
db = os.getenv("DB_NAME")
port = str(os.getenv("PORT"))
return f"mysql+mysqlconnector://{user}:{password}@{server}:{port}/{db}"
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = get_url()
context.configure(
url=url, target_metadata=target_metadata, literal_binds=True, compare_type=True
)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
configuration = config.get_section(config.config_ini_section)
configuration["sqlalchemy.url"] = get_url()
connectable = engine_from_config(
configuration,
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=True,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()```
i will appriciate any help im totally not sure how to solve this now.
tried running alembic autogenerate and i keep getting redundent field names that are added to the migration.