Simon Willison has cut the first release candidate of sqlite-utils 4.0, and on the surface the changelog reads like an unremarkable point release. Look closer and it’s the most substantive shift the project has taken in a long time. sqlite-utils has always been a Swiss-army knife sitting on top of Python’s built-in sqlite3 module: a CLI and library that turns JSON files into tables, transforms columns in ways raw SQLite makes painful, and quietly powers a generation of small data tools and Datasette sites. With 4.0rc1, it grows two features that meaningfully change how it can be used in real applications.

Migrations: a small library grows up

The headline addition is a migrations system. Until now, sqlite-utils has been famously schemaless-feeling — you point it at some data, it makes a table, you move on. That’s perfect for scrapers and notebooks, but awkward the moment a script outlives its first week and needs to evolve. Adding a migration mechanism is a quiet acknowledgement that sqlite-utils is no longer just a glue tool; people are shipping it inside longer-lived projects, and they need a sanctioned way to evolve schemas without hand-rolling their own version table.

This puts sqlite-utils into more direct conversation with heavier ORMs like SQLAlchemy/Alembic and Django’s migrations — but at a fraction of the ceremony. For the constituency that picks sqlite-utils precisely because it isn’t an ORM, that’s the right altitude to land at.

Nested transactions, finally

The second change is nested transactions, implemented via SQLite’s SAVEPOINT mechanism. Anyone who has tried to compose sqlite-utils operations inside a larger transactional block has run into the awkwardness of SQLite’s single-level transaction model leaking through. Savepoint-backed nesting means library code can wrap its own work in a transaction without caring whether the caller already opened one — a small thing that removes a recurring source of subtle bugs in batch import scripts.

Why a 4.0, and why now

Semver-wise, the jump to 4.0 signals breaking changes, and that’s the other story here. Willison has historically been conservative about backwards compatibility in sqlite-utils — the tool is embedded in too many one-off scripts to churn lightly. Saving the bigger cleanups for a major version, then bundling migrations and savepoints alongside them, is the disciplined move: users who pin to 3.x keep working, and 4.0 gets to clear out cruft without death by a thousand deprecations.

For day-to-day users, the practical upshot is modest: existing pipelines should keep working, with the upgrade paying off the first time a schema needs to change in production, or the first time a script needs to safely roll back a partial import. For the broader small-data ecosystem — Datasette, the Python-CLI-as-glue crowd, the long tail of civic-tech and journalism scripts — it’s a reminder that the most useful tools often aren’t the flashy ones. They’re the ones that quietly absorb a decade of feedback and ship a 4.0rc1 on a Sunday.