Перейти к содержимому

Практика 12. Практическая работа 12. Миграции схемы через Alembic

Раздел 3. Работа с данными в backend-приложениях. Длительность: ~2 академических часа

Цель

  1. Установить и инициализировать Alembic в проекте с SQLAlchemy.
  2. Настроить alembic.ini и env.py на метаданные моделей проекта.
  3. Сгенерировать первую миграцию через revision --autogenerate.
  4. Научиться применять (upgrade head) и откатывать (downgrade) миграции.
  5. Изменить модель (добавить столбец) и создать новую миграцию.
  6. Просматривать историю миграций и текущую версию схемы БД.

Теория

Миграция — это версионированный скрипт изменения схемы БД. Рабочую базу с данными нельзя пересоздать через 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:

models.py
from sqlalchemy import Column, Integer, String, Numeric, create_engine
from 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 sqlalchemy
alembic 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:

alembic.ini
sqlalchemy.url = sqlite:///./app.db

2) Метаданные моделей. В файле alembic/env.py подключите Base.metadata — это нужно, чтобы работал --autogenerate:

# alembic/env.py (фрагмент)
from models import Base # импортируем наши модели
# было: target_metadata = None
target_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 op
import 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 -1
alembic 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 op
import 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 head
alembic current # b2c3d4e5f6a7 (head)

Задание 6. История и текущая версия схемы

Просмотрите всю цепочку миграций, затем откатитесь до первой версии по её идентификатору (столбец in_stock пропадёт, таблица books останется) и верните всё:

Окно терминала
alembic history --verbose # вся цепочка миграций
alembic current # текущая применённая версия
alembic heads # самые свежие версии (концы цепочек)
alembic downgrade a1b2c3d4e5f6 # откат только миграции с in_stock
alembic current # a1b2c3d4e5f6
alembic 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%.


Вопросы для самопроверки

  1. Зачем нужны миграции, если схему можно создать через Base.metadata.create_all()?
  2. Что создают команды alembic init alembic — какие файлы и каталоги?
  3. Где указывается строка подключения к БД и куда подключают Base.metadata?
  4. Что делает alembic revision --autogenerate и почему результат проверяют вручную?
  5. За что отвечают поля revision и down_revision в файле миграции?
  6. Чем отличаются функции upgrade() и downgrade()?
  7. В чём разница между alembic upgrade head и alembic downgrade -1?
  8. В какой таблице Alembic хранит текущую версию схемы?
  9. Почему для нового NOT NULL-столбца в таблице с данными задают server_default?
  10. Чем отличается вывод команд alembic current, alembic history и alembic heads?