sqlite-utils 4.0a1 has several (minor) backwards incompatible changes
24th November 2025
I released a new alpha version of sqlite-utils last night—the 128th release of that package since I started building it back in 2018.
sqlite-utils is two things in one package: a Python library for conveniently creating and manipulating SQLite databases and a CLI tool for working with them in the terminal. Almost every feature provided by the package is available via both of those surfaces.
This is hopefully the last alpha before a 4.0 stable release. I use semantic versioning for this library, so the 4.0 version number indicates that there are backward incompatible changes that may affect code written against the 3.x line.
These changes are mostly very minor: I don’t want to break any existing code if I can avoid it. I made it all the way to version 3.38 before I had to ship a major release and I’m sad I couldn’t push that even further!
Here are the annotated release notes for 4.0a1.
- Breaking change: The
db.table(table_name)method now only works with tables. To access a SQL view usedb.view(view_name)instead. (#657)
This change is for type hint enthusiasts. The Python library used to encourage accessing both SQL tables and SQL views through the db["name_of_table_or_view"] syntactic sugar—but tables and view have different interfaces since there’s no way to handle a .insert(row) on a SQLite view. If you want clean type hints for your code you can now use the db.table(table_name) and db.view(view_name) methods instead.
- The
table.insert_all()andtable.upsert_all()methods can now accept an iterator of lists or tuples as an alternative to dictionaries. The first item should be a list/tuple of column names. See Inserting data from a list or tuple iterator for details. (#672)
A new feature, not a breaking change. I realized that supporting a stream of lists or tuples as an option for populating large tables would be a neat optimization over always dealing with dictionaries each of which duplicated the column names.
I had the idea for this one while walking the dog and built the first prototype by prompting Claude Code for web on my phone. Here’s the prompt I used and the prototype report it created, which included a benchmark estimating how much of a performance boost could be had for different sizes of tables.
- Breaking change: The default floating point column type has been changed from
FLOATtoREAL, which is the correct SQLite type for floating point values. This affects auto-detected columns when inserting data. (#645)
I was horrified to discover a while ago that I’d been creating SQLite columns called FLOAT but the correct type to use was REAL! This change fixes that. Previously the fix was to ask for tables to be created in strict mode.
- Now uses
pyproject.tomlin place ofsetup.pyfor packaging. (#675)
As part of this I also figured out recipes for using uv as a development environment for the package, which are now baked into the Justfile.
- Tables in the Python API now do a much better job of remembering the primary key and other schema details from when they were first created. (#655)
This one is best explained in the issue.
- Breaking change: The
table.convert()andsqlite-utils convertmechanisms no longer skip values that evaluate toFalse. Previously the--skip-falseoption was needed, this has been removed. (#542)
Another change which I would have made earlier but, since it introduces a minor behavior change to an existing feature, I reserved it for the 4.0 release.
- Breaking change: Tables created by this library now wrap table and column names in
"double-quotes"in the schema. Previously they would use[square-braces]. (#677)
Back in 2018 when I started this project I was new to working in-depth with SQLite and incorrectly concluded that the correct way to create tables and columns named after reserved words was like this:
create table [my table] (
[id] integer primary key,
[key] text
)
That turned out to be a non-standard SQL syntax which the SQLite documentation describes like this:
A keyword enclosed in square brackets is an identifier. This is not standard SQL. This quoting mechanism is used by MS Access and SQL Server and is included in SQLite for compatibility.
Unfortunately I baked it into the library early on and it’s been polluting the world with weirdly escaped table and column names ever since!
I’ve finally fixed that, with the help of Claude Code which took on the mind-numbing task of updating hundreds of existing tests that asserted against the generated schemas.
The above example table schema now looks like this:
create table "my table" (
"id" integer primary key,
"key" text
)
This may seem like a pretty small change but I expect it to cause a fair amount of downstream pain purely in terms of updating tests that work against tables created by sqlite-utils!
- The
--functionsCLI argument now accepts a path to a Python file in addition to accepting a string full of Python code. It can also now be specified multiple times. (#659)
I made this change first in LLM and decided to bring it to sqlite-utils for consistency between the two tools.
- Breaking change: Type detection is now the default behavior for the
insertandupsertCLI commands when importing CSV or TSV data. Previously all columns were treated asTEXTunless the--detect-typesflag was passed. Use the new--no-detect-typesflag to restore the old behavior. TheSQLITE_UTILS_DETECT_TYPESenvironment variable has been removed. (#679)
One last minor ugliness that I waited for a major version bump to fix.
More recent articles
- Olmo 3 is a fully open LLM - 22nd November 2025
- Nano Banana Pro aka gemini-3-pro-image-preview is the best available image generation model - 20th November 2025