<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: datasette</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/datasette.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-04-16T00:18:03+00:00</updated><author><name>Simon Willison</name></author><entry><title>datasette.io news preview</title><link href="https://simonwillison.net/2026/Apr/16/datasette-io-preview/#atom-tag" rel="alternate"/><published>2026-04-16T00:18:03+00:00</published><updated>2026-04-16T00:18:03+00:00</updated><id>https://simonwillison.net/2026/Apr/16/datasette-io-preview/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Tool:&lt;/strong&gt; &lt;a href="https://tools.simonwillison.net/datasette-io-preview"&gt;datasette.io news preview&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;The &lt;a href="https://datasette.io/"&gt;datasette.io&lt;/a&gt; website has a news section built from this &lt;a href="https://github.com/simonw/datasette.io/blob/main/news.yaml"&gt;news.yaml&lt;/a&gt; file in the underlying GitHub repository. The YAML format looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- date: 2026-04-15
  body: |-
    [Datasette 1.0a27](https://docs.datasette.io/en/latest/changelog.html#a27-2026-04-15) changes how CSRF protection works in a way that simplifies form and API integration, and introduces a new `RenameTableEvent` for when a table is renamed by a SQL query.
- date: 2026-03-18
  body: |-
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This format is a little hard to edit, so I finally &lt;a href="https://claude.ai/share/c96129b9-bcb0-4eba-aee9-4a7ad236dfb7"&gt;had Claude build a custom preview UI&lt;/a&gt; to make checking for errors have slightly less friction.&lt;/p&gt;
&lt;p&gt;I built it using standard &lt;a href="https://claude.ai/"&gt;claude.ai&lt;/a&gt; and Claude Artifacts, taking advantage of Claude's ability to clone GitHub repos and look at their content as part of a regular chat:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Clone https://github.com/simonw/datasette.io and look at the news.yaml file and how it is rendered on the homepage. Build an artifact I can paste that YAML into which previews what it will look like, and highlights any markdown errors or YAML errors&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="Screenshot showing two side-by-side views of a datasette.io news preview tool. The left panel shows a dark-themed YAML editor with news entries containing date and body fields in Markdown format, with a red validation error at the bottom indicating the date field has an invalid format. The right panel shows the rendered preview output with formatted headings by date (April 2026, 18th March 2026), displaying 115 news entries with linked release names, inline code snippets, and changelog descriptions. A red badge with &amp;quot;1&amp;quot; appears on the left panel header indicating one validation error." src="https://static.simonwillison.net/static/2026/datasette-io-preview.jpg" /&gt;&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/vibe-coding"&gt;vibe-coding&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude"&gt;claude&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tools"&gt;tools&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="vibe-coding"/><category term="claude"/><category term="tools"/><category term="datasette"/></entry><entry><title>datasette-export-database 0.3a1</title><link href="https://simonwillison.net/2026/Apr/15/datasette-export-database/#atom-tag" rel="alternate"/><published>2026-04-15T23:52:35+00:00</published><updated>2026-04-15T23:52:35+00:00</updated><id>https://simonwillison.net/2026/Apr/15/datasette-export-database/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-export-database/releases/tag/0.3a1"&gt;datasette-export-database 0.3a1&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;This plugin was using the &lt;code&gt;ds_csrftoken&lt;/code&gt; cookie as part of a custom signed URL, which needed upgrading now that Datasette 1.0a27 &lt;a href="https://simonwillison.net/2026/Apr/14/replace-token-based-csrf/"&gt;no longer sets that cookie&lt;/a&gt;.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette 1.0a27</title><link href="https://simonwillison.net/2026/Apr/15/datasette/#atom-tag" rel="alternate"/><published>2026-04-15T23:16:34+00:00</published><updated>2026-04-15T23:16:34+00:00</updated><id>https://simonwillison.net/2026/Apr/15/datasette/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/datasette/releases/tag/1.0a27"&gt;datasette 1.0a27&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;Two major changes in this new Datasette alpha. I covered the first of those &lt;a href="https://simonwillison.net/2026/Apr/14/replace-token-based-csrf/"&gt;in detail yesterday&lt;/a&gt; - Datasette no longer uses Django-style CSRF form tokens, instead using modern browser headers &lt;a href="https://words.filippo.io/csrf"&gt;as described by Filippo Valsorda&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The second big change is that Datasette now fires a new &lt;a href="https://docs.datasette.io/en/latest/events.html#datasette.events.RenameTableEvent"&gt;RenameTableEvent&lt;/a&gt; any time a table is renamed during a SQLite transaction. This is useful because some plugins (like &lt;a href="https://github.com/datasette/datasette-comments"&gt;datasette-comments&lt;/a&gt;) attach additional data to table records by name, so a renamed table requires them to react in appropriate ways.&lt;/p&gt;
&lt;p&gt;Here are the rest of the changes in the alpha:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;a href="https://docs.datasette.io/en/latest/internals.html#internals-datasette-client-actor"&gt;actor= parameter&lt;/a&gt; for &lt;code&gt;datasette.client&lt;/code&gt; methods, allowing internal requests to be made as a specific actor. This is particularly useful for writing automated tests. (&lt;a href="https://github.com/simonw/datasette/pull/2688"&gt;#2688&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;Database(is_temp_disk=True)&lt;/code&gt; option, used internally for the internal database. This helps resolve intermittent database locked errors caused by the internal database being in-memory as opposed to on-disk. (&lt;a href="https://github.com/simonw/datasette/issues/2683"&gt;#2683&lt;/a&gt;) (&lt;a href="https://github.com/simonw/datasette/pull/2684"&gt;#2684&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;/&amp;lt;database&amp;gt;/&amp;lt;table&amp;gt;/-/upsert&lt;/code&gt; API (&lt;a href="https://docs.datasette.io/en/latest/json_api.html#tableupsertview"&gt;docs&lt;/a&gt;) now rejects rows with &lt;code&gt;null&lt;/code&gt; primary key values. (&lt;a href="https://github.com/simonw/datasette/issues/1936"&gt;#1936&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Improved example in the API explorer for the &lt;code&gt;/-/upsert&lt;/code&gt; endpoint (&lt;a href="https://docs.datasette.io/en/latest/json_api.html#tableupsertview"&gt;docs&lt;/a&gt;). (&lt;a href="https://github.com/simonw/datasette/issues/1936"&gt;#1936&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;/&amp;lt;database&amp;gt;.json&lt;/code&gt; endpoint now includes an &lt;code&gt;"ok": true&lt;/code&gt; key, for consistency with other JSON API responses.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.datasette.io/en/latest/internals.html#internals-utils-call-with-supported-arguments"&gt;call_with_supported_arguments()&lt;/a&gt; is now documented as a supported public API. (&lt;a href="https://github.com/simonw/datasette/pull/2678"&gt;#2678&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/annotated-release-notes"&gt;annotated-release-notes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="annotated-release-notes"/><category term="datasette"/><category term="python"/></entry><entry><title>datasette-ports 0.3</title><link href="https://simonwillison.net/2026/Apr/15/datasette-ports/#atom-tag" rel="alternate"/><published>2026-04-15T02:50:57+00:00</published><updated>2026-04-15T02:50:57+00:00</updated><id>https://simonwillison.net/2026/Apr/15/datasette-ports/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-ports/releases/tag/0.3"&gt;datasette-ports 0.3&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;A small update for my tool for helping me figure out what all of the Datasette instances on my laptop are up to.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Show working directory derived from each PID&lt;/li&gt;
&lt;li&gt;Show the full path to each database file&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Output now looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://127.0.0.1:8007/ - v1.0a26
  Directory: /Users/simon/dev/blog
  Databases:
    simonwillisonblog: /Users/simon/dev/blog/simonwillisonblog.db
  Plugins:
    datasette-llm
    datasette-secrets
http://127.0.0.1:8001/ - v1.0a26
  Directory: /Users/simon/dev/creatures
  Databases:
    creatures: /tmp/creatures.db
&lt;/code&gt;&lt;/pre&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette PR #2689: Replace token-based CSRF with Sec-Fetch-Site header protection</title><link href="https://simonwillison.net/2026/Apr/14/replace-token-based-csrf/#atom-tag" rel="alternate"/><published>2026-04-14T23:58:53+00:00</published><updated>2026-04-14T23:58:53+00:00</updated><id>https://simonwillison.net/2026/Apr/14/replace-token-based-csrf/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette/pull/2689"&gt;datasette PR #2689: Replace token-based CSRF with Sec-Fetch-Site header protection&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Datasette has long protected against CSRF attacks using CSRF tokens, implemented using my &lt;a href="https://github.com/simonw/asgi-csrf"&gt;asgi-csrf&lt;/a&gt; Python library. These are something of a pain to work with - you need to scatter forms in templates with &lt;code&gt;&amp;lt;input type="hidden" name="csrftoken" value="{{ csrftoken() }}"&amp;gt;&lt;/code&gt; lines and then selectively disable CSRF protection for APIs that are intended to be called from outside the browser.&lt;/p&gt;
&lt;p&gt;I've been following Filippo Valsorda's research here with interest, described in &lt;a href="https://words.filippo.io/csrf/"&gt;this detailed essay from August 2025&lt;/a&gt; and shipped &lt;a href="https://tip.golang.org/doc/go1.25#nethttppkgnethttp"&gt;as part of Go 1.25&lt;/a&gt; that same month.&lt;/p&gt;
&lt;p&gt;I've now landed the same change in Datasette. Here's the PR description - Claude Code did much of the work (across 10 commits, closely guided by me and cross-reviewed by GPT-5.4) but I've decided to start writing these PR descriptions by hand, partly to make them more concise and also as an exercise in keeping myself honest.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;New CSRF protection middleware inspired by Go 1.25 and &lt;a href="https://words.filippo.io/csrf/"&gt;this research&lt;/a&gt; by Filippo Valsorda. This replaces the old CSRF token based protection.&lt;/li&gt;
&lt;li&gt;Removes all instances of &lt;code&gt;&amp;lt;input type="hidden" name="csrftoken" value="{{ csrftoken() }}"&amp;gt;&lt;/code&gt; in the templates - they are no longer needed.&lt;/li&gt;
&lt;li&gt;Removes the &lt;code&gt;def skip_csrf(datasette, scope):&lt;/code&gt; plugin hook defined in &lt;code&gt;datasette/hookspecs.py&lt;/code&gt; and its documentation and tests.&lt;/li&gt;
&lt;li&gt;Updated &lt;a href="https://docs.datasette.io/en/latest/internals.html#csrf-protection"&gt;CSRF protection documentation&lt;/a&gt; to describe the new approach.&lt;/li&gt;
&lt;li&gt;Upgrade guide now &lt;a href="https://docs.datasette.io/en/latest/upgrade_guide.html#csrf-protection-is-now-header-based"&gt;describes the CSRF change&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/csrf"&gt;csrf&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;&lt;/p&gt;



</summary><category term="csrf"/><category term="security"/><category term="datasette"/><category term="ai-assisted-programming"/></entry><entry><title>datasette-ports 0.2</title><link href="https://simonwillison.net/2026/Apr/6/datasette-ports-2/#atom-tag" rel="alternate"/><published>2026-04-06T03:25:43+00:00</published><updated>2026-04-06T03:25:43+00:00</updated><id>https://simonwillison.net/2026/Apr/6/datasette-ports-2/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-ports/releases/tag/0.2"&gt;datasette-ports 0.2&lt;/a&gt;&lt;/p&gt;
    &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;No longer requires Datasette - running &lt;code&gt;uvx datasette-ports&lt;/code&gt; now works as well.&lt;/li&gt;
&lt;li&gt;Installing it as a Datasette plugin continues to provide the &lt;code&gt;datasette ports&lt;/code&gt; command.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette-ports 0.1</title><link href="https://simonwillison.net/2026/Apr/6/datasette-ports/#atom-tag" rel="alternate"/><published>2026-04-06T00:23:55+00:00</published><updated>2026-04-06T00:23:55+00:00</updated><id>https://simonwillison.net/2026/Apr/6/datasette-ports/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-ports/releases/tag/0.1"&gt;datasette-ports 0.1&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;Another &lt;a href="https://gisthost.github.io/?f92d8a6bdadee1c77972b5e51954144e"&gt;example&lt;/a&gt; of README-driven development, this time solving a problem that might be unique to me.&lt;/p&gt;
&lt;p&gt;I often find myself running a bunch of different &lt;a href="https://datasette.io"&gt;Datasette&lt;/a&gt; instances with different databases and different in-development plugins, spreads across dozens of different terminal windows - enough that I frequently lose them!&lt;/p&gt;
&lt;p&gt;Now I can run this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;datasette install datasette-ports
datasette ports
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And get a list of every running instance that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;http://127.0.0.1:8333/ - v1.0a26
  Databases: data
  Plugins: datasette-enrichments, datasette-enrichments-llm, datasette-llm, datasette-secrets
http://127.0.0.1:8001/ - v1.0a26
  Databases: creatures
  Plugins: datasette-extract, datasette-llm, datasette-secrets
http://127.0.0.1:8900/ - v0.65.2
  Databases: logs
&lt;/code&gt;&lt;/pre&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette-llm 0.1a6</title><link href="https://simonwillison.net/2026/Apr/1/datasette-llm-2/#atom-tag" rel="alternate"/><published>2026-04-01T23:01:37+00:00</published><updated>2026-04-01T23:01:37+00:00</updated><id>https://simonwillison.net/2026/Apr/1/datasette-llm-2/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm/releases/tag/0.1a6"&gt;datasette-llm 0.1a6&lt;/a&gt;&lt;/p&gt;
    &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The same model ID no longer needs to be repeated in both the default model and allowed models lists - setting it as a default model automatically adds it to the allowed models list. &lt;a href="https://github.com/datasette/datasette-llm/issues/6"&gt;#6&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Improved documentation for &lt;a href="https://github.com/datasette/datasette-llm/blob/0.1a6/README.md#usage"&gt;Python API usage&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-enrichments-llm 0.2a1</title><link href="https://simonwillison.net/2026/Apr/1/datasette-enrichments-llm-2/#atom-tag" rel="alternate"/><published>2026-04-01T22:00:34+00:00</published><updated>2026-04-01T22:00:34+00:00</updated><id>https://simonwillison.net/2026/Apr/1/datasette-enrichments-llm-2/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-enrichments-llm/releases/tag/0.2a1"&gt;datasette-enrichments-llm 0.2a1&lt;/a&gt;&lt;/p&gt;
    &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;actor&lt;/code&gt; who triggers an enrichment is now passed to the &lt;code&gt;llm.mode(... actor=actor)&lt;/code&gt; method. &lt;a href="https://github.com/datasette/datasette-enrichments-llm/issues/3"&gt;#3&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/enrichments"&gt;enrichments&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="enrichments"/><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-extract 0.3a0</title><link href="https://simonwillison.net/2026/Apr/1/datasette-extract/#atom-tag" rel="alternate"/><published>2026-04-01T03:32:16+00:00</published><updated>2026-04-01T03:32:16+00:00</updated><id>https://simonwillison.net/2026/Apr/1/datasette-extract/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-extract/releases/tag/0.3a0"&gt;datasette-extract 0.3a0&lt;/a&gt;&lt;/p&gt;
    &lt;ul&gt;
&lt;li&gt;Now uses &lt;a href="https://github.com/datasette/datasette-llm"&gt;datasette-llm&lt;/a&gt; to manage model configuration, which means you can control which models are available for extraction tasks using the &lt;code&gt;extract&lt;/code&gt; purpose and &lt;a href="https://github.com/datasette/datasette-llm/blob/main/README.md#configuration"&gt;LLM model configuration&lt;/a&gt;. &lt;a href="https://github.com/datasette/datasette-extract/issues/38"&gt;#38&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-enrichments-llm 0.2a0</title><link href="https://simonwillison.net/2026/Apr/1/datasette-enrichments-llm/#atom-tag" rel="alternate"/><published>2026-04-01T03:28:44+00:00</published><updated>2026-04-01T03:28:44+00:00</updated><id>https://simonwillison.net/2026/Apr/1/datasette-enrichments-llm/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-enrichments-llm/releases/tag/0.2a0"&gt;datasette-enrichments-llm 0.2a0&lt;/a&gt;&lt;/p&gt;
    &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;This plugin now uses &lt;a href="https://github.com/datasette/datasette-llm"&gt;datasette-llm&lt;/a&gt; to configure and manage models. This means it's possible to &lt;a href="https://github.com/datasette/datasette-enrichments-llm/blob/0.2a0/README.md#configuration"&gt;specify which models&lt;/a&gt; should be made available for enrichments, using the new &lt;code&gt;enrichments&lt;/code&gt; purpose.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-llm-usage 0.2a0</title><link href="https://simonwillison.net/2026/Apr/1/datasette-llm-usage/#atom-tag" rel="alternate"/><published>2026-04-01T03:24:03+00:00</published><updated>2026-04-01T03:24:03+00:00</updated><id>https://simonwillison.net/2026/Apr/1/datasette-llm-usage/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm-usage/releases/tag/0.2a0"&gt;datasette-llm-usage 0.2a0&lt;/a&gt;&lt;/p&gt;
    &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Removed features relating to allowances and estimated pricing. These are now the domain of &lt;a href="https://github.com/datasette/datasette-llm-accountant"&gt;datasette-llm-accountant&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Now depends on &lt;a href="https://github.com/datasette/datasette-llm"&gt;datasette-llm&lt;/a&gt; for model configuration. &lt;a href="https://github.com/datasette/datasette-llm-usage/pull/3"&gt;#3&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Full prompts and responses and tool calls can now be logged to the &lt;code&gt;llm_usage_prompt_log&lt;/code&gt; table in the internal database if you set the new &lt;code&gt;datasette-llm-usage.log_prompts&lt;/code&gt; plugin configuration setting.&lt;/li&gt;
&lt;li&gt;Redesigned the &lt;code&gt;/-/llm-usage-simple-prompt&lt;/code&gt; page, which now requires the &lt;code&gt;llm-usage-simple-prompt&lt;/code&gt; permission.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-llm 0.1a5</title><link href="https://simonwillison.net/2026/Apr/1/datasette-llm/#atom-tag" rel="alternate"/><published>2026-04-01T03:11:01+00:00</published><updated>2026-04-01T03:11:01+00:00</updated><id>https://simonwillison.net/2026/Apr/1/datasette-llm/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm/releases/tag/0.1a5"&gt;datasette-llm 0.1a5&lt;/a&gt;&lt;/p&gt;
    &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;llm_prompt_context()&lt;/code&gt; plugin hook wrapper mechanism now tracks prompts executed within a chain as well as one-off prompts, which means it can be used to track tool call loops. &lt;a href="https://github.com/datasette/datasette-llm"&gt;#5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-llm 0.1a4</title><link href="https://simonwillison.net/2026/Mar/31/datasette-llm/#atom-tag" rel="alternate"/><published>2026-03-31T21:17:23+00:00</published><updated>2026-03-31T21:17:23+00:00</updated><id>https://simonwillison.net/2026/Mar/31/datasette-llm/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm/releases/tag/0.1a4"&gt;datasette-llm 0.1a4&lt;/a&gt;&lt;/p&gt;
    &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Ability to &lt;a href="https://github.com/datasette/datasette-llm/blob/0.1a4/README.md#model-references-with-custom-api-keys"&gt;configure different API keys for models based on their purpose&lt;/a&gt; - for example, set it up so enrichments always use &lt;code&gt;gpt-5.4-mini&lt;/code&gt; with an API key dedicated to that purpose. &lt;a href="https://github.com/datasette/datasette-llm/pull/4"&gt;#4&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I released &lt;a href="https://github.com/simonw/llm-echo/releases/tag/0.3"&gt;llm-echo 0.3&lt;/a&gt; to provide an API key testing utility I needed for the tests for this new feature.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-files 0.1a3</title><link href="https://simonwillison.net/2026/Mar/30/datasette-files/#atom-tag" rel="alternate"/><published>2026-03-30T23:58:49+00:00</published><updated>2026-03-30T23:58:49+00:00</updated><id>https://simonwillison.net/2026/Mar/30/datasette-files/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-files/releases/tag/0.1a3"&gt;datasette-files 0.1a3&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;I'm working on integrating &lt;code&gt;datasette-files&lt;/code&gt; into other plugins, such as &lt;a href="https://github.com/datasette/datasette-extract"&gt;datasette-extract&lt;/a&gt;. This necessitated a new release of the base plugin.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;owners_can_edit&lt;/code&gt; and &lt;code&gt;owners_can_delete&lt;/code&gt; configuration options, plus the &lt;code&gt;files-edit&lt;/code&gt; and &lt;code&gt;files-delete&lt;/code&gt; actions are now scoped to a new &lt;code&gt;FileResource&lt;/code&gt; which is a child of &lt;code&gt;FileSourceResource&lt;/code&gt;. &lt;a href="https://github.com/datasette/datasette-files/issues/18"&gt;#18&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The file picker UI is now available as a &lt;code&gt;&amp;lt;datasette-file-picker&amp;gt;&lt;/code&gt; Web Component. Thanks, &lt;a href="https://github.com/asg017"&gt;Alex Garcia&lt;/a&gt;. &lt;a href="https://github.com/datasette/datasette-files/issues/19"&gt;#19&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;from datasette_files import get_file&lt;/code&gt; Python API for other plugins that need to access file data. &lt;a href="https://github.com/datasette/datasette-files/issues/20"&gt;#20&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette-llm 0.1a3</title><link href="https://simonwillison.net/2026/Mar/30/datasette-llm/#atom-tag" rel="alternate"/><published>2026-03-30T19:48:43+00:00</published><updated>2026-03-30T19:48:43+00:00</updated><id>https://simonwillison.net/2026/Mar/30/datasette-llm/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm/releases/tag/0.1a3"&gt;datasette-llm 0.1a3&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;Adds the ability to configure &lt;a href="https://github.com/datasette/datasette-llm/tree/0.1a3#purpose-specific-configuration"&gt;which LLMs are available for which purpose&lt;/a&gt;, which means you can restrict the list of models that can be used with a specific plugin. &lt;a href="https://github.com/datasette/datasette-llm/issues/3"&gt;#3&lt;/a&gt;&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-llm 0.1a2</title><link href="https://simonwillison.net/2026/Mar/26/datasette-llm/#atom-tag" rel="alternate"/><published>2026-03-26T15:52:32+00:00</published><updated>2026-03-26T15:52:32+00:00</updated><id>https://simonwillison.net/2026/Mar/26/datasette-llm/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm/releases/tag/0.1a2"&gt;datasette-llm 0.1a2&lt;/a&gt;&lt;/p&gt;
    &lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;actor&lt;/code&gt; is now available to the &lt;code&gt;llm_prompt_context&lt;/code&gt; plugin hook. &lt;a href="https://github.com/datasette/datasette-llm/pull/2"&gt;#2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="llm"/><category term="datasette"/></entry><entry><title>datasette-files-s3 0.1a1</title><link href="https://simonwillison.net/2026/Mar/25/datasette-files-s3/#atom-tag" rel="alternate"/><published>2026-03-25T21:57:05+00:00</published><updated>2026-03-25T21:57:05+00:00</updated><id>https://simonwillison.net/2026/Mar/25/datasette-files-s3/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-files-s3/releases/tag/0.1a1"&gt;datasette-files-s3 0.1a1&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;A backend for &lt;a href="https://github.com/datasette/datasette-files"&gt;datasette-files&lt;/a&gt; that adds the ability to store and retrieve files using an S3 bucket. This release added &lt;a href="https://github.com/datasette/datasette-files-s3/blob/main/README.md#credentials-broker-response"&gt;a mechanism&lt;/a&gt; for fetching S3 configuration periodically from a URL, which means we can use time limited IAM credentials that are restricted to a prefix within a bucket.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/s3"&gt;s3&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="s3"/><category term="datasette"/></entry><entry><title>datasette-llm 0.1a1</title><link href="https://simonwillison.net/2026/Mar/25/datasette-llm/#atom-tag" rel="alternate"/><published>2026-03-25T21:24:31+00:00</published><updated>2026-03-25T21:24:31+00:00</updated><id>https://simonwillison.net/2026/Mar/25/datasette-llm/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-llm/releases/tag/0.1a1"&gt;datasette-llm 0.1a1&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;New release of the base plugin that makes models from &lt;a href="https://llm.datasette.io/"&gt;LLM&lt;/a&gt; available for use by other Datasette plugins such as &lt;a href="https://github.com/datasette/datasette-enrichments-llm"&gt;datasette-enrichments-llm&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;New &lt;a href="https://github.com/datasette/datasette-llm/blob/main/README.md#register_llm_purposes"&gt;&lt;code&gt;register_llm_purposes()&lt;/code&gt; plugin hook&lt;/a&gt; and &lt;code&gt;get_purposes()&lt;/code&gt; function for retrieving registered purpose strings. &lt;a href="https://github.com/datasette/datasette-llm/issues/1"&gt;#1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the responsibilities of this plugin is to configure which models are used for which purposes, so you can say in one place "data enrichment uses GPT-5.4-nano but SQL query assistance happens using Sonnet 4.6", for example.&lt;/p&gt;
&lt;p&gt;Plugins that depend on this can use &lt;code&gt;model = await llm.model(purpose="enrichment")&lt;/code&gt; to indicate the purpose of the prompts they wish to execute against the model. Those plugins can now also use the new &lt;code&gt;register_llm_purposes()&lt;/code&gt; hook to register those purpose strings, which means future plugins can list those purposes in one place to power things like an admin UI for assigning models to purposes.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/annotated-release-notes"&gt;annotated-release-notes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/plugins"&gt;plugins&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="annotated-release-notes"/><category term="llm"/><category term="datasette"/><category term="plugins"/></entry><entry><title>datasette-files 0.1a2</title><link href="https://simonwillison.net/2026/Mar/23/datasette-files/#atom-tag" rel="alternate"/><published>2026-03-23T23:06:38+00:00</published><updated>2026-03-23T23:06:38+00:00</updated><id>https://simonwillison.net/2026/Mar/23/datasette-files/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-files/releases/tag/0.1a2"&gt;datasette-files 0.1a2&lt;/a&gt;&lt;/p&gt;
    &lt;p&gt;The most interesting alpha of &lt;a href="https://github.com/datasette/datasette-files"&gt;datasette-files&lt;/a&gt; yet, a new plugin which adds the ability to upload files directly into a Datasette instance. Here are the release notes in full:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Columns are now configured using the &lt;a href="https://docs.datasette.io/en/latest/changelog.html#new-column-types-system"&gt;new column_types system&lt;/a&gt; from Datasette 1.0a26. &lt;a href="https://github.com/datasette/datasette-files/issues/8"&gt;#8&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;New &lt;code&gt;file_actions&lt;/code&gt; plugin hook, plus ability to import an uploaded CSV/TSV file to a table. &lt;a href="https://github.com/datasette/datasette-files/issues/10"&gt;#10&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;UI for uploading multiple files at once via the new documented JSON upload API. &lt;a href="https://github.com/datasette/datasette-files/issues/11"&gt;#11&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Thumbnails are now generated for image files and stored in an internal &lt;code&gt;datasette_files_thumbnails&lt;/code&gt; table. &lt;a href="https://github.com/datasette/datasette-files/issues/13"&gt;#13&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/annotated-release-notes"&gt;annotated-release-notes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="annotated-release-notes"/><category term="datasette"/></entry><entry><title>Coding agents for data analysis</title><link href="https://simonwillison.net/2026/Mar/16/coding-agents-for-data-analysis/#atom-tag" rel="alternate"/><published>2026-03-16T20:12:32+00:00</published><updated>2026-03-16T20:12:32+00:00</updated><id>https://simonwillison.net/2026/Mar/16/coding-agents-for-data-analysis/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/"&gt;Coding agents for data analysis&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Here's the handout I prepared for my NICAR 2026 workshop "Coding agents for data analysis" - a three hour session aimed at data journalists demonstrating ways that tools like Claude Code and OpenAI Codex can be used to explore, analyze and clean data.&lt;/p&gt;
&lt;p&gt;Here's the table of contents:&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/coding-agents.html"&gt;Coding agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/warmup.html"&gt;Warmup: ChatGPT and Claude&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/setup.html"&gt;Setup Claude Code and Codex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/asking-questions.html"&gt;Asking questions against a database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/exploring-data.html"&gt;Exploring data with agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/cleaning-trees.html"&gt;Cleaning data: decoding neighborhood codes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/visualizations.html"&gt;Creating visualizations with agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonw.github.io/nicar-2026-coding-agents/scraping.html"&gt;Scraping data with agents&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I ran the workshop using GitHub Codespaces and OpenAI Codex, since it was easy (and inexpensive) to distribute a budget-restricted API key for Codex that attendees could use during the class. Participants ended up burning $23 of Codex tokens.&lt;/p&gt;
&lt;p&gt;The exercises all used Python and SQLite and some of them used Datasette.&lt;/p&gt;
&lt;p&gt;One highlight of the workshop was when we started &lt;a href="https://simonw.github.io/nicar-2026-coding-agents/visualizations.html#javascript-visualizations"&gt;running Datasette&lt;/a&gt; such that it served static content from a &lt;code&gt;viz/&lt;/code&gt; folder, then had Claude Code start vibe coding new interactive visualizations directly in that folder. Here's a heat map it created for my trees database using Leaflet and &lt;a href="https://github.com/Leaflet/Leaflet.heat"&gt;Leaflet.heat&lt;/a&gt;, &lt;a href="https://gist.github.com/simonw/985ae2a6a3cd3df3fd375eb58dabea0f"&gt;source code here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of a &amp;quot;Trees SQL Map&amp;quot; web application with the heading &amp;quot;Trees SQL Map&amp;quot; and subheading &amp;quot;Run a query and render all returned points as a heat map. The default query targets roughly 200,000 trees.&amp;quot; Below is an input field containing &amp;quot;/trees/-/query.json&amp;quot;, a &amp;quot;Run Query&amp;quot; button, and a SQL query editor with the text &amp;quot;SELECT cast(Latitude AS float) AS latitude, cast(Longitude AS float) AS longitude, CASE WHEN DBH IS NULL OR DBH = '' THEN 0.3 WHEN cast(DBH AS float) &amp;lt;= 0 THEN 0.3 WHEN cast(DBH AS float) &amp;gt;= 80 THEN 1.0&amp;quot; (query is truncated). A status message reads &amp;quot;Loaded 1,000 rows and plotted 1,000 points as heat map.&amp;quot; Below is a Leaflet/OpenStreetMap interactive map of San Francisco showing a heat map overlay of tree locations, with blue/green clusters concentrated in areas like the Richmond District, Sunset District, and other neighborhoods. Map includes zoom controls and a &amp;quot;Leaflet | © OpenStreetMap contributors&amp;quot; attribution." src="https://static.simonwillison.net/static/2026/tree-sql-map.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I designed the handout to also be useful for people who weren't able to attend the session in person. As is usually the case, material aimed at data journalists is equally applicable to anyone else with data to explore.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/nicar"&gt;nicar&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/speaking"&gt;speaking&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/coding-agents"&gt;coding-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/data-journalism"&gt;data-journalism&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/codex-cli"&gt;codex-cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-code"&gt;claude-code&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/leaflet"&gt;leaflet&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;&lt;/p&gt;



</summary><category term="nicar"/><category term="sqlite"/><category term="ai"/><category term="speaking"/><category term="llms"/><category term="coding-agents"/><category term="generative-ai"/><category term="data-journalism"/><category term="github-codespaces"/><category term="codex-cli"/><category term="datasette"/><category term="claude-code"/><category term="python"/><category term="leaflet"/><category term="geospatial"/></entry><entry><title>datasette-files 0.1a1</title><link href="https://simonwillison.net/2026/Feb/20/datasette-files/#atom-tag" rel="alternate"/><published>2026-02-20T00:36:39+00:00</published><updated>2026-02-20T00:36:39+00:00</updated><id>https://simonwillison.net/2026/Feb/20/datasette-files/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-files/releases/tag/0.1a1"&gt;datasette-files 0.1a1&lt;/a&gt;&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette-files-s3 0.1a0</title><link href="https://simonwillison.net/2026/Feb/20/datasette-files-s3/#atom-tag" rel="alternate"/><published>2026-02-20T00:32:25+00:00</published><updated>2026-02-20T00:32:25+00:00</updated><id>https://simonwillison.net/2026/Feb/20/datasette-files-s3/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-files-s3/releases/tag/0.1a0"&gt;datasette-files-s3 0.1a0&lt;/a&gt;&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette-files 0.1a0</title><link href="https://simonwillison.net/2026/Feb/19/datasette-files/#atom-tag" rel="alternate"/><published>2026-02-19T22:25:14+00:00</published><updated>2026-02-19T22:25:14+00:00</updated><id>https://simonwillison.net/2026/Feb/19/datasette-files/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-files/releases/tag/0.1a0"&gt;datasette-files 0.1a0&lt;/a&gt;&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette-showboat 0.1a1</title><link href="https://simonwillison.net/2026/Feb/18/datasette-showboat/#atom-tag" rel="alternate"/><published>2026-02-18T12:53:00+00:00</published><updated>2026-02-18T12:53:00+00:00</updated><id>https://simonwillison.net/2026/Feb/18/datasette-showboat/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/datasette-showboat/releases/tag/0.1a1"&gt;datasette-showboat 0.1a1&lt;/a&gt;&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/showboat"&gt;showboat&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="showboat"/><category term="datasette"/></entry><entry><title>Two new Showboat tools: Chartroom and datasette-showboat</title><link href="https://simonwillison.net/2026/Feb/17/chartroom-and-datasette-showboat/#atom-tag" rel="alternate"/><published>2026-02-17T00:43:45+00:00</published><updated>2026-02-17T00:43:45+00:00</updated><id>https://simonwillison.net/2026/Feb/17/chartroom-and-datasette-showboat/#atom-tag</id><summary type="html">
    &lt;p&gt;I &lt;a href="https://simonwillison.net/2026/Feb/10/showboat-and-rodney/"&gt;introduced Showboat&lt;/a&gt; a week ago - my CLI tool that helps coding agents create Markdown documents that demonstrate the code that they have created. I've been finding new ways to use it on a daily basis, and I've just released two new tools to help get the best out of the Showboat pattern. &lt;a href="https://github.com/simonw/chartroom"&gt;Chartroom&lt;/a&gt; is a CLI charting tool that works well with Showboat, and &lt;a href="https://github.com/simonw/datasette-showboat"&gt;datasette-showboat&lt;/a&gt; lets Showboat's new remote publishing feature incrementally push documents to a Datasette instance.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/17/chartroom-and-datasette-showboat/#showboat-remote-publishing"&gt;Showboat remote publishing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/17/chartroom-and-datasette-showboat/#datasette-showboat"&gt;datasette-showboat&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/17/chartroom-and-datasette-showboat/#chartroom"&gt;Chartroom&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/17/chartroom-and-datasette-showboat/#how-i-built-chartroom"&gt;How I built Chartroom&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2026/Feb/17/chartroom-and-datasette-showboat/#the-burgeoning-showboat-ecosystem"&gt;The burgeoning Showboat ecosystem&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="showboat-remote-publishing"&gt;Showboat remote publishing&lt;/h4&gt;
&lt;p&gt;I normally use Showboat in Claude Code for web (see &lt;a href="https://simonwillison.net/2026/Feb/16/rodney-claude-code/"&gt;note from this morning&lt;/a&gt;). I've used it in several different projects in the past few days, each of them with a prompt that looks something like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Use "uvx showboat --help" to perform a very thorough investigation of what happens if you use the Python sqlite-chronicle and sqlite-history-json libraries against the same SQLite database table&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/simonw/research/blob/main/sqlite-chronicle-vs-history-json/demo.md"&gt;the resulting document&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Just telling Claude Code to run &lt;code&gt;uvx showboat --help&lt;/code&gt; is enough for it to learn how to use the tool - the &lt;a href="https://github.com/simonw/showboat/blob/main/help.txt"&gt;help text&lt;/a&gt; is designed to work as a sort of ad-hoc Skill document.&lt;/p&gt;
&lt;p&gt;The one catch with this approach is that I can't &lt;em&gt;see&lt;/em&gt; the new Showboat document until it's finished. I have to wait for Claude to commit the document plus embedded screenshots and push that to a branch in my GitHub repo - then I can view it through the GitHub interface.&lt;/p&gt;
&lt;p&gt;For a while I've been thinking it would be neat to have a remote web server of my own which Claude instances can submit updates to while they are working. Then this morning I realized Showboat might be the ideal mechanism to set that up...&lt;/p&gt;
&lt;p&gt;Showboat &lt;a href="https://github.com/simonw/showboat/releases/tag/v0.6.0"&gt;v0.6.0&lt;/a&gt; adds a new "remote" feature. It's almost invisible to users of the tool itself, instead being configured by an environment variable.&lt;/p&gt;
&lt;p&gt;Set a variable like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;export&lt;/span&gt; SHOWBOAT_REMOTE_URL=https://www.example.com/submit&lt;span class="pl-k"&gt;?&lt;/span&gt;token=xyz&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And every time you run a &lt;code&gt;showboat init&lt;/code&gt; or &lt;code&gt;showboat note&lt;/code&gt; or &lt;code&gt;showboat exec&lt;/code&gt; or &lt;code&gt;showboat image&lt;/code&gt; command the resulting document fragments will be POSTed to that API endpoint, in addition to the Showboat Markdown file itself being updated.&lt;/p&gt;
&lt;p&gt;There are &lt;a href="https://github.com/simonw/showboat/blob/v0.6.0/README.md#remote-document-streaming"&gt;full details in the Showboat README&lt;/a&gt; - it's a very simple API format, using regular POST form variables or a multipart form upload for the image attached to &lt;code&gt;showboat image&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="datasette-showboat"&gt;datasette-showboat&lt;/h4&gt;
&lt;p&gt;It's simple enough to build a webapp to receive these updates from Showboat, but I needed one that I could easily deploy and would work well with the rest of my personal ecosystem.&lt;/p&gt;
&lt;p&gt;So I had Claude Code write me a Datasette plugin that could act as a Showboat remote endpoint. I actually had this building at the same time as the Showboat remote feature, a neat example of running &lt;a href="https://simonwillison.net/2025/Oct/5/parallel-coding-agents/"&gt;parallel agents&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-showboat"&gt;datasette-showboat&lt;/a&gt;&lt;/strong&gt; is a Datasette plugin that adds a &lt;code&gt;/-/showboat&lt;/code&gt; endpoint to Datasette for viewing documents and a &lt;code&gt;/-/showboat/receive&lt;/code&gt; endpoint for receiving updates from Showboat.&lt;/p&gt;
&lt;p&gt;Here's a very quick way to try it out:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx --with datasette-showboat --prerelease=allow \
  datasette showboat.db --create \
  -s plugins.datasette-showboat.database showboat \
  -s plugins.datasette-showboat.token secret123 \
  --root --secret cookie-secret-123&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Click on the sign in as root link that shows up in the console, then navigate to &lt;a href="http://127.0.0.1:8001/-/showboat"&gt;http://127.0.0.1:8001/-/showboat&lt;/a&gt; to see the interface.&lt;/p&gt;
&lt;p&gt;Now set your environment variable to point to this instance:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;export&lt;/span&gt; SHOWBOAT_REMOTE_URL=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;http://127.0.0.1:8001/-/showboat/receive?token=secret123&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And run Showboat like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx showboat init demo.md &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Showboat Feature Demo&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Refresh that page and you should see this:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/datasette-showboat-documents.jpg" alt="Title: Showboat. Remote viewer for Showboat documents. Showboat Feature Demo 2026-02-17 00:06 · 6 chunks, UUID. To send showboat output to this server, set the SHOWBOAT_REMOTE_URL environment variable: export SHOWBOAT_REMOTE_URL=&amp;quot;http://127.0.0.1:8001/-/showboat/receive?token=your-token&amp;quot;" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Click through to the document, then start Claude Code or Codex or your agent of choice and prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Run 'uvx showboat --help' and then use showboat to add to the existing demo.md document with notes and exec and image to demonstrate the tool - fetch a placekitten for the image demo.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;code&gt;init&lt;/code&gt; command assigns a UUID and title and sends those up to Datasette.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/datasette-showboat.gif" alt="Animated demo - in the foreground a terminal window runs Claude Code, which executes various Showboat commands. In the background a Firefox window where the Showboat Feature Demo adds notes then some bash commands, then a placekitten image." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;The best part of this is that it works in Claude Code for web. Run the plugin on a server somewhere (an exercise left up to the reader - I use &lt;a href="https://fly.io/"&gt;Fly.io&lt;/a&gt; to host mine) and set that &lt;code&gt;SHOWBOAT_REMOTE_URL&lt;/code&gt; environment variable in your Claude environment, then any time you tell it to use Showboat the document it creates will be transmitted to your server and viewable in real time.&lt;/p&gt;
&lt;p&gt;I built &lt;a href="https://simonwillison.net/2026/Feb/10/showboat-and-rodney/#rodney-cli-browser-automation-designed-to-work-with-showboat"&gt;Rodney&lt;/a&gt;, a CLI browser automation tool, specifically to work with Showboat. It makes it easy to have a Showboat document load up web pages, interact with them via clicks or injected JavaScript and captures screenshots to embed in the Showboat document and show the effects.&lt;/p&gt;
&lt;p&gt;This is wildly useful for hacking on web interfaces using Claude Code for web, especially when coupled with the new remote publishing feature. I only got this stuff working this morning and I've already had several sessions where Claude Code has published screenshots of its work in progress, which I've then been able to provide feedback on directly in the Claude session while it's still working.&lt;/p&gt;
&lt;h3 id="chartroom"&gt;Chartroom&lt;/h3&gt;
&lt;p&gt;A few days ago I had another idea for a way to extend the Showboat ecosystem: what if Showboat documents could easily include charts?&lt;/p&gt;
&lt;p&gt;I sometimes fire up Claude Code for data analysis tasks, often telling it to download a SQLite database and then run queries against it to figure out interesting things from the data.&lt;/p&gt;
&lt;p&gt;With a simple CLI tool that produced PNG images I could have Claude use Showboat to build a document with embedded charts to help illustrate its findings.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonw/chartroom"&gt;Chartroom&lt;/a&gt;&lt;/strong&gt; is exactly that. It's effectively a thin wrapper around the excellent &lt;a href="https://matplotlib.org/"&gt;matplotlib&lt;/a&gt; Python library, designed to be used by coding agents to create charts that can be embedded in Showboat documents.&lt;/p&gt;
&lt;p&gt;Here's how to render a simple bar chart:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;&lt;span class="pl-c1"&gt;echo&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;name,value&lt;/span&gt;
&lt;span class="pl-s"&gt;Alice,42&lt;/span&gt;
&lt;span class="pl-s"&gt;Bob,28&lt;/span&gt;
&lt;span class="pl-s"&gt;Charlie,35&lt;/span&gt;
&lt;span class="pl-s"&gt;Diana,51&lt;/span&gt;
&lt;span class="pl-s"&gt;Eve,19&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;|&lt;/span&gt; uvx chartroom bar --csv \
  --title &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Sales by Person&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; --ylabel &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Sales&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;a target="_blank" rel="noopener noreferrer nofollow" href="https://raw.githubusercontent.com/simonw/chartroom/8812afc02e1310e9eddbb56508b06005ff2c0ed5/demo/1f6851ec-2026-02-14.png"&gt;&lt;img src="https://raw.githubusercontent.com/simonw/chartroom/8812afc02e1310e9eddbb56508b06005ff2c0ed5/demo/1f6851ec-2026-02-14.png" alt="A chart of those numbers, with a title and y-axis label" style="max-width: 100%;" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It can also do line charts, bar charts, scatter charts, and histograms - as seen in &lt;a href="https://github.com/simonw/chartroom/blob/0.2.1/demo/README.md"&gt;this demo document&lt;/a&gt; that was built using Showboat.&lt;/p&gt;
&lt;p&gt;Chartroom can also generate alt text. If you add &lt;code&gt;-f alt&lt;/code&gt; to the above it will output the alt text for the chart instead of the image:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;&lt;span class="pl-c1"&gt;echo&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;name,value&lt;/span&gt;
&lt;span class="pl-s"&gt;Alice,42&lt;/span&gt;
&lt;span class="pl-s"&gt;Bob,28&lt;/span&gt;
&lt;span class="pl-s"&gt;Charlie,35&lt;/span&gt;
&lt;span class="pl-s"&gt;Diana,51&lt;/span&gt;
&lt;span class="pl-s"&gt;Eve,19&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;|&lt;/span&gt; uvx chartroom bar --csv \
  --title &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Sales by Person&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; --ylabel &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Sales&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; -f alt&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Outputs:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Sales by Person. Bar chart of value by name — Alice: 42, Bob: 28, Charlie: 35, Diana: 51, Eve: 19
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you can use &lt;code&gt;-f html&lt;/code&gt; or &lt;code&gt;-f markdown&lt;/code&gt; to get the image tag with alt text directly:&lt;/p&gt;
&lt;div class="highlight highlight-text-md"&gt;&lt;pre&gt;&lt;span class="pl-s"&gt;![&lt;/span&gt;Sales by Person. Bar chart of value by name — Alice: 42, Bob: 28, Charlie: 35, Diana: 51, Eve: 19&lt;span class="pl-s"&gt;]&lt;/span&gt;&lt;span class="pl-s"&gt;(&lt;/span&gt;&lt;span class="pl-corl"&gt;/Users/simon/chart-7.png&lt;/span&gt;&lt;span class="pl-s"&gt;)&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I added support for Markdown images with alt text to Showboat in &lt;a href="https://github.com/simonw/showboat/releases/tag/v0.5.0"&gt;v0.5.0&lt;/a&gt;, to complement this feature of Chartroom.&lt;/p&gt;
&lt;p&gt;Finally, Chartroom has support for different &lt;a href="https://matplotlib.org/stable/gallery/style_sheets/style_sheets_reference.html"&gt;matplotlib styles&lt;/a&gt;. I had Claude build a Showboat document to demonstrate these all in one place - you can see that at &lt;a href="https://github.com/simonw/chartroom/blob/main/demo/styles.md"&gt;demo/styles.md&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="how-i-built-chartroom"&gt;How I built Chartroom&lt;/h4&gt;
&lt;p&gt;I started the Chartroom repository with my &lt;a href="https://github.com/simonw/click-app"&gt;click-app&lt;/a&gt; cookiecutter template, then told a fresh Claude Code for web session:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We are building a Python CLI tool which uses matplotlib to generate a PNG image containing a chart. It will have multiple sub commands for different chart types, controlled by command line options. Everything you need to know to use it will be available in the single "chartroom --help" output.&lt;/p&gt;
&lt;p&gt;It will accept data from files or standard input as CSV or TSV or JSON, similar to how sqlite-utils accepts data - clone simonw/sqlite-utils to /tmp for reference there. Clone matplotlib/matplotlib for reference as well&lt;/p&gt;
&lt;p&gt;It will also accept data from --sql path/to/sqlite.db "select ..." which runs in read-only mode&lt;/p&gt;
&lt;p&gt;Start by asking clarifying questions - do not use the ask user tool though it is broken - and generate a spec for me to approve&lt;/p&gt;
&lt;p&gt;Once approved proceed using red/green TDD running tests with "uv run pytest"&lt;/p&gt;
&lt;p&gt;Also while building maintain a demo/README.md document using the "uvx showboat --help" tool - each time you get a new chart type working commit the tests, implementation, root level
README update and a new version of that demo/README.md document with an inline image demo of the new chart type (which should be a UUID image filename managed by the showboat image command and should be stored in the demo/ folder&lt;/p&gt;
&lt;p&gt;Make sure "uv build" runs cleanly without complaining about extra directories but also ensure dist/ and uv.lock are in gitignore&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This got most of the work done. You can see the rest &lt;a href="https://github.com/simonw/chartroom/pulls?q=is%3Apr+is%3Aclosed"&gt;in the PRs&lt;/a&gt; that followed.&lt;/p&gt;
&lt;h4 id="the-burgeoning-showboat-ecosystem"&gt;The burgeoning Showboat ecosystem&lt;/h4&gt;
&lt;p&gt;The Showboat family of tools now consists of &lt;a href="https://github.com/simonw/showboat"&gt;Showboat&lt;/a&gt; itself, &lt;a href="https://github.com/simonw/rodney"&gt;Rodney&lt;/a&gt; for browser automation, &lt;a href="https://github.com/simonw/chartroom"&gt;Chartroom&lt;/a&gt; for charting and &lt;a href="https://github.com/simonw/datasette-showboat"&gt;datasette-showboat&lt;/a&gt; for streaming remote Showboat documents to Datasette.&lt;/p&gt;
&lt;p&gt;I'm enjoying how these tools can operate together based on a very loose set of conventions. If a tool can output a path to an image Showboat can include that image in a document. Any tool that can output text can be used with Showboat.&lt;/p&gt;
&lt;p&gt;I'll almost certainly be building more tools that fit this pattern. They're very quick to knock out!&lt;/p&gt;
&lt;p&gt;The environment variable mechanism for Showboat's remote streaming is a fun hack too - so far I'm just using it to stream documents somewhere else, but it's effectively a webhook extension mechanism that could likely be used for all sorts of things I haven't thought of yet.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-code"&gt;claude-code&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/coding-agents"&gt;coding-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/generative-ai"&gt;generative-ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/showboat"&gt;showboat&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/charting"&gt;charting&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="ai"/><category term="claude-code"/><category term="llms"/><category term="coding-agents"/><category term="ai-assisted-programming"/><category term="datasette"/><category term="generative-ai"/><category term="projects"/><category term="showboat"/><category term="charting"/></entry><entry><title>datasette-showboat 0.1a0</title><link href="https://simonwillison.net/2026/Feb/16/datasette-showboat/#atom-tag" rel="alternate"/><published>2026-02-16T19:43:11+00:00</published><updated>2026-02-16T19:43:11+00:00</updated><id>https://simonwillison.net/2026/Feb/16/datasette-showboat/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/datasette-showboat/releases/tag/0.1a0"&gt;datasette-showboat 0.1a0&lt;/a&gt;&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/showboat"&gt;showboat&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="showboat"/><category term="datasette"/></entry><entry><title>datasette-pins 0.1a7</title><link href="https://simonwillison.net/2026/Feb/5/datasette-pins/#atom-tag" rel="alternate"/><published>2026-02-05T16:34:33+00:00</published><updated>2026-02-05T16:34:33+00:00</updated><id>https://simonwillison.net/2026/Feb/5/datasette-pins/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/datasette/datasette-pins/releases/tag/0.1a7"&gt;datasette-pins 0.1a7&lt;/a&gt;&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>datasette-youtube-embed 0.2</title><link href="https://simonwillison.net/2026/Feb/4/datasette-youtube-embed/#atom-tag" rel="alternate"/><published>2026-02-04T16:46:34+00:00</published><updated>2026-02-04T16:46:34+00:00</updated><id>https://simonwillison.net/2026/Feb/4/datasette-youtube-embed/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;strong&gt;Release:&lt;/strong&gt; &lt;a href="https://github.com/simonw/datasette-youtube-embed/releases/tag/0.2"&gt;datasette-youtube-embed 0.2&lt;/a&gt;&lt;/p&gt;
    
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="datasette"/></entry><entry><title>Distributing Go binaries like sqlite-scanner through PyPI using go-to-wheel</title><link href="https://simonwillison.net/2026/Feb/4/distributing-go-binaries/#atom-tag" rel="alternate"/><published>2026-02-04T14:59:47+00:00</published><updated>2026-02-04T14:59:47+00:00</updated><id>https://simonwillison.net/2026/Feb/4/distributing-go-binaries/#atom-tag</id><summary type="html">
    &lt;p&gt;I've been exploring Go for building small, fast and self-contained binary applications recently. I'm enjoying how there's generally one obvious way to do things and the resulting code is boring and readable - and something that LLMs are very competent at writing. The one catch is distribution, but it turns out publishing Go binaries to PyPI means any Go binary can be just a &lt;code&gt;uvx package-name&lt;/code&gt; call away.&lt;/p&gt;
&lt;h4 id="sqlite-scanner"&gt;sqlite-scanner&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/simonw/sqlite-scanner"&gt;sqlite-scanner&lt;/a&gt; is my new Go CLI tool for scanning a filesystem for SQLite database files.&lt;/p&gt;
&lt;p&gt;It works by checking if the first 16 bytes of the file exactly match the SQLite magic number sequence &lt;code&gt;SQLite format 3\x00&lt;/code&gt;. It can search one or more folders recursively, spinning up concurrent goroutines to accelerate the scan. It streams out results as it finds them in plain text, JSON or newline-delimited JSON. It can optionally display the file sizes as well.&lt;/p&gt;
&lt;p&gt;To try it out you can download a release from the &lt;a href="https://github.com/simonw/sqlite-scanner/releases"&gt;GitHub releases&lt;/a&gt; - and then &lt;a href="https://support.apple.com/en-us/102445"&gt;jump through macOS hoops&lt;/a&gt; to execute an "unsafe" binary. Or you can clone the repo and compile it with Go. Or... you can run the binary like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx sqlite-scanner
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By default this will search your current directory for SQLite databases. You can pass one or more directories as arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx sqlite-scanner ~ /tmp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add &lt;code&gt;--json&lt;/code&gt; for JSON output, &lt;code&gt;--size&lt;/code&gt; to include file sizes or &lt;code&gt;--jsonl&lt;/code&gt; for newline-delimited JSON. Here's a demo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uvx sqlite-scanner ~ --jsonl --size
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/sqlite-scanner-demo.gif" alt="running that command produces a sequence of JSON objects, each with a path and a size key" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;If you haven't been uv-pilled yet you can instead install &lt;code&gt;sqlite-scanner&lt;/code&gt; using &lt;code&gt;pip install sqlite-scanner&lt;/code&gt; and then run &lt;code&gt;sqlite-scanner&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To get a permanent copy with &lt;code&gt;uv&lt;/code&gt; use &lt;code&gt;uv tool install sqlite-scanner&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="how-the-python-package-works"&gt;How the Python package works&lt;/h4&gt;
&lt;p&gt;The reason this is worth doing is that &lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;uv&lt;/code&gt; and &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt; will work together to identify the correct compiled binary for your operating system and architecture.&lt;/p&gt;
&lt;p&gt;This is driven by file names. If you visit &lt;a href="https://pypi.org/project/sqlite-scanner/#files"&gt;the PyPI downloads for sqlite-scanner&lt;/a&gt; you'll see the following files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-win_arm64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-win_amd64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-musllinux_1_2_x86_64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-musllinux_1_2_aarch64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-manylinux_2_17_x86_64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-manylinux_2_17_aarch64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sqlite_scanner-0.1.1-py3-none-macosx_10_9_x86_64.whl&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When I run &lt;code&gt;pip install sqlite-scanner&lt;/code&gt; or &lt;code&gt;uvx sqlite-scanner&lt;/code&gt; on my Apple Silicon Mac laptop Python's packaging magic ensures I get that &lt;code&gt;macosx_11_0_arm64.whl&lt;/code&gt; variant.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://tools.simonwillison.net/zip-wheel-explorer?url=https%3A%2F%2Ffiles.pythonhosted.org%2Fpackages%2F88%2Fb1%2F17a716635d2733fec53ba0a8267f85bd6b6cf882c6b29301bc711fba212c%2Fsqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl#sqlite_scanner/__init__.py"&gt;what's in the wheel&lt;/a&gt;, which is a zip file with a &lt;code&gt;.whl&lt;/code&gt; extension.&lt;/p&gt;
&lt;p&gt;In addition to the &lt;code&gt;bin/sqlite-scanner&lt;/code&gt; the most important file is &lt;code&gt;sqlite_scanner/__init__.py&lt;/code&gt; which includes the following:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;get_binary_path&lt;/span&gt;():
    &lt;span class="pl-s"&gt;"""Return the path to the bundled binary."""&lt;/span&gt;
    &lt;span class="pl-s1"&gt;binary&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;path&lt;/span&gt;.&lt;span class="pl-c1"&gt;join&lt;/span&gt;(&lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;path&lt;/span&gt;.&lt;span class="pl-c1"&gt;dirname&lt;/span&gt;(&lt;span class="pl-s1"&gt;__file__&lt;/span&gt;), &lt;span class="pl-s"&gt;"bin"&lt;/span&gt;, &lt;span class="pl-s"&gt;"sqlite-scanner"&lt;/span&gt;)
 
    &lt;span class="pl-c"&gt;# Ensure binary is executable on Unix&lt;/span&gt;
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;platform&lt;/span&gt; &lt;span class="pl-c1"&gt;!=&lt;/span&gt; &lt;span class="pl-s"&gt;"win32"&lt;/span&gt;:
        &lt;span class="pl-s1"&gt;current_mode&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;stat&lt;/span&gt;(&lt;span class="pl-s1"&gt;binary&lt;/span&gt;).&lt;span class="pl-c1"&gt;st_mode&lt;/span&gt;
        &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-c1"&gt;not&lt;/span&gt; (&lt;span class="pl-s1"&gt;current_mode&lt;/span&gt; &lt;span class="pl-c1"&gt;&amp;amp;&lt;/span&gt; &lt;span class="pl-s1"&gt;stat&lt;/span&gt;.&lt;span class="pl-c1"&gt;S_IXUSR&lt;/span&gt;):
            &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;chmod&lt;/span&gt;(&lt;span class="pl-s1"&gt;binary&lt;/span&gt;, &lt;span class="pl-s1"&gt;current_mode&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-s1"&gt;stat&lt;/span&gt;.&lt;span class="pl-c1"&gt;S_IXUSR&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-s1"&gt;stat&lt;/span&gt;.&lt;span class="pl-c1"&gt;S_IXGRP&lt;/span&gt; &lt;span class="pl-c1"&gt;|&lt;/span&gt; &lt;span class="pl-s1"&gt;stat&lt;/span&gt;.&lt;span class="pl-c1"&gt;S_IXOTH&lt;/span&gt;)
 
    &lt;span class="pl-k"&gt;return&lt;/span&gt; &lt;span class="pl-s1"&gt;binary&lt;/span&gt;
 
 
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;main&lt;/span&gt;():
    &lt;span class="pl-s"&gt;"""Execute the bundled binary."""&lt;/span&gt;
    &lt;span class="pl-s1"&gt;binary&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;get_binary_path&lt;/span&gt;()
 
    &lt;span class="pl-k"&gt;if&lt;/span&gt; &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;platform&lt;/span&gt; &lt;span class="pl-c1"&gt;==&lt;/span&gt; &lt;span class="pl-s"&gt;"win32"&lt;/span&gt;:
        &lt;span class="pl-c"&gt;# On Windows, use subprocess to properly handle signals&lt;/span&gt;
        &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;exit&lt;/span&gt;(&lt;span class="pl-s1"&gt;subprocess&lt;/span&gt;.&lt;span class="pl-c1"&gt;call&lt;/span&gt;([&lt;span class="pl-s1"&gt;binary&lt;/span&gt;] &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;argv&lt;/span&gt;[&lt;span class="pl-c1"&gt;1&lt;/span&gt;:]))
    &lt;span class="pl-k"&gt;else&lt;/span&gt;:
        &lt;span class="pl-c"&gt;# On Unix, exec replaces the process&lt;/span&gt;
        &lt;span class="pl-s1"&gt;os&lt;/span&gt;.&lt;span class="pl-c1"&gt;execvp&lt;/span&gt;(&lt;span class="pl-s1"&gt;binary&lt;/span&gt;, [&lt;span class="pl-s1"&gt;binary&lt;/span&gt;] &lt;span class="pl-c1"&gt;+&lt;/span&gt; &lt;span class="pl-s1"&gt;sys&lt;/span&gt;.&lt;span class="pl-c1"&gt;argv&lt;/span&gt;[&lt;span class="pl-c1"&gt;1&lt;/span&gt;:])&lt;/pre&gt;
&lt;p&gt;That &lt;code&gt;main()&lt;/code&gt; method - also called from &lt;code&gt;sqlite_scanner/__main__.py&lt;/code&gt; - locates the binary and executes it when the Python package itself is executed, using the &lt;code&gt;sqlite-scanner = sqlite_scanner:main&lt;/code&gt; entry point defined in the wheel.&lt;/p&gt;
&lt;h4 id="which-means-we-can-use-it-as-a-dependency"&gt;Which means we can use it as a dependency&lt;/h4&gt;
&lt;p&gt;Using PyPI as a distribution platform for Go binaries feels a tiny bit abusive, albeit &lt;a href="https://simonwillison.net/2022/May/23/bundling-binary-tools-in-python-wheels/"&gt;there is plenty of precedent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ll justify it by pointing out that this means &lt;strong&gt;we can use Go binaries as dependencies&lt;/strong&gt; for other Python packages now.&lt;/p&gt;
&lt;p&gt;That's genuinely useful! It means that any functionality which is available in a cross-platform Go binary can now be subsumed into a Python package. Python is really good at running subprocesses so this opens up a whole world of useful tricks that we can bake into our Python tools.&lt;/p&gt;
&lt;p&gt;To demonstrate this, I built &lt;a href="https://github.com/simonw/datasette-scan"&gt;datasette-scan&lt;/a&gt; - a new Datasette plugin which depends on &lt;code&gt;sqlite-scanner&lt;/code&gt; and then uses that Go binary to scan a folder for SQLite databases and attach them to a Datasette instance.&lt;/p&gt;
&lt;p&gt;Here's how to use that (without even installing anything first, thanks &lt;code&gt;uv&lt;/code&gt;) to explore any SQLite databases in your Downloads folder:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run --with datasette-scan datasette scan &lt;span class="pl-k"&gt;~&lt;/span&gt;/Downloads&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If you peek at the code you'll see it &lt;a href="https://github.com/simonw/datasette-scan/blob/1a2b6d1e6b04c8cd05f5676ff7daa877efd99f08/pyproject.toml#L14"&gt;depends on sqlite-scanner&lt;/a&gt; in &lt;code&gt;pyproject.toml&lt;/code&gt; and calls it using &lt;code&gt;subprocess.run()&lt;/code&gt; against &lt;code&gt;sqlite_scanner.get_binary_path()&lt;/code&gt; in its own &lt;a href="https://github.com/simonw/datasette-scan/blob/1a2b6d1e6b04c8cd05f5676ff7daa877efd99f08/datasette_scan/__init__.py#L38-L58"&gt;scan_directories() function&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've been exploring this pattern for other, non-Go binaries recently - here's &lt;a href="https://github.com/simonw/tools/blob/main/python/livestream-gif.py"&gt;a recent script&lt;/a&gt; that depends on &lt;a href="https://pypi.org/project/static-ffmpeg/"&gt;static-ffmpeg&lt;/a&gt; to ensure that &lt;code&gt;ffmpeg&lt;/code&gt; is available for the script to use.&lt;/p&gt;
&lt;h4 id="building-python-wheels-from-go-packages-with-go-to-wheel"&gt;Building Python wheels from Go packages with go-to-wheel&lt;/h4&gt;
&lt;p&gt;After trying this pattern myself a couple of times I realized it would be useful to have a tool to automate the process.&lt;/p&gt;
&lt;p&gt;I first &lt;a href="https://claude.ai/share/2d9ced56-b3e8-4651-83cc-860b9b419187"&gt;brainstormed with Claude&lt;/a&gt; to check that there was no existing tool to do this. It pointed me to &lt;a href="https://www.maturin.rs/bindings.html#bin"&gt;maturin bin&lt;/a&gt; which helps distribute Rust projects using Python wheels, and &lt;a href="https://github.com/Bing-su/pip-binary-factory"&gt;pip-binary-factory&lt;/a&gt; which bundles all sorts of other projects, but did not identify anything that addressed the exact problem I was looking to solve.&lt;/p&gt;
&lt;p&gt;So I &lt;a href="https://gisthost.github.io/?41f04e4eb823b1ceb888d9a28c2280dd/index.html"&gt;had Claude Code for web build the first version&lt;/a&gt;, then refined the code locally on my laptop with the help of more Claude Code and a little bit of OpenAI Codex too, just to mix things up.&lt;/p&gt;
&lt;p&gt;The full documentation is in the &lt;a href="https://github.com/simonw/go-to-wheel"&gt;simonw/go-to-wheel&lt;/a&gt; repository. I've published that tool to PyPI so now you can run it using:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx go-to-wheel --help&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;sqlite-scanner&lt;/code&gt; package you can &lt;a href="https://pypi.org/project/sqlite-scanner/"&gt;see on PyPI&lt;/a&gt; was built using &lt;code&gt;go-to-wheel&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx go-to-wheel &lt;span class="pl-k"&gt;~&lt;/span&gt;/dev/sqlite-scanner \
  --set-version-var main.version \
  --version 0.1.1 \
  --readme README.md \
  --author &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Simon Willison&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt; \
  --url https://github.com/simonw/sqlite-scanner \
  --description &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;Scan directories for SQLite databases&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This created a set of wheels in the &lt;code&gt;dist/&lt;/code&gt; folder. I tested one of them like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run --with dist/sqlite_scanner-0.1.1-py3-none-macosx_11_0_arm64.whl \
  sqlite-scanner --version&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When that spat out the correct version number I was confident everything had worked as planned, so I pushed the whole set of wheels to PyPI using &lt;code&gt;twine upload&lt;/code&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uvx twine upload dist/&lt;span class="pl-k"&gt;*&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I had to paste in a PyPI API token I had saved previously.&lt;/p&gt;
&lt;h4 id="i-expect-to-use-this-pattern-a-lot"&gt;I expect to use this pattern a lot&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;sqlite-scanner&lt;/code&gt; is very clearly meant as a proof-of-concept for this wider pattern - Python is very much capable of recursively crawling a directory structure looking for files that start with a specific byte prefix on its own!&lt;/p&gt;
&lt;p&gt;That said, I think there's a &lt;em&gt;lot&lt;/em&gt; to be said for this pattern. Go is a great complement to Python - it's fast, compiles to small self-contained binaries, has excellent concurrency support and a rich ecosystem of libraries.&lt;/p&gt;
&lt;p&gt;Go is similar to Python in that it has a strong standard library. Go is particularly good for HTTP tooling - I've built several HTTP proxies in the past using Go's excellent &lt;code&gt;net/http/httputil.ReverseProxy&lt;/code&gt; handler.&lt;/p&gt;
&lt;p&gt;I've also been experimenting with &lt;a href="https://github.com/wazero/wazero"&gt;wazero&lt;/a&gt;, Go's robust and mature zero dependency WebAssembly runtime as part of my ongoing quest for the ideal sandbox for running untrusted code. &lt;a href="https://github.com/simonw/research/tree/main/wasm-repl-cli"&gt;Here's my latest experiment&lt;/a&gt; with that library.&lt;/p&gt;
&lt;p&gt;Being able to seamlessly integrate Go binaries into Python projects without the end user having to think about Go at all - they &lt;code&gt;pip install&lt;/code&gt; and everything Just Works - feels like a valuable addition to my toolbox.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/go"&gt;go&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/packaging"&gt;packaging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="uv"/><category term="go"/><category term="pypi"/><category term="packaging"/><category term="ai-assisted-programming"/><category term="python"/><category term="datasette"/><category term="projects"/><category term="sqlite"/></entry></feed>