Video + notes on upgrading a Datasette plugin for the latest 1.0 alpha, with help from uv and OpenAI Codex CLI
6th November 2025
I’m upgrading various plugins for compatibility with the new Datasette 1.0a20 alpha release and I decided to record a video of the process. This post accompanies that video with detailed additional notes.
The datasette-checkbox plugin
I picked a very simple plugin to illustrate the upgrade process (possibly too simple). datasette-checkbox adds just one feature to Datasette: if you are viewing a table with boolean columns (detected as integer columns with names like is_active or has_attachments or should_notify) and your current user has permission to update rows in that table it adds an inline checkbox UI that looks like this:

I built the first version with the help of Claude back in August 2024—details in this issue comment.
Most of the implementation is JavaScript that makes calls to Datasette 1.0’s JSON write API. The Python code just checks that the user has the necessary permissions before including the extra JavaScript.
Running the plugin’s tests
The first step in upgrading any plugin is to run its tests against the latest Datasette version.
Thankfully uv makes it easy to run code in scratch virtual environments that include the different code versions you want to test against.
I have a test utility called tadd (for “test against development Datasette”) which I use for that purpose. I can run it in any plugin directory like this:
taddAnd it will run the existing plugin tests against whatever version of Datasette I have checked out in my ~/dev/datasette directory.
You can see the full implementation of tadd (and its friend radd described below) in this TIL—the basic version looks like this:
#!/bin/sh
uv run --no-project --isolated \
--with-editable '.[test]' --with-editable ~/dev/datasette \
python -m pytest "$@"I started by running tadd in the datasette-checkbox directory, and got my first failure... but it wasn’t due to permissions, it was because the pyproject.toml for the plugin was pinned to a specific mismatched version of Datasette:
dependencies = [
"datasette==1.0a19"
]I fixed this problem by swapping == to >= and ran the tests again... and they passed! Which was a problem because I was expecting permission-related failures.
It turns out when I first wrote the plugin I was lazy with the tests—they weren’t actually confirming that the table page loaded without errors.
I needed to actually run the code myself to see the expected bug.
First I created myself a demo database using sqlite-utils create-table:
sqlite-utils create-table demo.db \
demo id integer is_checked integer --pk idThen I ran it with Datasette against the plugin’s code like so:
radd demo.dbSure enough, visiting /demo/demo produced a 500 error about the missing Datasette.permission_allowed() method.
The next step was to update the test to also trigger this error:
@pytest.mark.asyncio async def test_plugin_adds_javascript(): datasette = Datasette() db = datasette.add_memory_database("demo") await db.execute_write( "CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY, is_active INTEGER)" ) await datasette.invoke_startup() response = await datasette.client.get("/demo/test") assert response.status_code == 200
And now tadd fails as expected.
Upgrading the plugin with Codex
It this point I could have manually fixed the plugin itself—which would likely have been faster given the small size of the fix—but instead I demonstrated a bash one-liner I’ve been using to apply these kinds of changes automatically:
codex exec --dangerously-bypass-approvals-and-sandbox \
"Run the command tadd and look at the errors and then
read ~/dev/datasette/docs/upgrade-1.0a20.md and apply
fixes and run the tests again and get them to pass"codex exec runs OpenAI Codex in non-interactive mode—it will loop until it has finished the prompt you give it.
I tell it to consult the subset of the Datasette upgrade documentation that talks about Datasette permissions and then get the tadd command to pass its tests.
This is an example of what I call designing agentic loops—I gave Codex the tools it needed (tadd) and a clear goal and let it get to work on my behalf.
The remainder of the video covers finishing up the work—testing the fix manually, commiting my work using:
git commit -a -m "$(basename "$PWD") for datasette>=1.0a20" \
-m "Refs https://github.com/simonw/datasette/issues/2577"Then shipping a 0.1a4 release to PyPI using the pattern described in this TIL.
Finally, I demonstrated that the shipped plugin worked in a fresh environment using uvx like this:
uvx --prerelease=allow --with datasette-checkbox \
datasette --root ~/dev/ecosystem/datasette-checkbox/demo.dbExecuting this command installs and runs a fresh Datasette instance with a fresh copy of the new alpha plugin (--prerelease=allow). It’s a neat way of confirming that freshly released software works as expected.
A colophon for the video
This video was shot in a single take using Descript, with no rehearsal and perilously little preparation in advance. I recorded through my AirPods and applied the “Studio Sound” filter to clean up the audio. I pasted in a simonwillison.net closing slide from my previous video and exported it locally at 1080p, then uploaded it to YouTube.
Something I learned from the Software Carpentry instructor training course is that making mistakes in front of an audience is actively helpful—it helps them see a realistic version of how software development works and they can learn from watching you recover. I see this as a great excuse for not editing out all of my mistakes!
I’m trying to build new habits around video content that let me produce useful videos while minimizing the amount of time I spend on production.
I plan to iterate more on the format as I get more comfortable with the process. I’m hoping I can find the right balance between production time and value to viewers.
More recent articles
- Code research projects with async coding agents like Claude Code and Codex - 6th November 2025
- A new SQL-powered permissions system in Datasette 1.0a20 - 4th November 2025