Практика 12. Практическая работа 12. Миграции схемы через Alembic
Раздел 3. Работа с данными в backend-приложениях. Длительность: ~2 академических часа
Цель
- Установить и инициализировать Alembic в проекте с SQLAlchemy.
- Настроить
alembic.iniиenv.pyна метаданные моделей проекта. - Сгенерировать первую миграцию через
revision --autogenerate. - Научиться применять (
upgrade head) и откатывать (downgrade) миграции. - Изменить модель (добавить столбец) и создать новую миграцию.
- Просматривать историю миграций и текущую версию схемы БД.
Теория
Миграция — это версионированный скрипт изменения схемы БД. Рабочую базу с данными
нельзя пересоздать через Base.metadata.create_all() без потери данных, поэтому схему
меняют эволюционно: каждое изменение оформляется отдельной миграцией и хранится в
репозитории рядом с кодом.
Alembic — официальный инструмент миграций от автора SQLAlchemy. Он сравнивает модели
(target_metadata) с текущей схемой БД и автоматически генерирует скрипт изменений.
Ключевые элементы каждой миграции: revision (id версии), down_revision (ссылка на
предыдущую миграцию — так выстраивается цепочка), upgrade() (применить) и downgrade()
(откатить). Текущую версию схемы Alembic хранит в служебной таблице alembic_version
внутри самой БД, поэтому всегда «знает», какие миграции уже применены.
Основные команды:
| Команда | Назначение |
|---|---|
alembic init alembic | создать каталог миграций и alembic.ini |
alembic revision --autogenerate -m "msg" | сгенерировать миграцию по моделям |
alembic upgrade head | применить все миграции до самой свежей |
alembic downgrade -1 | откатить одну миграцию назад |
alembic downgrade base | откатить все миграции |
alembic current | показать текущую версию БД |
alembic history | показать всю цепочку миграций |
Важно: результат
autogenerateвсегда просматривают вручную — он не всегда улавливает переименования столбцов и сложные изменения.
Задание
Подготовка: модели и engine
Будем работать с сервисом «Книги», но теперь с настоящей БД (SQLite).
Создайте файл models.py:
from sqlalchemy import Column, Integer, String, Numeric, create_enginefrom sqlalchemy.orm import declarative_base
engine = create_engine("sqlite:///./app.db", echo=False)Base = declarative_base()
class Book(Base): __tablename__ = "books"
id = Column(Integer, primary_key=True) title = Column(String(200), nullable=False) author = Column(String(100), nullable=False) year = Column(Integer, nullable=False) price = Column(Numeric(10, 2), nullable=False)Обратите внимание: мы не вызываем
Base.metadata.create_all(). Таблицы создаст Alembic — в этом и смысл миграций.
Задание 1. Установка и инициализация Alembic
В активированном виртуальном окружении проекта выполните:
pip install alembic sqlalchemyalembic init alembicКоманда создаст:
- каталог
alembic/(внутри —env.py,script.py.mako, папкаversions/); - файл конфигурации
alembic.iniв корне проекта.
Проверьте структуру проекта:
ls alembic# env.py README script.py.mako versionsЗадание 2. Настройка alembic.ini и env.py
1) Строка подключения. В файле alembic.ini найдите и задайте sqlalchemy.url:
sqlalchemy.url = sqlite:///./app.db2) Метаданные моделей. В файле alembic/env.py подключите Base.metadata —
это нужно, чтобы работал --autogenerate:
# alembic/env.py (фрагмент)from models import Base # импортируем наши модели
# было: target_metadata = Nonetarget_metadata = Base.metadata # стало: метаданные всех моделейЕсли импорт
from models import Baseне находится, добавьте в началоenv.py:import sys, os; sys.path.append(os.getcwd()).
Задание 3. Автогенерация первой миграции
Сгенерируйте миграцию для создания таблицы books:
alembic revision --autogenerate -m "create books table"В каталоге alembic/versions/ появится файл вида a1b2c3d4e5f6_create_books_table.py.
Откройте его и проверьте сгенерированный код — он должен выглядеть примерно так:
"""create books table"""from alembic import opimport sqlalchemy as sa
revision = "a1b2c3d4e5f6"down_revision = None # это первая миграция, предыдущей нет
def upgrade(): op.create_table( "books", sa.Column("id", sa.Integer(), nullable=False), sa.Column("title", sa.String(length=200), nullable=False), sa.Column("author", sa.String(length=100), nullable=False), sa.Column("year", sa.Integer(), nullable=False), sa.Column("price", sa.Numeric(precision=10, scale=2), nullable=False), sa.PrimaryKeyConstraint("id"), )
def downgrade(): op.drop_table("books")Убедитесь, что
down_revision = None— это маркер первой миграции в цепочке.
Задание 4. Применение и откат миграции
Применить миграцию (создать таблицу в БД):
alembic upgrade headПосле выполнения в файле app.db появятся таблицы books и служебная alembic_version.
Проверьте текущую версию:
alembic current# a1b2c3d4e5f6 (head)Откатить миграцию на одну назад (таблица books удалится):
alembic downgrade -1alembic current # вывод пуст — версии нет, схема откатилась к базовойСнова накатите её, чтобы продолжить работу:
alembic upgrade head
downgrade baseоткатит все миграции до пустого состояния,upgrade head— накатит все до самой свежей.
Задание 5. Изменение модели и новая миграция
Добавьте в модель Book новый столбец in_stock (наличие книги).
# models.py — внутри класса Book добавьте строку: in_stock = Column(Boolean, nullable=False, server_default=sa.text("1"))Для нового NOT NULL-столбца в таблице с данными важно задать
server_default, иначе уже существующие строки нечем заполнить. Добавьтеimport sqlalchemy as saв началоmodels.py.
Сгенерируйте новую миграцию:
alembic revision --autogenerate -m "add in_stock to books"Проверьте полученный файл alembic/versions/...add_in_stock_to_books.py:
"""add in_stock to books"""from alembic import opimport sqlalchemy as sa
revision = "b2c3d4e5f6a7"down_revision = "a1b2c3d4e5f6" # ссылка на предыдущую миграцию — цепочка
def upgrade(): op.add_column( "books", sa.Column("in_stock", sa.Boolean(), nullable=False, server_default=sa.text("1")), )
def downgrade(): op.drop_column("books", "in_stock")Примените изменение:
alembic upgrade headalembic current # b2c3d4e5f6a7 (head)Задание 6. История и текущая версия схемы
Просмотрите всю цепочку миграций, затем откатитесь до первой версии по её
идентификатору (столбец in_stock пропадёт, таблица books останется) и верните всё:
alembic history --verbose # вся цепочка миграцийalembic current # текущая применённая версияalembic heads # самые свежие версии (концы цепочек)
alembic downgrade a1b2c3d4e5f6 # откат только миграции с in_stockalembic current # a1b2c3d4e5f6alembic upgrade head # вернуть всё обратноСравните
alembic historyдо и послеupgrade/downgrade— сама история (файлы миграций) не меняется, меняется лишь текущая версия БД.
Критерии оценки
| Критерий | Баллы |
|---|---|
Alembic установлен, выполнен alembic init, структура каталога корректна (Задание 1) | 15% |
alembic.ini и env.py настроены на Base.metadata, autogenerate работает (Задание 2) | 20% |
| Сгенерирована и проверена первая миграция создания таблицы (Задание 3) | 15% |
Корректно выполнены upgrade head и downgrade, версия проверена через current (Задание 4) | 20% |
| Добавлен новый столбец, сгенерирована и применена вторая миграция (Задание 5) | 20% |
Просмотрены history/current/heads, выполнен откат по идентификатору (Задание 6) | 10% |
Итого: 100%. Работа засчитывается при наборе не менее 60%.
Вопросы для самопроверки
- Зачем нужны миграции, если схему можно создать через
Base.metadata.create_all()? - Что создают команды
alembic init alembic— какие файлы и каталоги? - Где указывается строка подключения к БД и куда подключают
Base.metadata? - Что делает
alembic revision --autogenerateи почему результат проверяют вручную? - За что отвечают поля
revisionиdown_revisionв файле миграции? - Чем отличаются функции
upgrade()иdowngrade()? - В чём разница между
alembic upgrade headиalembic downgrade -1? - В какой таблице Alembic хранит текущую версию схемы?
- Почему для нового NOT NULL-столбца в таблице с данными задают
server_default? - Чем отличается вывод команд
alembic current,alembic historyиalembic heads?