Alembic migrations autogeneration

When it comes to autogeneration database migration files, I like the Django approach. Django compares migrations with model definitions and generates migration files, it is well described in this post.

Alembic migration autogeneration is based on a comparison of table metadata in the application and actual database state. In other words, you need to have a database with the actual state to detect changes in model definitions. It’s not convenient, but there is a workaround that will make it more Django-like.

Application.

This is our test Flask application with Flask-Migrate and Flask-SQLAlchemy extensions:

# app.py

import os

from flask import Flask
from flask_migrate import Migrate
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('DB_URL')

db = SQLAlchemy(app)
migrate = Migrate(app, db)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80))

The application gets a database URL from the environment variables.

Generate a new migrations repository:

$ FLASK_APP=app.py flask db init
  Creating directory migrations ...  done
  Creating directory migrations/versions ...  done
  Generating migrations/env.py ...  done
  Generating migrations/alembic.ini ...  done
  Generating migrations/README ...  done
  Generating migrations/script.py.mako ...  done
  Please edit configuration/connection/logging settings in 'migrations/alembic.ini' before proceeding.

Migrations auotogeneration.

The actual workaround is the following:

  • Create a temporary database
  • Set the database URL in environment variables
  • Apply all existent migrations
  • Generate new migrations

Let’s wrap all these steps in a Makefile:

# Makefile

makemigrations:
	find . -name _temp.db -delete
	DB_URL="sqlite:///_temp.db" flask db upgrade
	DB_URL="sqlite:///_temp.db" flask db migrate
	find . -name _temp.db -delete

Now you can generate migrations:

$ make makemigrations
find . -name _temp.db -delete
DB_URL="sqlite:///_temp.db" flask db upgrade
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
DB_URL="sqlite:///_temp.db" flask db migrate
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.autogenerate.compare] Detected added table 'user'
  Generating migrations/versions/eb3206646776_.py ...  done
find . -name _temp.db -delete

Feel free to contact me on LinkedIn or drop me an email.