How To Add Flask-Migrate To An Existing Project

Posted by
on under

A question that I frequently get is how to start tracking database migrations with my Flask-Migrate extension when the project has an established database, making it impossible to delete everything and start over from scratch. In this article and companion video I'm going to show you how to set up Flask-Migrate with zero risk for your current database.

Installing and Configuring Flask-Migrate

This step is the same for new and existing projects. Assuming you have a project that uses Flask-SQLAlchemy to manage a database, you begin by installing the Flask-Migrate extension:

(venv) $ pip install flask-migrate

The extension then needs to be added to the project. This involves creating and initializing a Migrate instance. If you use the direct method of creating extensions:

from flask_migrate import Migrate

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

If you use an application factory function you very likely use the delayed method to create your extensions. In that case you would initialize Flask-Migrate as follows:

from flask_migrate import Migrate

db = SQLAlchemy()
migrate = Migrate()

def create_app():
    app = Flask(__name__)
    db.init_app(app)
    migrate.init_app(app, db)
    return app

Once the extension is added to the project you will have a flask db command available:

(venv) $ flask db
Usage: flask db [OPTIONS] COMMAND [ARGS]...

  Perform database migrations.

Options:
  --help  Show this message and exit.

Commands:
  branches   Show current branch points
  current    Display the current revision for each...
  downgrade  Revert to a previous version
  edit       Edit a revision file
  heads      Show current available heads in the script...
  history    List changeset scripts in chronological...
  init       Creates a new migration repository.
  merge      Merge two revisions together, creating a new...
  migrate    Autogenerate a new revision file (Alias for...
  revision   Create a new revision file.
  show       Show the revision denoted by the given...
  stamp      'stamp' the revision table with the given...
  upgrade    Upgrade to a later version

Creating the Migration Repository

The next step in the process is to create a database migration repository. This is a directory where all the migration scripts are going to be stored. The migration repository should be considered part of your source code, and should be added to your source control.

To create the repository you can use the init command:

(venv) $ flask db init
  Creating directory /Users/mgrinberg/microblog/migrations ... done
  Creating directory /Users/mgrinberg/microblog/migrations/versions ... done
  Generating /Users/mgrinberg/microblog/migrations/script.py.mako ... done
  Generating /Users/mgrinberg/microblog/migrations/env.py ... done
  Generating /Users/mgrinberg/microblog/migrations/README ... done
  Generating /Users/mgrinberg/microblog/migrations/alembic.ini ... done
  Please edit configuration/connection/logging settings in 'migrations/alembic.ini' before proceeding.

This command adds a migrations directory in the root of your project. Add the directory and all of its contents to source control.

Creating the Initial Migration

If you look at the documentation, the next step in the process is to create an initial migration for your project, using the migrate command:

(venv) $ flask db migrate

This step does not actually work if you have an existing project that has a populated database:

(venv) $ flask db migrate
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.env] No changes in schema detected.

The "No changes in schema detected" message indicates that there is no need to migrate the database. But how can that be if we are just getting started with migrations in this project?

To understand the problem you have to think about how Alembic (the migration engine used by Flask-Migrate) generates database migrations. The contents of a migration are obtained by running a comparison between the current model definitions and the current schema of your database.

In a new project the models are going to be compared against a brand new (empty) database, so the migration script will include all the model definitions. For an existing project the database is up to date and in sync with the models, so there are no differences, and thus, Alembic thinks that there is nothing to put in the database migration script.

The trick to generate this initial migration for a project that uses an up to date database is to temporarily switch to an empty database, just for the flask db migrate command. In my projects I use a DATABASE_URL environment variable to configure the location of the database. This is a good practice that I recommend you follow as well, because with this variable it is easy to redirect to another database.

In general you can replace your real database with an in-memory SQLite database, just for the migrate command:

