{"id":4891,"date":"2013-07-02T15:58:07","date_gmt":"2013-07-02T23:58:07","guid":{"rendered":"http:\/\/www.chesnok.com\/daily\/?p=4891"},"modified":"2014-02-10T13:17:43","modified_gmt":"2014-02-10T21:17:43","slug":"a-practical-guide-to-using-alembic","status":"publish","type":"post","link":"https:\/\/www.chesnok.com\/daily\/2013\/07\/02\/a-practical-guide-to-using-alembic\/","title":{"rendered":"A practical guide to using Alembic"},"content":{"rendered":"<p><em>I spent some time guiding a coworker through using <a href=\"http:\/\/alembic.readthedocs.org\">Alembic<\/a> for the first time with <a href=\"http:\/\/github.com\/mozilla\/socorro\">Socorro<\/a> this morning and what follows are my notes from that meeting.<\/em><\/p>\n<p>I&#8217;ve been using Alembic, a <a href=\"http:\/\/en.wikipedia.org\/wiki\/Schema_migration\">database schema migration<\/a> tool, for about three months now, and really liking it a lot. I created a blog post that served as a slide deck for an internal team called <a href=\"http:\/\/www.chesnok.com\/daily\/2013\/05\/17\/migrations-with-alembic-a-lightspeed-tour\/\">A lightspeed tour of Alembic<\/a> as my first stab at user education.<\/p>\n<p>Setting things up initially was pretty simple, but explaining it to a coworker after I&#8217;d set everything up for myself proved slightly more difficult. Below are my notes on the differences between <a href=\"http:\/\/alembic.readthedocs.org\">Alembic<\/a> and some other migration tool.<\/p>\n<h2>Terminology<\/h2>\n<p>Alembic calls each migration a <em>revision<\/em>. Revisions know what order to be run in because each revision is given a <code>down_revision<\/code> to identify its parent. If <code>down_revision<\/code> is <code>None<\/code>, that revision is the very first revision according to Alembic. You can put your whole schema in that revision, or you can just start adding changes to this initial revision. Alembic doesn&#8217;t complain either way.<\/p>\n<p>A best practice would likely be putting your entire model into the first revision. I may go back and &#8220;fix&#8221; this for us later. I opted to just have the default use case be to create a database fresh with a tool we call <code>setupdb_app.py<\/code>.<\/p>\n<p>If you&#8217;re looking to migrate to using alembic, you&#8217;ll also need to use SQLAlchemy. I used sqlautocode for my initial schema <a href=\"http:\/\/docs.sqlalchemy.org\/en\/latest\/core\/schema.html#reflecting-database-objects\">reflection<\/a>, and there&#8217;s a new tool <a href=\"https:\/\/bitbucket.org\/agronholm\/sqlacodegen\/src\">sqlacodegen<\/a> you may want to check out for generating your SQLAlchemy models for the first time.<\/p>\n<h2>Preparation: edit config and activate a virtualenv<\/h2>\n<p>Our environment was set up per the <a href=\"https:\/\/alembic.readthedocs.org\/en\/latest\/tutorial.html#creating-an-environment\">alembic tutorial for creating an environment<\/a>. I ran:<\/p>\n<p><code>alembic init alembic<\/code><\/p>\n<p>I also put an <code>alembic.ini-dist<\/code> file into our project&#8217;s <code>config\/<\/code> directory, and modified <code>alembic\/env.py<\/code> to include our model.<\/p>\n<p>To get started working with an existing install, you&#8217;ll need to modify <code>alembic.ini-dist<\/code>, and copy it to <code>config\/alembic.ini<\/code> to fit your environment &#8211; setting the connection string and the path to the alembic directory are the two most important settings. We have a script which creates databases from our <code>models.py<\/code> called <code>setupdb_app.py<\/code>. This script takes <code>--database_name<\/code> as a command-line argument. My default for our project is to use <code>breakpad<\/code>.<\/p>\n<p>We use a <code>virtualenv<\/code> called <code>socorro-virtualenv<\/code>. The virtualenv is created automatically if you run <code>make test<\/code>. If you&#8217;re creating a standalone virtualenv, you can do that with <code>virtualenv socorro-virtualenv<\/code>. Activate this with <code>. socorro-virtualenv\/bin\/activate<\/code>.<\/p>\n<h1>Creating a revision<\/h1>\n<ol>\n<li>Create a fresh database to work from. For Socorro, the command is: <code>PYTHONPATH=. socorro\/external\/postgresql\/setupdb_app.py --database_name=breakpad<\/code><\/li>\n<li>Edit <code>models.py<\/code> with the change to the schema<\/li>\n<li>Run: <code>PYTHONPATH=. alembic -c config\/alembic.ini revision -m 'your message about the migration'<\/code>. The output will include the name of the new file.<\/li>\n<li>Edit the new file as needed <code>alembic\/versions\/*.py<\/code><\/li>\n<li>Run: <code>PYTHONPATH=. alembic -c config\/alembic.ini upgrade +1<\/code><\/li>\n<li>Test your downgrade with <code>PYTHONPATH=. alembic -c config\/alembic.ini downgrade -1<\/code><\/li>\n<\/ol>\n<p>If all goes well, your revision is ready! If something goes wrong, edit and try again. The revision will automatically rollback if there are any errors.<\/p>\n<p>Downgrades are a little tricky to properly execute. In an ideal world, you&#8217;d be able to revert the underlying code, but preserve <em>only<\/em> the commit containing the migration. More on this in a future blog post!<\/p>\n<h1>Creating a revision using <code>--autogenerate<\/code><\/h1>\n<p>This is very similar to the above, with the addition of <code>--autogenerate<\/code> to your <code>revision<\/code> command. This should do the right thing, but definitely check your generated file for accuracy.<\/p>\n<ol>\n<li>Create a fresh database to work from. For Socorro, the command is: <code>PYTHONPATH=. socorro\/external\/postgresql\/setupdb_app.py --database_name=breakpad<\/code><\/li>\n<li>Edit <code>models.py<\/code> with the change to the schema<\/li>\n<li>Run: <code>PYTHONPATH=. alembic -c config\/alembic.ini revision --autogenerate -m 'your message about the migration'<\/code>. The output will include the name of the new file.<\/li>\n<li>Edit the new file as needed <code>alembic\/versions\/*.py<\/code><\/li>\n<li>Run: <code>PYTHONPATH=. alembic -c config\/alembic.ini upgrade +1<\/code><\/li>\n<li>Test your downgrade with <code>PYTHONPATH=. alembic -c config\/alembic.ini downgrade -1<\/code><\/li>\n<\/ol>\n<p>If all goes well, your revision is ready! If something goes wrong, edit and try again. The revision will automatically rollback if there are any errors.<\/p>\n<h1>Production deployment<\/h1>\n<p>You&#8217;ll need to deploy an <code>alembic.ini<\/code> on your production database system and probably a <code>virtualenv<\/code> to support your python modules.<\/p>\n<p>We deploy our virtualenvs with our application, so this step was pretty simple for everything except for alembic itself. The virtualenv put in full, static paths for the python binaries and had some dependencies that I haven&#8217;t figured out yet for actually running alembic. To get around this, I created a virualenv locally on the system for the <em>postgres<\/em> user. Having your postgres user run the migrations locally is a must for me because I need to access the filesystem to pull in new versions of user defined functions stashed in the directory my model lives in.<\/p>\n<p>I just deploy a new release of our application code on the database server locally, and then I run alembic against the <code>versions<\/code> directory that&#8217;s deployed.<\/p>\n<h1>FAQ<\/h1>\n<p>And here&#8217;s an FAQ for the common problems folks ran into:<\/p>\n<h2>OOPS I forgot to create a database before I created a revision!<\/h2>\n<p>To &#8220;fix&#8221; this, try:<\/p>\n<ol>\n<li>Create the database from scratch using your current <code>models.py<\/code>.<\/li>\n<li>Run: <code>PYTHONPATH=. alembic -c config\/alembic.ini downgrade -1<\/code><\/li>\n<li>Run: <code>PYTHONPATH=. alembic -c config\/alembic.ini upgrade +1<\/code><\/li>\n<\/ol>\n<p>Assuming your downgrade function works, this should allow you reverse the latest revision and then test your migration.<\/p>\n<h2>Error message: &#8220;Only a single head supported so far.&#8221;<\/h2>\n<p>See <a href=\"https:\/\/alembic.readthedocs.org\/en\/latest\/tutorial.html#working-with-branches\">Working with Branches<\/a>.<\/p>\n<h2>I&#8217;m using schemas, and alembic doesn&#8217;t recognize them when I try to use <code>--autogenerate<\/code>.<\/h2>\n<p>See <a href=\"http:\/\/alembic.readthedocs.org\/en\/latest\/api.html?highlight=include_symbol\">include_symbol<\/a>. And be sure to add this to both the &#8220;offline&#8221; and &#8220;online&#8221; versions of the revision code in <code>env.py<\/code>.<\/p>\n<h2>Error message: Target database is not up to date.<\/h2>\n<p>This means you&#8217;ve got a file in your <code>versions<\/code> directory that contains one or more migrations that haven&#8217;t been applied to the current database. You can either apply them with <code>alembic upgrade head<\/code> or have a look in that directory and remove the migration(s) that you don&#8217;t want.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>I spent some time guiding a coworker through using Alembic for the first time with Socorro this morning and what follows are my notes from that meeting. I&#8217;ve been using Alembic, a database schema migration tool, for about three months &hellip; <a href=\"https:\/\/www.chesnok.com\/daily\/2013\/07\/02\/a-practical-guide-to-using-alembic\/\">Continue reading &rarr;<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[9,407],"tags":[598,647,624,599],"class_list":["post-4891","post","type-post","status-publish","format-standard","hentry","category-postgresql","category-python","tag-alembic","tag-postgres","tag-postgresql","tag-sqlalchemy"],"_links":{"self":[{"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/posts\/4891","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/comments?post=4891"}],"version-history":[{"count":10,"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/posts\/4891\/revisions"}],"predecessor-version":[{"id":5160,"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/posts\/4891\/revisions\/5160"}],"wp:attachment":[{"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/media?parent=4891"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/categories?post=4891"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.chesnok.com\/daily\/wp-json\/wp\/v2\/tags?post=4891"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}