Как управлять схемой SQLite в Python: гайд по миграциям с sqlite-utils 4.0
Узнайте, как реализовать систему миграций схем SQLite в Python с помощью sqlite-utils 4.0. Пошаговый гайд для разработчиков.

Раньше, чтобы обновить схему базы данных SQLite, приходилось писать кучу ручного кода, что приводило к ошибкам и сложностям в поддержке. С выходом sqlite-utils версии 4.0 эта проблема решена: теперь у вас есть полноценная, управляемая система миграций.
Зачем нужна система миграций (и почему на ней нельзя экономить)
Если вы пишете приложение, которое использует SQLite для хранения данных (например, локальный кэш, или базу для MVP), то со временем структура данных (схема) обязательно изменится. Добавится столбец, поменяется тип поля, или потребуется новая таблица.
Без системы миграций (migrations) вам придется вручную прописывать логику обновления схемы в коде, которая будет запускаться каждый раз при старте программы. Это не масштабируемо и очень хрупко.
Система миграций решает эту проблему, предоставляя стандартизированный механизм:
- Определяете все изменения схемы в виде отдельных, изолированных файлов (миграций).
- Инструмент сам отслеживает, какие изменения уже были применены к базе.
- При запуске он выполняет только те миграции, которые еще не были применены.
Как настроить миграции: пошаговый запуск
В основе нового функционала лежит класс Migrations из библиотеки sqlite-utils. Он позволяет вам писать изменения схемы как обычный код Python, но с гарантией, что эти изменения будут применены последовательно и надежно.
Предположим, у нас есть база данных data.db, и мы хотим, чтобы она прошла три этапа эволюции: создание таблицы, добавление поля, и изменение типов.
Шаг 1. Создаем файл миграций
Создайте файл, например, migrations.py. В нем мы определим последовательность изменений, используя декоратор @migrations().
В этом примере мы создадим таблицу creatures, добавим в нее weight, и изменим типы species и weight.
```python
migrations.py
from sqlite_utils import Migrations
Инициализируем класс Migrations для конкретной схемы
migrations = Migrations("creatures")
@migrations() def create_table(db): # 1. Создание основной таблицы db["creatures"].create({"id": "INTEGER PRIMARY KEY", "name": "TEXT", "species": "TEXT"})
@migrations() def add_weight(db): # 2. Добавляем столбец 'weight' типа float db["creatures"].add_column("weight", "REAL")
@migrations() def change_column_types(db): # 3. Изменяем типы данных для 'species' и 'weight' # Используется метод transform(), который реализует сложный ALTER TABLE db["creatures"].transform(types={"species": "INTEGER", "weight": "TEXT"}) ```
Что здесь важно:
sqlite-utilsиспользует методtransform()для выполнения сложных операций изменения схемы, которые стандартный SQLALTER TABLEне поддерживает. Он сам реализует паттерн: создание временной таблицы, копирование данных, удаление старой и переименование.- Система сама отслеживает, что каждая функция (
create_table,add_weightи т.д.) является отдельной миграцией.
Шаг 2. Запуск миграций
Теперь, когда у вас есть база данных data.db и файл migrations.py, вам нужно запустить миграции через CLI.
В терминале выполните команду:
``bash uvx sqlite-utils migrate data.db migrations.py ``
Что произойдет:
- Инструмент проверит базу данных на наличие специальной таблицы
_sqlite_migrations. - Он увидит, какие миграции из
migrations.pyеще не применены. - Он выполнит их в строгом порядке (по порядку определения в коде).
- После успешного выполнения каждая миграция будет зафиксирована в таблице
_sqlite_migrations.
Шаг 3. Проверка состояния
Чтобы убедиться, что все прошло успешно, можно проверить схему и список миграций.
Проверка схемы: ``bash uvx sqlite-utils schema data.db ` Вы увидите, что таблица creatures имеет схему, отражающую все три шага: id, name, species (теперь INTEGER) и weight` (теперь TEXT).
Проверка статуса миграций: ``bash uvx sqlite-utils migrate data.db migrations.py --list `` Вы получите список:
- Applied: Перечислены все три миграции с датами их применения.
- Pending: Будет пусто, если все успешно применилось.
Отличия от ORM-миграций (Почему это круче для SQLite)
Многие разработчики привыкли к паттернам миграций, которые работают с ORM (например, Django). В Django вы описываете модель, и система сама генерирует миграции.
sqlite-utils сознательно делает акцент на программном создании таблиц, а не на ORM. Это значит, что:
- Вы не можете автоматически сгенерировать миграцию из определения модели — вы пишете миграцию.
- Это дает вам максимальный контроль, что критично при работе с низкоуровневыми базами данных, как SQLite.
Поэтому, если ваша задача — управлять схемой, основываясь на бизнес-логике, а не на абстрактной модели, этот подход идеален.
Подводные камни и что попробовать дальше
⚠️ Подводный камень 1: Откат (Rollback)
В отличие от Django, sqlite-utils намеренно упрощает систему, и функция отката (rollback) отсутствует. Это не слабость, а особенность: при работе с SQLite, самый надежный способ — это просто сделать копию файла базы данных перед запуском миграций.
⚠️ Подводный камень 2: Автоматическое обнаружение
Если вы не указываете файл миграций, команда sqlite-utils migrate data.db попытается найти все файлы с именем migrations.py в текущей директории и всех поддиректориях. Убедитесь, что у вас нет лишних файлов, которые могут вызвать конфликты.
Что попробовать дальше
- Интеграция в код: Вы можете не вызывать миграции через CLI, а реализовать логику запуска внутри своего Python-приложения, используя метод
migrations.apply(db). Это полезно, если вы строите инструмент, который сам должен управлять своей базой данных. - Вложенные транзакции: Версия 4.0 также добавила поддержку вложенных транзакций через метод
db.atomic(). Если вам нужно, чтобы несколько шагов миграции выполнялись как единая атомарная операция (либо все успешно, либо ничего не меняется), используйте этот метод.
В итоге, sqlite-utils 4.0 дает вам не просто инструмент, а полноценный, надежный цикл разработки: вы меняете схему в коде, запускаете миграцию, и ваш продакшн-код всегда работает с ожидаемой структурой данных.