(venv) $ DATABASE_URL=sqlite:/// 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'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_email' on '['email']'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_token' on '['token']'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_user_username' on '['username']'
INFO  [alembic.autogenerate.compare] Detected added table 'followers'
INFO  [alembic.autogenerate.compare] Detected added table 'message'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_message_timestamp' on '['timestamp']'
INFO  [alembic.autogenerate.compare] Detected added table 'notification'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_notification_name' on '['name']'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_notification_timestamp' on '['timestamp']'
INFO  [alembic.autogenerate.compare] Detected added table 'post'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_post_timestamp' on '['timestamp']'
INFO  [alembic.autogenerate.compare] Detected added table 'task'
INFO  [alembic.autogenerate.compare] Detected added index 'ix_task_name' on '['name']'
  Generating migrations/versions/97180cecd04b_.py ... done

Using an in-memory SQLite database is very convenient, but will only work if you are in one of the two following situations:

  • You are also using SQLite for your real database
  • You are using another database engine (MySQL, Postgres, etc.), but your models are written generically, without using any constructs that are specific to your chosen engine.

If you cannot use an in-memory SQLite database, then go to your database server and create a new database there, and then set DATABASE_URL to it for the migrate command. Once you have generated the first migration you can delete the empty database from your server.

The next step according to the documentation is to run the upgrade command, which executes the migration script and applies the changes in it to your database. Obviously this is also going to fail, because the database does not need updating. Instead you have to tell Flask-Migrate and Alembic that the database is up to date. You can do this with the stamp command"

(venv) $ flask db stamp head

This command will add a alembic_version table to your database, and will store the initial migration generated above as the current migration. The head alias always points to the most recent migration, which in our case is the first and only one currently in the repository.

As I mentioned above, the contents of the migrations directory needs to be under source control. Remember to add the new migration script, which you will find in the migrations/versions directory.

Updating Additional Databases

You will likely have multiple databases for your application, at least one for development and one for production. You may be part of a team, so each team member will have their own development database in their local systems. Once you created the initial migration and added it to source control, you can just "stamp" all the additional databases. For each additional database make sure the code is refreshed from source control to include the migration repository and then just run the stamp command from above to "fix" the database:

(venv) $ flask db stamp head

Migration Workflow

From this point on you have a project that is fully enabled to use database migrations. The normal migration process goes as follows:

  • You will make some changes to your models in your Python source code.
  • You will then run flask db migrate to generate a new database migration for these changes.
  • You will finally apply the changes to the database by running flask db upgrade.

This cycle repeats every time new changes to the database schema are needed.

Conclusion

I hope this short tutorial clarifies how to add database migrations to your existing project. If you want to avoid the extra trouble required to get that first migration generated, in your next project consider adding Flask-Migrate from the start. It may seem unnecessary at first, but you'll eventually want to have it.

Become a Patron!

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

