pythonsqlalchemycontinuous-integrationalembic

How do I check if Alembic migrations need to be generated?


I'm trying to improve CI pipeline to prevent situations where SQLAlchemy models are added or changed, but no Alembic migration is written or generated by the commit author from hitting the production branch.

alembic --help doesn't seem to provide any helpful commands for this case, yet it already has all the metadata required (target_metadata variable) and the database credentials in env.py to make this happen.

What would be the best practice for implementing this check in CI?


Solution

  • Here's a solution that I use. It's a check that I have implemented as a test.

    from alembic.autogenerate import compare_metadata
    from alembic.command import upgrade
    from alembic.runtime.migration import MigrationContext
    from alembic.config import Config
    
    from models.base import Base
    
    
    def test_migrations_sane():
        """
        This test ensures that models defined by SQLAlchemy match what alembic migrations think
        the database should look like. If these are different, then once we have constructed
        the database via Alembic (via running all migrations) alembic will generate a set of changes to
        modify the database to match the schema defined by SQLAlchemy models. If these are the same,
        the set of changes is going to be empty. Which is exactly what we want to check.
        """
        engine = "SQLAlchemy DB Engine instance"
        try:
            with engine.connect() as connection:
                alembic_conf_file = "location of alembic.ini"
                alembic_config = Config(alembic_conf_file)
                upgrade(alembic_config, "head")
                mc = MigrationContext.configure(connection)
                diff = compare_metadata(mc, Base.metadata)
    
                assert diff == []
        finally:
            with engine.connect() as connection:
                # Resetting the DB
                connection.execute(
                    """
                    DROP SCHEMA public CASCADE;
                    CREATE SCHEMA public;
                    GRANT ALL ON SCHEMA public TO postgres;
                    GRANT ALL ON SCHEMA public TO public;
                    """
                )
    

    EDIT: I noticed you linked a library that's supposed to do the same thing. I gave it a go but it seems like it assumes that the database that it's running the check against has to have had alembic run against it. My solution works against a blank db.

    EDIT EDIT: Since leaving this answer, I have discovered the pytest-alembic package which offers this functionality and more. I have not used it personally but it seems pretty sweet.