Missing batch_op.f() #1458
-
Describe the bugWhen alembic generates the code for migrations, it uses I don't know why (did not have the time to search for too long) but sometimes Alembic will not add batch_op.f() in batch operation. It seems to me that it does not add during:
This behaviour of not adding the batch_op.f() seem to affect:
Expected behaviorAdding To ReproduceI can't share the full code but this is my env file: def run_migrations_offline() -> None:
url = config.get_main_option("sqlalchemy.url")
context.configure(
url=url,
target_metadata=target_metadata,
literal_binds=True,
dialect_opts={"paramstyle": "named"},
render_as_batch=True,
)
with context.begin_transaction():
logging.warning("Offline migration")
context.run_migrations()
def run_migrations_online() -> None:
connectable = engine_from_config(
config.get_section(config.config_ini_section, {}),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=target_metadata,
render_as_batch=True,
)
with context.begin_transaction():
context.run_migrations()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online() ErrorI don't remember if it was an error or the contraint being recreating for each migration. (It was few weeks ago) Versions
Dirty fixI had many old migration that were affected and did not want to modify manually thousands of lines of codes. The fix is more for people that already have migrations with missing def fix_names_not_encapsulated(
func, arg_pos: int | None = None, kwarg_key: str | None = None
):
def sanitize_name(constraint_name: str | None) -> str | None | sqlalchemy_conv:
if not (
constraint_name is None
or constraint_name == "alembic_version_pkc"
or isinstance(constraint_name, sqlalchemy_conv)
):
constraint_name = op.f(constraint_name)
for frame_info in inspect.stack()[1:]:
if os.path.normpath(os.path.dirname(__file__)) in frame_info.filename:
message = (
f'Missing encapsulation for "{constraint_name}" '
f'(File "{frame_info.filename}", '
f"line {frame_info.lineno}, "
f"in {frame_info.function})"
)
break
else:
message = f"Missing encapsulation for {constraint_name}"
logging.info(message)
return constraint_name
@wraps(func)
def wrapper(*args, **kwargs):
if arg_pos is not None:
args = (*args[:arg_pos], sanitize_name(args[arg_pos]), *args[arg_pos + 1 :])
elif kwarg_key is not None:
kwargs[kwarg_key] = sanitize_name(kwargs[kwarg_key])
else:
raise ValueError("You must specify either arg_pos or kwarg_key")
func(*args, **kwargs)
return wrapper
DropConstraintOp.__init__ = fix_names_not_encapsulated(DropConstraintOp.__init__, arg_pos=1)
DropIndexOp.__init__ = fix_names_not_encapsulated(DropIndexOp.__init__, arg_pos=1)
CreateIndexOp.__init__ = fix_names_not_encapsulated(CreateIndexOp.__init__, arg_pos=1)
AddConstraintOp.__init__ = fix_names_not_encapsulated(AddConstraintOp.__init__, arg_pos=1)
CreateForeignKeyOp.__init__ = fix_names_not_encapsulated(CreateForeignKeyOp.__init__, arg_pos=1)
Constraint.__init__ = fix_names_not_encapsulated(Constraint.__init__, kwarg_key="name") If you need some more info, feel free. This is not really an issue for me anymore since I got a fix but it might help someone understand what is going wrong with its own setup Have a nice day! |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 2 replies
-
you dont need op.f() if your constraints have names already or if you arent using naming conventions, can I see an example of a "bad" migration as well as your naming convention setup? thanks |
Beta Was this translation helpful? Give feedback.
-
that's not the purpose of op.f(). op.f() is strictly to override constraint naming conventions from taking place. if your names are hardcoded more than 64 chars that's a bug on your end and you need to fix that |
Beta Was this translation helpful? Give feedback.
-
I'm using naming convention. I did not create the names. This is my SQLAlchemy setup: from sqlalchemy.orm.decl_api import declarative_base
from sqlalchemy.schema import MetaData
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",
}
Base = declarative_base(metadata=MetaData(naming_convention=convention))
metadata: MetaData = Base.metadata When using def upgrade() -> None:
[...]
with op.batch_alter_table("single_source_of_truth", schema=None) as batch_op:
batch_op.add_column(
sa.Column("asset_models_version_id", sa.Integer(), nullable=True)
)
batch_op.add_column(
sa.Column("asset_financials_version_id", sa.Integer(), nullable=True)
)
batch_op.add_column(
sa.Column("financial_context_version_id", sa.Integer(), nullable=True)
)
batch_op.drop_constraint(
"fk_single_source_of_truth_solar_pv_version_id_file_version",
type_="foreignkey",
)
batch_op.drop_constraint(
"fk_single_source_of_truth_biogas_plant_version_id_file_version",
type_="foreignkey",
)
batch_op.drop_constraint(
"fk_single_source_of_truth_air_separation_unit_version_id_file_version",
type_="foreignkey",
)
batch_op.drop_constraint(
"fk_single_source_of_truth_saf_fischer_tropsch_version_id_file_version",
type_="foreignkey",
)
batch_op.drop_constraint(
"fk_single_source_of_truth_desalination_plant_version_id_file_version",
type_="foreignkey",
)
batch_op.drop_constraint(
"fk_single_source_of_truth_hydrogen_longterm_storage_version_id_file_version", print(len('fk_single_source_of_truth_hydrogen_longterm_storage_version_id_file_version'))
75 It might have something to do with the fact that I may have use SQLite during the migration generation. |
Beta Was this translation helpful? Give feedback.
the f() operation can always be from
op.f()
and it's not needed for those FK names.op.f()
is only needed when the naming convention uses the"%(constraint_name)s"
token, to indicate this name is already translated.names like
""fk_single_source_of_truth_solar_pv_version_id_file_version"
that are DROPs here came directly from your database, which in this case would be the SQLite database. So if those names are then too long for MySQL, this is not a directly supported use case for alembic, alembic assumes autogen migrations would be created against the specific type of database that is used for production operations. so you would need to manually specify these constraint names in your code.…