def migrate_db(self, ctx, config, from_tree, to_tree): def alembic(against, *args, hide='out'): alembic_path = Path(against) / 'src' with cd(str(alembic_path)): return run('alembic -x url={dsn} '.format(dsn=database) + ' '.join(args), hide=hide) database = ctx.qabel.block.psql_dsn if from_tree: current_commit = get_tree_commit(from_tree) to_commit = get_tree_commit(to_tree) # We are upgrading if the current current commit is an ancestor (precedes) the commit we are deploying. # Otherwise it's an downgrade. This is important because we need to use the correct alembic folder # which must hold the more recent revision. Alembic also needs to know whether it's an upgrade or # downgrade, it can't figure this out on it's own. upgrading = commit_is_ancestor(current_commit, to_commit) # map "abcdef (head)" to just "abcdef". Empty output means no DB state ("None") current_revision = alembic(from_tree, 'current').stdout.strip().split(' ')[0] or None else: upgrading = True current_revision = None # map "Rev: 129sdjsakdasd (head)" (+ extra lines) to just "129sdjsakdasd" to_revision = alembic(to_tree, 'history -vr +0:').stdout.split('\n')[0].split()[1] if current_revision == to_revision: print('No database migration required (alembic revision {rev})'.format(rev=current_revision)) return print('Current database is at alembic revision', current_revision) print('Migrating to alembic revision', to_revision) alembic_verb = 'upgrade' if upgrading else 'downgrade' against = to_tree if upgrading else from_tree alembic(against, alembic_verb, to_revision, hide=None)
def migrate_db(self, ctx, config, from_tree, to_tree): def manage_py(against, args, hide='out'): return manage_command(against, config, args, hide=hide) def get_migrations(tree): # showmigrations output looks like this: # <app label> # [X] <migration name> <-- applied migration # [ ] <migration> <-- not applied line_prefix_length = len(' [X] ') all_migrations = manage_py(tree, 'showmigrations').stdout.splitlines() app_migrations = {} while all_migrations: current_app = all_migrations.pop(0) migrations = [] while all_migrations and all_migrations[0].startswith(' ['): migrations.append(all_migrations.pop(0)) # migrations is now a list of " [x] nnnn_...", find last applied and the last overall migration last_migration = migrations[-1][line_prefix_length:] applied_migrations = filter(lambda m: m.startswith(' [X] '), migrations) last_applied_migration = None for last_applied_migration in applied_migrations: pass if last_applied_migration: last_applied_migration = last_applied_migration[line_prefix_length:] app_migrations[current_app] = last_applied_migration, last_migration return app_migrations if from_tree: current_commit = get_tree_commit(from_tree) to_commit = get_tree_commit(to_tree) # We are upgrading if the current current commit is an ancestor (precedes) the commit we are deploying. # Otherwise it's an downgrade. This is important because we need to use the tree which has the most currently # applied migration when downgrading, since the downgraded-to-tree cannot undo migrations it doesn't know. upgrading = commit_is_ancestor(current_commit, to_commit) else: # If we have no old tree it's always a straight upgrade of everything upgrading = True print('Database is being', 'upgraded' if upgrading else 'downgraded') if upgrading: manage_py(to_tree, 'migrate') else: if from_tree: from_migrations = get_migrations(from_tree) else: from_migrations = {} to_migrations = get_migrations(to_tree) not_migrated = [] for application, (_, target_migration) in to_migrations.items(): try: applied_migration, _ = from_migrations[application] except KeyError: applied_migration = '<None/Unknown>' if applied_migration == target_migration: not_migrated.append(application) continue print('Migrating \'{app}\': {applied} -> {target}'.format(app=application, applied=applied_migration, target=target_migration)) if upgrading: against = to_tree else: against = from_tree manage_py(against, 'migrate {app} {target}'.format(app=application, target=target_migration)) print('No migrations needed for:', ', '.join(not_migrated))