Never knowingly knowing narwhals

SQLAlchemy-Migrate upgrade scripts in a transaction

SQLAlchemy Migrate is a really good tool for managing database upgrades for SQLAlchemy centric projects. I've been using it for 6 months on New Metal Army and I haven't had a screwed website upgrade yet!

For those that don't know SQLAlchemy-migrate allows you to version control database changes and easily upgrade and downgrade a database. Basically you write a python script with two functions: upgrade and downgrade. You test the script against the database, commit it to the SQLAlchemy-migrate version repository (not to be confused with your source control mechanism). Finally you upgrade your development database.

When it the time comes to deploy the new application you simply ask sqlalchemy-migrate to upgrade your database to the current version. sqlalchemy-migrate reads the current version of your schema from the database (via a custom table it inserts) and proceeds to upgrade your schema by running each upgrade script in turn.

Often you want your upgrades and downgrades to run within a transaction. Not because you expect them to fail but because while writing them you don't wont to leave the database partially upgraded or downgraded if your script fails. To do this I wrote a transaction decorator. Here is a template for an upgrade script:

  1. #!/usr/bin/env python
  2. # encoding: utf-8
  3.  
  4. from datetime import datetime
  5. from sqlalchemy import *
  6. from migrate import *
  7. from migrate.changeset import *
  8.  
  9. metadata = MetaData(migrate_engine)
  10.  
  11. def transaction(f, *args, **kwargs):
  12. def wrapper(*args, **kwargs):
  13. connection = migrate_engine.connect()
  14. transaction = connection.begin()
  15. try:
  16. result = f(*args, **kwargs)
  17. transaction.commit()
  18. return result
  19. except:
  20. transaction.rollback()
  21. raise
  22. finally:
  23. connection.close()
  24.  
  25. wrapper.__name__ = f.__name__
  26. wrapper.__dict__ = f.__dict__
  27. wrapper.__doc__ = f.__doc__
  28. return wrapper
  29.  
  30. @transaction
  31. def upgrade():
  32. pass
  33.  
  34. @transaction
  35. def downgrade():
  36. pass
  37.  

I fill in the upgrade and downgrade functions and I'm done :)

I include the decorator in every script as it's good practice to make your scripts as independent as possible. If you imported it from a module you may improve it in the future and inadvertently break your old downgrade scripts.

I hope this helps you version control your database schema and data.

8 Comments to SQLAlchemy-Migrate upgrade scripts in a transaction

  1. Sacha Varma's Gravatar Sacha Varma
    July 28, 2008 at 9:55 am | Permalink

    A couple of questions:

    1. I can see that this would work when upgrading the database one revision at a time, but if sqlalchemy-migrate applies multiple updates in sequence, you’d probably want a transaction around the whole sequence? (i.e. I reckon sqlalchemy-migrate should do the rollback management)

    2. How do you manage updates that involve not just database changes, but also template & code changes? Is there a ‘turbogears-migrate’ that incorporates sqlalchemy-migrate?

  2. Sacha Varma's Gravatar Sacha Varma
    July 28, 2008 at 10:21 am | Permalink

    If you have an old model.py (A) and a new model.py (B), do you have to write the code to upgrade from A to B, or is there a tool that “diffs” the two and spits out the code to do the upgrade/downgrade?

  3. Sacha Varma's Gravatar Sacha Varma
    July 28, 2008 at 11:32 am | Permalink

    Python nub questions:

    #!/usr/bin/env python
    # encoding: utf-8

    /usr/bin/env? Why not just /usr/bin/python? Is this just a way of not having to explicitly specify the location of python, and have it pick it up from your $PATH?

    The encoding line presumably relates to the source code that follows?

  4. px's Gravatar px
    February 16, 2010 at 12:37 pm | Permalink

    Is it working? I see that SQLalchemy-migrate works with autocommit after each operation. But if upgrade fails dwongrade is called. Maybe there have to be some extra configuration of sqlalchemy-migrate that I don’t know.

Leave a Reply

You can use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>