sqlite-utils 4.0rc3: составные внешние ключи и AI-разработка
Разбор релиза sqlite-utils 4.0rc3: составные внешние ключи, регистронезависимые столбцы и использование Claude и GPT при разработке.
sqlite-utils 4.0rc3: составные внешние ключи и AI-помощники в разработке
sqlite-utils — это популярная Python-библиотека и CLI-утилита (инструмент командной строки) от Саймона Уиллисона (Simon Willison) для удобной работы с базами данных SQLite. 6 июля 2026 года вышел релиз-кандидат 4.0rc3, который оказался куда богаче изменениями, чем планировалось изначально.
Почему rc3, а не стабильная версия?
Саймон планировал выпустить стабильную версию sqlite-utils 4.0 в эти выходные, но по мере того как он разбирал накопившиеся задачи и pull request’ы с помощью комбинации Claude Fable 5 и GPT-5.5, список изменений после rc2 продолжал расти.
Это показательный момент: разработчик не просто использовал ИИ-ассистентов как вспомогательный инструмент — он применял Claude Fable 5 и GPT-5.5 для активной работы с очередью задач. В результате количество улучшений оказалось настолько большим, что выход полноценного стабильного релиза пришлось отложить.
Иногда самый быстрый путь к стабильному релизу — это сначала правильно реализовать все накопившиеся улучшения, даже если это требует дополнительного времени.
Главная новинка: составные внешние ключи
Крупнейшей новой функцией стала поддержка интроспекции (анализа структуры) и создания compound foreign keys (составных внешних ключей) — возможность, которая повлекла за собой незначительное изменение, ломающее обратную совместимость, в table.foreign_keys, и именно поэтому её нужно было включить в версию 4.0.
Что такое составной внешний ключ?
Compound foreign key (составной, или составной внешний ключ) — это внешний ключ, который ссылается сразу на несколько столбцов в другой таблице. Такая конструкция необходима, когда первичный ключ родительской таблицы состоит из нескольких полей.
Классический пример: таблица альбомов, где первичный ключ образован парой (исполнитель, название), и таблица песен, ссылающихся на этот составной ключ:
CREATE TABLE album (
albumartist TEXT,
albumname TEXT,
albumcover BINARY,
PRIMARY KEY (albumartist, albumname)
);
CREATE TABLE song (
songid INTEGER,
songartist TEXT,
songalbum TEXT,
songname TEXT,
FOREIGN KEY (songartist, songalbum)
REFERENCES album(albumartist, albumname)
);
Как это работало раньше (и почему это было неверно)
Составные (многоколоночные) внешние ключи теперь представлены как единый объект ForeignKey с is_compound=True и заполненными кортежами columns/other_columns, тогда как поля column и other_column равны None. Раньше они возвращались как по одному объекту ForeignKey на каждый столбец, что вводило в заблуждение, создавая видимость нескольких независимых внешних ключей.
Другими словами, если у вас была связь через два столбца, старая версия библиотеки сообщала вам о двух отдельных ключах вместо одного составного — это неверное представление реальной структуры базы данных.
graph TD
A["Старое поведение (3.x)"] --> B["ForeignKey: songartist → albumartist"]
A --> C["ForeignKey: songalbum → albumname"]
D["Новое поведение (4.0)"] --> E["ForeignKey is_compound=True\ncolumns=(songartist, songalbum)\nother_columns=(albumartist, albumname)"]
style A fill:#ff6b6b,color:#fff
style D fill:#51cf66,color:#fff
Breaking change: переход с namedtuple на dataclass
Метод table.foreign_keys теперь возвращает объекты ForeignKey, являющиеся dataclass‘ами, а не экземплярами namedtuple — это означает, что их больше нельзя распаковывать или индексировать как кортежи (table, column, other_table, other_column). Вместо этого нужно обращаться к полям по имени.
table.foreign_keys по позиции — например, table, col, other_table, other_col = fk — это перестанет работать в версии 4.0. Перейдите на именованный доступ: fk.table, fk.column, fk.other_table, fk.other_column.Пример миграции кода:
# Старый код (работал в 3.x, сломается в 4.0)
for fk in table.foreign_keys:
table_name, col, other_table, other_col = fk # ❌
# Новый код (правильно для 4.0)
for fk in table.foreign_keys:
print(fk.table, fk.column, fk.other_table, fk.other_column) # ✅
# Для составных ключей
if fk.is_compound:
print(fk.columns, fk.other_columns) # ✅
Дополнительное улучшение: внешние ключи, объявленные как REFERENCES other_table без явного указания столбца, теперь корректно разрешаются до первичного ключа другой таблицы в table.foreign_keys, вместо того чтобы возвращать other_column=None.
Регистронезависимые имена столбцов
sqlite-utils теперь следует соглашению SQLite о регистронезависимых именах столбцов (case insensitive column names) — это изменение затронуло сразу несколько различных мест в коде.
Имена столбцов, передаваемые в методы Python API, теперь сопоставляются со схемой таблицы без учёта регистра — так же, как SQLite обрабатывает идентификаторы. Раньше многие методы принимали идентификаторы в смешанном регистре в генерируемом SQL, но затем давали сбой или молча ничего не делали при Python-сравнениях со схемой.
Конкретные исправления включают:
Методы table.insert() и table.upsert() теперь корректно заполняют table.last_pk при использовании аргумента pk= с регистром, отличающимся от схемы таблицы или ключей записи — ранее это вызывало KeyError после того, как строка уже была записана.
Прочие изменения в 4.0rc3
Контекстный менеджер db.ensure_autocommit_off() был переименован в db.ensure_autocommit_on(), поскольку старое название описывало противоположное тому, что оно делало.
Удалена поддержка использования sqlean.py в качестве замены стандартного модуля sqlite3. Теперь sqlite-utils будет использовать pysqlite3, если он установлен, в противном случае — sqlite3 из стандартной библиотеки.
Обзор изменений: 3.x → 4.0
| Аспект | sqlite-utils 3.x | sqlite-utils 4.0 |
|---|---|---|
Тип ForeignKey | namedtuple | dataclass |
| Составные внешние ключи | Несколько объектов | Один объект с is_compound=True |
| Регистр столбцов | Частичная поддержка | Полная регистронезависимость |
| Autocommit-менеджер | ensure_autocommit_off() | ensure_autocommit_on() |
| sqlean.py | Поддерживается | Удалена поддержка |
| pysqlite3 | — | Автоматически используется если установлен |
Хронология выхода версий
Путь к релизу 4.0 оказался долгим: 4.0a0 вышел ещё в мае 2025 года, 4.0a1 — в ноябре 2025-го, 4.0rc1 — в июне 2026-го, 4.0rc2 — 5 июля 2026-го, и, наконец, 4.0rc3 — 6 июля 2026 года.
timeline
title История релизов sqlite-utils 4.0
май 2025 : 4.0a0 — первая альфа
ноябрь 2025 : 4.0a1 — вторая альфа
июнь 2026 : 4.0rc1 — первый RC (миграции и вложенные транзакции)
5 июл 2026 : 4.0rc2
6 июл 2026 : 4.0rc3 — составные FK, регистронезависимость
ИИ как напарник разработчика
Отдельного внимания заслуживает рабочий процесс Саймона Уиллисона: он использовал одновременно Claude Fable 5 (от Anthropic) и GPT-5.5 (от OpenAI) для разбора задач и pull request’ов. Это не просто эксперимент — подобный подход позволил ему за одни выходные обработать большой объём накопившихся изменений, хотя и привёл к тому, что релиз пришлось опять сдвинуть.
Как установить или обновиться
Для установки последнего релиз-кандидата:
pip install sqlite-utils==4.0rc3
Или через uv:
uv pip install sqlite-utils==4.0rc3
import sqlite_utils
db = sqlite_utils.Database(memory=True)
# Создаём таблицы со составным первичным ключом
db.execute("""
CREATE TABLE album (
artist TEXT, title TEXT,
PRIMARY KEY (artist, title)
)
""")
db.execute("""
CREATE TABLE song (
id INTEGER PRIMARY KEY,
song_artist TEXT, song_title TEXT,
FOREIGN KEY (song_artist, song_title)
REFERENCES album(artist, title)
)
""")
# Новое поведение в 4.0
for fk in db["song"].foreign_keys:
print(fk.is_compound) # True
print(fk.columns) # ('song_artist', 'song_title')
print(fk.other_columns) # ('artist', 'title')
Итог
sqlite-utils 4.0 — значимый релиз, который наводит порядок в давно назревших проблемах: некорректное представление составных внешних ключей, нестабильная работа с регистром имён столбцов, запутанное именование API. Всё это — ломающие изменения (breaking changes), ради которых и нужна мажорная версия. Стабильный релиз 4.0 уже совсем близко.