sqlite-utils 4.0rc3: составные внешние ключи и AI-помощники в разработке

sqlite-utils — это популярная Python-библиотека и CLI-утилита (инструмент командной строки) от Саймона Уиллисона (Simon Willison) для удобной работы с базами данных SQLite. 6 июля 2026 года вышел релиз-кандидат 4.0rc3, который оказался куда богаче изменениями, чем планировалось изначально.

ℹ Что такое sqlite-utils?
sqlite-utils — это открытая Python-библиотека и CLI-утилита для создания, наполнения и управления SQLite-базами данных. Особенно популярна среди журналистов-данных, исследователей и разработчиков, работающих с локальными данными.

Почему 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). Вместо этого нужно обращаться к полям по имени.

⚠ Важно для пользователей 3.x
Если ваш код распаковывал результат 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.xsqlite-utils 4.0
Тип ForeignKeynamedtupledataclass
Составные внешние ключиНесколько объектовОдин объект с 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’ов. Это не просто эксперимент — подобный подход позволил ему за одни выходные обработать большой объём накопившихся изменений, хотя и привёл к тому, что релиз пришлось опять сдвинуть.

💡 Практический вывод
Комбинирование нескольких ИИ-ассистентов (Claude + GPT) для code review и разбора задач — рабочая практика, которая ускоряет разработку open source проектов. Важно понимать: ИИ помогает быстрее находить и исправлять проблемы, но это одновременно означает, что список изменений может вырасти, а стабильный релиз — снова отложиться.

Как установить или обновиться

Для установки последнего релиз-кандидата:

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 уже совсем близко.