Шаг 02. База данных и миграции

📁 Серия: Капстоун A ⏱️ ~30 мин 🎯 Сложность: Начальная
#flask-sqlalchemy #flask-migrate #alembic #migrations

⚡ Кратко: что делаем на этом шаге

Цель: Настроить Flask-Migrate (обёртка над Alembic) для управления версиями схемы БД. Вместо db.create_all() — полноценные миграции, которые можно откатить и отследить в git.

  • Файлы: app/__init__.py (уже есть), migrations/ (создаётся командой)
  • Команды: flask db initflask db migrate -m "init"flask db upgrade
  • Результат: папка migrations/ в git, БД создана через миграцию

🎯 Цель этапа

На этом шаге мы настраиваем Flask-Migrate — расширение, которое интегрирует Alembic (систему миграций SQLAlchemy) в Flask-приложение. После этого изменения схемы БД будут храниться в виде версионированных файлов миграций, а не применяться через разрушительный db.create_all().

💡 Ключевая идея — миграции vs create_all: db.create_all() создаёт таблицы, но не умеет их изменять. Добавили колонку в модель — create_all() ничего не сделает с уже существующей таблицей. Миграции Alembic генерируют SQL для изменения существующей схемы и хранят историю в git. Откатить можно командой flask db downgrade.

После этого шага у нас будет

  • Папка migrations/ с конфигурацией Alembic
  • Flask-Migrate инициализирован в create_app()
  • Первая «пустая» миграция (пока моделей нет — она просто инициализирует историю)
  • Понимание цикла: изменить модель → flask db migrateflask db upgrade

📄 Затрагиваемые файлы

ФайлДействиеОписание
app/__init__.pyУже естьmigrate уже инициализирован на шаге 01, проверяем импорты
migrations/Создать (командой)Директория Alembic — создаётся командой flask db init
migrations/versions/Создать (командой)Файлы версий миграций
.flaskenvСоздатьПеременные для Flask CLI (FLASK_APP)

🔨 Шаги

1. Проверяем app/__init__.py

Flask-Migrate уже подключён на шаге 01. Убеждаемся, что всё на месте:

📄 app/__init__.py (должен выглядеть так после шага 01)
# app/__init__.py
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate

from config import config_map, Config

db: SQLAlchemy = SQLAlchemy()
migrate: Migrate = Migrate()


def create_app(config_name: str = "development") -> Flask:
    app = Flask(__name__)
    config_class: type[Config] = config_map[config_name]
    app.config.from_object(config_class)

    db.init_app(app)
    migrate.init_app(app, db)   # ← Flask-Migrate инициализирован здесь

    @app.route("/health")
    def health_check():
        return jsonify({"status": "ok", "env": config_name}), 200

    return app
💡 Порядок важен: db.init_app(app) должен идти до migrate.init_app(app, db), потому что Migrate принимает уже инициализированный объект db.

2. Создаём .flaskenv для Flask CLI

📄 .flaskenv
# .flaskenv — переменные окружения для flask CLI
# Этот файл читается python-dotenv автоматически при запуске flask
FLASK_APP=run.py
APP_ENV=development
💡 Зачем .flaskenv? Flask CLI (команды flask db ...) должен знать, где находится приложение. Переменная FLASK_APP=run.py говорит Flask, какой файл импортировать. В Windows PowerShell временно задать можно: $env:FLASK_APP = "run.py" — но .flaskenv удобнее и не теряется.

3. Инициализируем директорию миграций

💻 Терминал (корень проекта, venv активирован)
flask db init

После команды появится папка migrations/:

migrations/
├── alembic.ini          # конфиг Alembic
├── env.py               # точка входа Alembic (редко трогают)
├── script.py.mako       # шаблон для генерации файлов миграций
└── versions/            # здесь будут файлы миграций
⚠️ flask db init — только один раз! Команду flask db init запускают один раз при старте проекта. Если запустить повторно — Alembic откажется создавать директорию заново. Папку migrations/ коммитят в git вместе с кодом.

4. Создаём первую миграцию

Пока моделей нет, первая миграция будет почти пустой — она просто инициализирует таблицу версий Alembic (alembic_version). На шаге 03 после добавления моделей создадим «настоящую» миграцию.

💻 Терминал
flask db migrate -m "initial migration"

Alembic сгенерирует файл в migrations/versions/ с именем вида xxxxxxxxxxxx_initial_migration.py.

5. Применяем миграцию (создаём БД)

💻 Терминал
flask db upgrade

6. Проверяем текущую версию

💻 Терминал
flask db current

🧠 Объяснение логики

Цикл работы с миграциями

  1. Изменяете модель (добавляете поле, таблицу, индекс)
  2. flask db migrate -m "add field X to Question" — Alembic сравнивает модели с текущей схемой БД и генерирует файл миграции
  3. Просматриваете сгенерированный файл в migrations/versions/ — убеждаетесь, что всё правильно
  4. flask db upgrade — применяет изменения к БД
  5. Коммитите файл миграции в git

Откат миграции

💻 Терминал
# Откат на одну версию назад
flask db downgrade

# Откат до конкретной версии
flask db downgrade <revision_id>

# Просмотр истории миграций
flask db history

db.create_all() vs Flask-Migrate

Характеристикаdb.create_all()Flask-Migrate
Создаёт таблицыДаДа
Изменяет существующиеНетДа
История измененийНетДа (в git)
ОткатНетДа (downgrade)
Подходит для prodНетДа
⚠️ Не используйте db.create_all() в production! Единственное допустимое использование db.create_all() — в тестах, где БД в памяти (sqlite:///:memory:) создаётся заново при каждом запуске. Для разработки и продакшна — только Flask-Migrate.

✅ Проверка

1. Проверяем структуру после init

💻 Терминал (PowerShell)
ls migrations

2. Проверяем текущую версию

💻 Терминал
flask db current

3. Ожидаемый результат

Успех после flask db upgrade:
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> xxxxxxxxxxxx, initial migration
Успех flask db current:
xxxxxxxxxxxx (head)
Создан файл БД: community_pulse_dev.db в корне проекта.

Диагностика: если что-то пошло не так

  • Ошибка: Error: Could not locate a Flask application — не установлена переменная FLASK_APP. Проверьте .flaskenv или выполните $env:FLASK_APP = "run.py"
  • Ошибка: Directory migrations already existsflask db init уже выполнялась. Удалите папку migrations/ или пропустите этот шаг
  • Ошибка: Target database is not up to date — сначала выполните flask db upgrade

➡️ Что дальше

На следующем шаге создаём модели Question и Response с современным синтаксисом SQLAlchemy 2.x: Mapped[T] и mapped_column(). После этого запустим flask db migrate ещё раз — и получим «настоящую» миграцию с реальными таблицами.

  • Готово: Flask-Migrate настроен, первая миграция применена, структура migrations/ в git
  • Далее (шаг 03): модели Question и Response с Mapped/mapped_column, relationship, миграция таблиц