MyTetra Share
Делитесь знаниями!
Миграция схемы БД при помощи Flask-Migrate
Время создания: 14.01.2018 12:21
Автор: br0ke
Текстовые метки: python, flask, flask-migrate, database, sqlalchemy, db, sqlite, workaround
Раздел: Информационные технологии - Python - Библиотеки - Flask

Flask-Migrate -- это расширение, которое обрабатывает миграции базы данных SQLAlchemy для приложений Flask, используя Alembic. По сути, это просто обёртка над Alembic для удобного использования с Flask. Работает подобно миграциям в Django: ты обновляешь свою модель в коде, запускаешь команду 'migrate', она генерирует скрипт перехода между предыдущим состоянием и следущим. После этого можно спокойно переходить между состояниями базы данных. Эти состояния можно хранить в системе контроля версий вместе с кодом приложения.


Установка:

pip install Flask-Migrate


Использование:

# manage.py


from flask_script import Manager

from flask_migrate import Migrate, MigrateCommand

import os


from project import app, db

from random import choice

from project.models.company import Company

from project.models.user import User



migrate = Migrate(app, db)

manager = Manager(app)


manager.add_command('db', MigrateCommand)



# Создать все таблицы в БД

@manager.command

def db_create():

# create all the database and all the db tables

db.create_all()


# insert companies

Companies = [Company('Test Company #{}'.format(i)) for i in range(3)]

for obj in Companies:

db.session.add(obj)


# insert users

Users = [User('admin{}'.format(i), 'admin{}'.format(i), '') for i in range(5)]

for obj in Users:

db.session.add(obj)


# commit the changes

db.session.commit()



if __name__ == '__main__':

manager.run()


До начала использования:

python manage.py db init


При обновлении модели данных (например, классов User или Company):

python manage.py db migrate

python manage.py db upgrade


Если нужно вернуться к предыдущему состоянию:

python manage.py db downgrade


Папку migrations нужно добавить в систему контроля версий. Скрипты "перехода" между состояниями хранятся в migrations/versions.


Известная проблема (изменение столбцов в SQLite):

При работе с СУБД SQLite не поддерживается удаление или переименование столбцов. Это обусловлено ограничениями самой СУБД (не предусмотрены команды изменения структуры уже созданной таблицы). Как с этим бороться:


1. Если уже изменили модель, выполнили migrate и получили ошибку, то нужно сделать следующее:

1.1. Вручную привести БД в соответствие с внесенными изменениями (удалить таблицу и создать заново с измененными столбцами);

1.2. Вручную сменить текущую версию БД:

1.2.1. Найти самый новый файл в migrations/versions. Скопировать его имя без расширения *.py.

1.2.2. Выполнить следущую команду, где вместо HEAD подставить имя файла из предыдущего пункта:

manage.py db stamp HEAD

1.3. Выполнить пункт 2.

2. В одной из недавних версий Alembic появился фикс для этой проблемы, но он пока что ещё не используется в Flask-Migrate. Называется batch-mode. В этом режиме будут работать и удаления, и переименования столбцов в SQLite. Для его применения нужно отредактировать файл migrations/env.py cледующим образом (изменения выделены жирным шрифтом):


from __future__ import with_statement

from alembic import context

from sqlalchemy import engine_from_config, pool

from logging.config import fileConfig

import logging


# this is the Alembic Config object, which provides

# access to the values within the .ini file in use.

config = context.config


# Interpret the config file for Python logging.

# This line sets up loggers basically.

fileConfig(config.config_file_name)

logger = logging.getLogger('alembic.env')


# add your model's MetaData object here

# for 'autogenerate' support

# from myapp import mymodel

# target_metadata = mymodel.Base.metadata

from flask import current_app

config.set_main_option('sqlalchemy.url',

current_app.config.get('SQLALCHEMY_DATABASE_URI'))

target_metadata = current_app.extensions['migrate'].db.metadata


# other values from the config, defined by the needs of env.py,

# can be acquired:

# my_important_option = config.get_main_option("my_important_option")

# ... etc.



def run_migrations_offline():

"""Run migrations in 'offline' mode.


This configures the context with just a URL

and not an Engine, though an Engine is acceptable

here as well. By skipping the Engine creation

we don't even need a DBAPI to be available.


Calls to context.execute() here emit the given string to the

script output.


"""

url = config.get_main_option("sqlalchemy.url")

context.configure(url=url)


with context.begin_transaction():

context.run_migrations()



def run_migrations_online():

"""Run migrations in 'online' mode.


In this scenario we need to create an Engine

and associate a connection with the context.


"""


# this callback is used to prevent an auto-migration from being generated

# when there are no changes to the schema

# reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html

def process_revision_directives(context, revision, directives):

if getattr(config.cmd_opts, 'autogenerate', False):

script = directives[0]

if script.upgrade_ops.is_empty():

directives[:] = []

logger.info('No changes in schema detected.')


engine = engine_from_config(config.get_section(config.config_ini_section),

prefix='sqlalchemy.',

poolclass=pool.NullPool)


connection = engine.connect()

context.configure(connection=connection,

target_metadata=target_metadata,

render_as_batch=config.get_main_option('sqlalchemy.url').startswith('sqlite:///'),

process_revision_directives=process_revision_directives,

**current_app.extensions['migrate'].configure_args)


try:

with context.begin_transaction():

context.run_migrations()

finally:

connection.close()



if context.is_offline_mode():

run_migrations_offline()

else:

run_migrations_online()


3. Стоит задуматься о смене СУБД на что-то более надежное и функциональное, например, PostgreSQL или MariaDB.


Ссылки:

https://github.com/miguelgrinberg/Flask-Migrate/issues/17

https://github.com/miguelgrinberg/Flask-Migrate/issues/61

 
MyTetra Share v.0.53
Яндекс индекс цитирования