71 comments
  • #51 Miguel Grinberg said

    @Graeme: this often happens when the models aren't being imported. If the models are not imported, then SQLAlchemy and Alembic do not know about them and they assume the intention is to have a database without any tables in it.

  • #52 Anya Precious said

    Special Regards Miguel, I learnt Flask using your tutorial and so far I've set up several projects on heroku. I recently ran into problems with Heroku's Ephemeral filesystem, I have fix my flask app to use a cloud storage instead but my fear is if I deploy the update, my database maybe overwritten and it's content is already in use. I don't know if I should remove the flask db upgrade command from the profile and then deploy or it's safe to just deploy. Note the changes to the app is only on the routes.py file, no database changes at all

  • #53 Miguel Grinberg said

    @Anya: first of all, make a database backup. You should do this anyway, so go now and back up your data. I don't understand what the cloud storage has to do with your database. What database are you using?

  • #54 Anya Precious said

    @Miguel, I'm saving images files on external storage platform after I discovered heroku Ephemeral filesystem. I'm saving the filenames on the database. So I want to redeploy the app so that users upload automatically get stored on the external platform (Cloudinary)! I want to keep the database intact. It's postgresql database!

  • #55 Miguel Grinberg said

    @Anya: okay, so why do you think a "flask db upgrade" command will destroy your database? You should test your database upgrades locally on a test database before you deploy on the production system, but other than that there is no reason to remove the upgrade for a deployment.

  • #56 Luis Carlos said

    Well, that doesn't work to me. I add Flask-Migrate to an existing project of mine, but it was unable to detect migrations until I import them on where I define my factory function "create_app()". I don't know if this is a desgin failure from flask, the extension or if this is intentional.

  • #57 Miguel Grinberg said

    @Luis: I don't understand what you mean by "until I import them". What's them? If you mean the migration scripts, those are not meant to be imported.

  • #58 Sarah Brook said

    Hi Miguel,
    I'm already using flask-migrate in my project, but I've recently deployed my application to AWS (using Docker and AWS Lightsail containers). I'm not aware how I can access the command line (when the app is runing in a Lightsail container) to run flask-migrate if a new version of my app requires a schema update (eg columns need to be added or changed). Is there a way that my app can detect that it needs to update the schema (i.e. that the structure of the existing MySQL db in AWS no longer matches the updated models in the app ?)
    and run a db migrate/upgrade from within my flask app?
    PS - Your mega-tutorial is fantastic and really helped me get strated on my project!
    Thanks and regards
    Sarah.

  • #59 Miguel Grinberg said

    @Sarah: I haven't used containers on Lightsail, but don't you define a Dockerfile, with an entry point command? You can make the entry point of your app a script that first runs flask db upgrade and then your web server. Here is one of mine as an example: https://github.com/miguelgrinberg/microblog/blob/main/boot.sh.

  • #60 Sarah Brook said

    Thanks for the quick reply Miguel! That looks like a great suggestion! I'll try on Monday to see if I can do something similar. Thanks again!

  • #61 Rafal said

    Does Flask-migrate supports sequences? Seems that the answer is no. I would say its not a production ready tool (unfortunatelly)

  • #62 Miguel Grinberg said

    @Rafal: That tone isn't really going to help you here. FYI, Flask-Migrate is a Flask friendly wrapper for Alembic. That's all it is. I honestly have no idea what you are talking about, but whatever problems you have with migrations and production readiness you have to take them to Alembic, not Flask-Migrate.

  • #63 paul simmons said

    Hi Miguel.
    I have a question concerning your Flask-Migrate, I have three flask apps on one server, each with its own url, to simplify things they will share the same database, but only certain tables, will each need its own models.py? and if so how would I import this into each app? or if each needs its own models.py how would I migrate these if I alter or add a table to one off the apps?
    I hope you can help me on this
    Warm regards
    Paul

  • #64 Miguel Grinberg said

    @Paul: How you set everything up is really up to you. You can define models in a single app, or have models in all of them. For Flask-Migrate and Alembic the set up is easier if the models are all in a single app. Have different tables of the database assigned to different apps can be managed, but it is harder to set up. You will need to consult the Alembic documentation to learn how to create partitions using the include_object directive.

  • #65 Daniel Donovan said

    After I run flask db stamp head; if I run flask db migrate again the autodetect just keeps Detecting changes and generates a duplicate migrate script.

  • #66 Miguel Grinberg said

    @Daniel: What did you expected the flask db stamp head would do? This is just setting the current migration as applied, it has nothing to do with how the automatic migrations are generated. You need to find the correct revision your database is at, then run flask db stamp <revision>. After that, flask db upgrade to upgrade your database to latest.

  • #67 Leo said

    Hello Miguel, would this migrate library work in my started project Flask-SQLite/SQL Alchemy?
    I have a website in which several users are posting images, prices, etc, and every time
    I do a change in my code and then a commit, the database is replaced by the one I have on my localhost,
    so all POSTS made in the live server are being wiped.
    If I implement this Migration I will manage to solve my issue? Hope it has been understood. Thanks!

  • #68 Miguel Grinberg said

    @Leo: your problem is not related to database migrations. You should have separate databases for development and production.

  • #69 Netherwhal said

    Thanks,

    This helped a lot. There were too many wrong "solutions" in stackoverflow + github issues.

  • #70 Rob Neville said

    I'm having the exact opposite problem....

    If I don't do anything funny with the DB url, it generates drops for everything.

    When I override the DB url to an in-memory one, it says nothing needs to be done.

    What am I missing?

  • #71 Miguel Grinberg said

    @Rob: My guess is that what you are missing is importing your models. If Flask-Migrate things there is nothing to do when you have an empty database, then the models must be missing as well.

Leave a Comment