Created On: March 15, 2021, Last Updated On: March 19, 2021
By Ozzie Ghani3 new
Lets talk about what database migration is and why is it essential to use them. Wikipedia describes database migration as the management of incremental reversible to a relational database.
Suppose you've deployed your application in Prod and now you have thousands of users signed up. A Few months later you are in need to introduce an incremental upgrade which requires adding a column to one of your database model and also requires adding it to SQL-Alchemy model. At this point we cannot delete and re-create database tables as it will delete all user's data. This is where database migration tools fit in and they're not exclusive to Flask and SQL-Alchemy. Other frameworks such as Ruby on Rails support database migration too.
Alembic works very well with SQL-Alchemy and will allow us to modify our existing database without having to clear out old data. This tool allows us to both apply and revert changes.
https://alembic.sqlalchemy.org/en/latest/
pip install alembic
Suppose we are in need to add a new table to our existing database and model looks like following
class Fib(ResourceMixin, db.Model):
__tablename__ = 'fibtable'
id = db.Column(db.Integer, primary_key=True)
fib_index = db.Column(db.Integer, nullable=False, default=0)
fib_value = db.Column(db.Integer, nullable=False, default=0)
def __init__(self, fib_index, fib_value):
self.fib_index = fib_index
self.fib_value = fib_value
@classmethod
def get_fib_values(cls, limit):
r = Fib.query.with_entities(
Fib.fib_index, Fib.fib_value).order_by(
Fib.id.desc()).limit(limit).all()
return r
In order for Alembic to perform this upgrade, Alembic must revise and create a version file. This version file will hold set of instructions to either upgrade or downgrade the change.
Following command can be run to create a version file.
docker exec <container_name> alembic revision -m "Create foo table"
This command will create a version file with ".py" extension within migrations/versions folder of the root folder of your application. The output of the command will provide the absolute path of the revision file create.
Following is the example of revision file
import sqlalchemy as sa
from alembic import op
from webapp.lib.util_datetime import tzware_datetime
from webapp.lib.util_sqlalchemy import AwareDateTime
"""
create foo table
Revision ID: bb73aa30e9cb
Revises:
Create Date: 2020-08-30 06:25:51.995563
"""
# Revision identifiers, used by Alembic.
revision = 'bb73aa30e9cb'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
pass
def downgrade():
pass
At this point only revision file has been created but database has not yet been changed. There are few things to note here.
We can now manually edit the upgrade function and provide set of instruction it require to perform upgrade.
def upgrade():
op.create_table('fibtable',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('created_on', AwareDateTime(),
default=tzware_datetime),
sa.Column('updated_on', AwareDateTime(),
default=tzware_datetime, onupdate=tzware_datetime),
sa.Column('fib_index', sa.Integer, nullable=False, default=0),
sa.Column('fib_value', sa.Integer, nullable=False, default=0)
)
def downgrade():
op.drop_table('foos')
With instructions updated in revision file, we can now perform upgrade with following command
docker exec <container_name> alembic upgrade head
In case if changes must be reverted, following command can be run
docker exec <container_name> alembic downgrade -1
In this example we will add a new column 'title' to existing table.
class Fib(ResourceMixin, db.Model):
__tablename__ = 'fibtable'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(512), nullable=True, server_default='')
fib_index = db.Column(db.Integer, nullable=False, default=0)
fib_value = db.Column(db.Integer, nullable=False, default=0)
Run following command to create a revision file
docker exec <container_name> alembic revision -m "Adding new column `title` to fibtable"
Lets examine revision file
import sqlalchemy as sa
from alembic import op
from lib.util_datetime import tzware_datetime
from lib.util_sqlalchemy import AwareDateTime
"""
added a new column for title
Revision ID: 641f5c82c88e
Revises: bb73aa30e9cb
Create Date: 2020-09-16 06:34:20.479903
"""
# Revision identifiers, used by Alembic.
revision = '641f5c82c88e'
down_revision = 'bb73aa30e9cb'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('fibtable',
sa.Column('title', sa.String(length=512), server_default='', nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('fibtable', 'title')
# ### end Alembic commands ###
Lets perform upgrade
docker exec <container_name> alembic upgrade head
docker exec <container_name> alembic revision -m "Adding new column `title` to fibtable"
docker exec <container_name> alembic revision --autogenerate -m "Adding new column `title` to fibtable"
docker exec <container_name> alembic upgrade head
docker exec <container_name> alembic downgrade -1
docker exec <container_name> alembic current
docker exec <container_name> alembic history --verbos