chore: Improve Alembic Migration Generation (#4192)

This commit is contained in:
Michael Genson 2024-09-16 08:52:12 -05:00 committed by GitHub
parent 77208384ed
commit 8778559a20
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
35 changed files with 105 additions and 148 deletions

View File

@ -1,4 +1,6 @@
from sqlalchemy import engine_from_config, pool
from typing import Any
import sqlalchemy as sa
import mealie.db.models._all_models # noqa: F401
from alembic import context
@ -29,6 +31,28 @@ if not settings.DB_URL:
config.set_main_option("sqlalchemy.url", settings.DB_URL.replace("%", "%%"))
def include_object(object: Any, name: str, type_: str, reflected: bool, compare_to: Any):
# skip dropping food/unit unique constraints; they are defined manually so alembic doesn't see them
# see: revision dded3119c1fe
if type_ == "unique_constraint" and name == "ingredient_foods_name_group_id_key" and compare_to is None:
return False
if type_ == "unique_constraint" and name == "ingredient_units_name_group_id_key" and compare_to is None:
return False
# skip changing the quantity column in recipes_ingredients; it's a float on postgres, but an integer on sqlite
# see: revision 263dd6707191
if (
type_ == "column"
and name == "quantity"
and object.table.name == "recipes_ingredients"
and hasattr(compare_to, "type")
and isinstance(compare_to.type, sa.Integer)
):
return False
return True
def run_migrations_offline():
"""Run migrations in 'offline' mode.
@ -60,15 +84,19 @@ def run_migrations_online():
and associate a connection with the context.
"""
connectable = engine_from_config(
connectable = sa.engine_from_config(
config.get_section(config.config_ini_section),
prefix="sqlalchemy.",
poolclass=pool.NullPool,
poolclass=sa.pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata, user_module_prefix="mealie.db.migration_types."
connection=connection,
target_metadata=target_metadata,
user_module_prefix="mealie.db.migration_types.",
render_as_batch=True,
include_object=include_object,
)
with context.begin_transaction():

View File

@ -9,13 +9,15 @@ import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
${imports if imports else ""}
% if imports:
${imports}
% endif
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
branch_labels = ${repr(branch_labels)}
depends_on = ${repr(depends_on)}
down_revision: str | None = ${repr(down_revision)}
branch_labels: str | tuple[str, ...] | None = ${repr(branch_labels)}
depends_on: str | tuple[str, ...] | None = ${repr(depends_on)}
def upgrade():

View File

@ -14,9 +14,9 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "6b0f5f32d602"
down_revision = None
branch_labels = None
depends_on = None
down_revision: str | None = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Adapted from https://improveandrepeat.com/2021/09/python-friday-87-handling-pre-existing-tables-with-alembic-and-sqlalchemy/

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "263dd6707191"
down_revision = "6b0f5f32d602"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "f1a2dbee5fe9"
down_revision = "263dd6707191"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "59eb59135381"
down_revision = "f1a2dbee5fe9"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "09dfc897ad62"
down_revision = "59eb59135381"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "ab0bae02578f"
down_revision = "09dfc897ad62"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "f30cf048c228"
down_revision = "ab0bae02578f"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "188374910655"
down_revision = "f30cf048c228"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "089bfa50d0ed"
down_revision = "188374910655"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "44e8d670719d"
down_revision = "089bfa50d0ed"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "2ea7a807915c"
down_revision = "44e8d670719d"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "1923519381ad"
down_revision = "2ea7a807915c"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "167eb69066ad"
down_revision = "1923519381ad"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "165d943c64ee"
down_revision = "167eb69066ad"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -11,8 +11,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "ff5f73b01a7a"
down_revision = "165d943c64ee"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -11,8 +11,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "16160bf731a0"
down_revision = "ff5f73b01a7a"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -17,8 +17,8 @@ from mealie.db.models._model_utils.guid import GUID
# revision identifiers, used by Alembic.
revision = "5ab195a474eb"
down_revision = "16160bf731a0"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
class SqlAlchemyBase(DeclarativeBase):

View File

@ -18,8 +18,8 @@ from mealie.db.models._model_utils.guid import GUID
# revision identifiers, used by Alembic.
revision = "b04a08da2108"
down_revision = "5ab195a474eb"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "38514b39a824"
down_revision = "b04a08da2108"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@ -11,8 +11,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "b3dbb554ba53"
down_revision = "38514b39a824"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def get_db_type():

View File

@ -16,8 +16,8 @@ from mealie.db.models.group.group import Group
# revision identifiers, used by Alembic.
revision = "04ac51cbe9a4"
down_revision = "b3dbb554ba53"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def populate_group_slugs(session: Session):

View File

@ -13,8 +13,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "1825b5225403"
down_revision = "04ac51cbe9a4"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -11,8 +11,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "bcfdad6b7355"
down_revision = "1825b5225403"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -15,8 +15,8 @@ from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUn
# revision identifiers, used by Alembic.
revision = "0341b154f79a"
down_revision = "bcfdad6b7355"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def populate_normalized_fields():

View File

@ -23,8 +23,8 @@ from mealie.db.models.recipe.ingredient import IngredientFoodModel, IngredientUn
# revision identifiers, used by Alembic.
revision = "dded3119c1fe"
down_revision = "0341b154f79a"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
# Intermediate table definitions

View File

@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "ba1e4a6cfe99"
down_revision = "dded3119c1fe"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -20,8 +20,8 @@ logger = get_logger()
# revision identifiers, used by Alembic.
revision = "2298bb460ffd"
down_revision = "ba1e4a6cfe99"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@ -11,8 +11,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "09aba125b57a"
down_revision = "2298bb460ffd"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@ -20,8 +20,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "d7c6efd2de42"
down_revision = "09aba125b57a"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():
@ -202,8 +202,6 @@ def downgrade():
)
op.drop_index(op.f("ix_recipes_rating"), table_name="recipes")
op.alter_column("recipes", "rating", existing_type=sa.Float(), type_=sa.INTEGER(), existing_nullable=True)
op.create_unique_constraint("ingredient_units_name_group_id_key", "ingredient_units", ["name", "group_id"])
op.create_unique_constraint("ingredient_foods_name_group_id_key", "ingredient_foods", ["name", "group_id"])
op.create_table(
"users_to_favorites",
sa.Column("user_id", sa.CHAR(length=32), nullable=True),

View File

@ -14,8 +14,8 @@ from alembic import op
# revision identifiers, used by Alembic.
revision = "7788478a0338"
down_revision = "d7c6efd2de42"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def upgrade():

View File

@ -11,12 +11,11 @@ from sqlalchemy import orm
from alembic import op
# revision identifiers, used by Alembic.
revision = "32d69327997b"
down_revision = "7788478a0338"
branch_labels = None
depends_on = None
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
def is_postgres():

View File

@ -22,8 +22,8 @@ from mealie.core.config import get_app_settings
# revision identifiers, used by Alembic.
revision = "feecc8ffb956"
down_revision = "32d69327997b"
branch_labels = None # type: ignore
depends_on = None # type: ignore
branch_labels: str | tuple[str, ...] | None = None
depends_on: str | tuple[str, ...] | None = None
settings = get_app_settings()

View File

@ -11,75 +11,5 @@ How exactly you need to modify it is of course highly contextual to the change y
In your dev container you can run something like (change the message) `task py:migrate -- "Add creation tag to group preferences"` to have Alembic generate an upgrade script for you.
The script Alembic generates, will be limited! (Perhaps there's a way to resolve that? Haven't looked into it yet)
For example, Alembic generated a script _similar_ to this (it has been modified already to have accurate foreign key names, for instance):
```Python
"""Add creation tag to group preferences
Revision ID: 0ea6eb8eaa44
Revises: ba1e4a6cfe99
Create Date: 2024-01-04 12:40:03.062671
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "0ea6eb8eaa44"
down_revision = "ba1e4a6cfe99"
branch_labels = None
depends_on = None
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.add_column(
"group_preferences", sa.Column("recipe_creation_tag", mealie.db.migration_types.GUID(), nullable=True)
)
op.create_foreign_key("fk_groupprefs_tags", "group_preferences", "tags", ["recipe_creation_tag"], ["id"])
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_constraint("fk_groupprefs_tags", "group_preferences", type_="foreignkey")
op.drop_column("group_preferences", "recipe_creation_tag")
### end Alembic commands ###
```
But when trying to actually use that upgrade script, it becomes clear that our SQLite database doesn't like them. The minor modification needed looks like:
```Python
"""Add creation tag to group preferences
Revision ID: 0ea6eb8eaa44
Revises: ba1e4a6cfe99
Create Date: 2024-01-04 12:40:03.062671
"""
import sqlalchemy as sa
import mealie.db.migration_types
from alembic import op
# revision identifiers, used by Alembic.
revision = "0ea6eb8eaa44"
down_revision = "ba1e4a6cfe99"
branch_labels = None
depends_on = None
def upgrade():
with op.batch_alter_table("group_preferences", schema=None) as batch_op:
batch_op.add_column(sa.Column("recipe_creation_tag", mealie.db.migration_types.GUID(), nullable=True))
batch_op.create_foreign_key("fk_groupprefs_tags", "tags", ["recipe_creation_tag"], ["id"])
def downgrade():
with op.batch_alter_table("group_preferences", schema=None) as batch_op:
batch_op.drop_constraint("fk_groupprefs_tags", type_="foreignkey")
batch_op.drop_column("recipe_creation_tag")
```
Alembic's script migration isn't perfect, so you will need to review which changes are generated. You will also need to make sure any custom operations work on both SQLite and Postgres.
There are some known limitations with our migrations and Alembic's auto-generation, which is accounted for in `/alembic/env.py`. If any of your migrations overlap with the columns in `include_object`, you may need to manually adjust the migration.