<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: jinja</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/jinja.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-10-20T19:43:15+00:00</updated><author><name>Simon Willison</name></author><entry><title>Claude Code for web - a new asynchronous coding agent from Anthropic</title><link href="https://simonwillison.net/2025/Oct/20/claude-code-for-web/#atom-tag" rel="alternate"/><published>2025-10-20T19:43:15+00:00</published><updated>2025-10-20T19:43:15+00:00</updated><id>https://simonwillison.net/2025/Oct/20/claude-code-for-web/#atom-tag</id><summary type="html">
    &lt;p&gt;Anthropic launched Claude Code for web this morning. It's an &lt;a href="https://simonwillison.net/tags/async-coding-agents/"&gt;asynchronous coding agent&lt;/a&gt; - their answer to OpenAI's &lt;a href="https://simonwillison.net/2025/May/16/openai-codex/"&gt;Codex Cloud&lt;/a&gt; and &lt;a href="https://simonwillison.net/2025/May/19/jules/"&gt;Google's Jules&lt;/a&gt;, and has a very similar shape. I had preview access over the weekend and I've already seen some very promising results from it.&lt;/p&gt;
&lt;p&gt;It's available online at &lt;a href="https://claude.ai"&gt;claude.ai/code&lt;/a&gt; and shows up as a tab in the Claude iPhone app as well:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/claude-code-for-web.jpg" alt="Screenshot of Claude AI interface showing a conversation about updating a README file. The left sidebar shows &amp;quot;Claude&amp;quot; at the top, followed by navigation items: &amp;quot;Chats&amp;quot;, &amp;quot;Projects&amp;quot;, &amp;quot;Artifacts&amp;quot;, and &amp;quot;Code&amp;quot; (highlighted). Below that is &amp;quot;Starred&amp;quot; section listing several items with trash icons: &amp;quot;LLM&amp;quot;, &amp;quot;Python app&amp;quot;, &amp;quot;Check my post&amp;quot;, &amp;quot;Artifacts&amp;quot;, &amp;quot;Summarize&amp;quot;, and &amp;quot;Alt text writer&amp;quot;. The center panel shows a conversation list with items like &amp;quot;In progress&amp;quot;, &amp;quot;Run System C&amp;quot;, &amp;quot;Idle&amp;quot;, &amp;quot;Update Rese&amp;quot;, &amp;quot;Run Matplotl&amp;quot;, &amp;quot;Run Marketin&amp;quot;, &amp;quot;WebAssembl&amp;quot;, &amp;quot;Benchmark M&amp;quot;, &amp;quot;Build URL Qu&amp;quot;, and &amp;quot;Add Read-Or&amp;quot;. The right panel displays the active conversation titled &amp;quot;Update Research Project README&amp;quot; showing a task to update a GitHub README file at https://github.com/simonw/research/blob/main/deepseek-ocr-nvidia-spark/README.md, followed by Claude's response and command outputs showing file listings with timestamps from Oct 20 17:53." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;As far as I can tell it's their latest &lt;a href="https://www.claude.com/product/claude-code"&gt;Claude Code CLI&lt;/a&gt; app wrapped in a container (Anthropic are getting &lt;em&gt;really&lt;/em&gt; &lt;a href="https://simonwillison.net/2025/Sep/9/claude-code-interpreter/"&gt;good at containers&lt;/a&gt; these days) and configured to &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;. It appears to behave exactly the same as the CLI tool, and includes a neat "teleport" feature which can copy both the chat transcript and the edited files down to your local Claude Code CLI tool if you want to take over locally.&lt;/p&gt;
&lt;p&gt;It's very straight-forward to use. You point Claude Code for web at a GitHub repository, select an environment (fully locked down, restricted to an allow-list of domains or configured to access domains of your choosing, including "*" for everything) and kick it off with a prompt.&lt;/p&gt;
&lt;p&gt;While it's running you can send it additional prompts which are queued up and executed after it completes its current step.&lt;/p&gt;
&lt;p&gt;Once it's done it opens a branch on your repo with its work and can optionally open a pull request.&lt;/p&gt;
&lt;h4 id="putting-claude-code-for-web-to-work"&gt;Putting Claude Code for web to work&lt;/h4&gt;
&lt;p&gt;Claude Code for web's PRs are indistinguishable from Claude Code CLI's, so Anthropic told me it was OK to submit those against public repos even during the private preview. Here are some examples from this weekend:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/tools/pull/73"&gt;Add query-string-stripper.html tool&lt;/a&gt; against my simonw/tools repo - a &lt;em&gt;very&lt;/em&gt; simple task that creates (and deployed via GitHub Pages) this &lt;a href="https://tools.simonwillison.net/query-string-stripper"&gt;query-string-stripper&lt;/a&gt; tool.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/research/tree/main/minijinja-vs-jinja2"&gt;minijinja vs jinja2 Performance Benchmark&lt;/a&gt; - I ran this against a private repo and then copied the results here, so no PR. Here's &lt;a href="https://github.com/simonw/research/blob/main/minijinja-vs-jinja2/README.md#the-prompt"&gt;the prompt&lt;/a&gt; I used.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/simonw/research/pull/1"&gt;Update deepseek-ocr README to reflect successful project completion&lt;/a&gt; - I noticed that the README produced by Claude Code CLI for &lt;a href="https://simonwillison.net/2025/Oct/20/deepseek-ocr-claude-code/"&gt;this project&lt;/a&gt; was misleadingly out of date, so I had Claude Code for web fix the problem.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That second example is the most interesting. I saw &lt;a href="https://x.com/mitsuhiko/status/1980034078297514319"&gt;a tweet from Armin&lt;/a&gt; about his &lt;a href="https://github.com/mitsuhiko/minijinja"&gt;MiniJinja&lt;/a&gt; Rust template language &lt;a href="https://github.com/mitsuhiko/minijinja/pull/841"&gt;adding support&lt;/a&gt; for Python 3.14 free threading. I hadn't realized that project &lt;em&gt;had&lt;/em&gt; Python bindings, so I decided it would be interesting to see a quick performance comparison between MiniJinja and Jinja2.&lt;/p&gt;
&lt;p&gt;I ran Claude Code for web against a private repository with a completely open environment (&lt;code&gt;*&lt;/code&gt; in the allow-list) and prompted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’m interested in benchmarking the Python bindings for &lt;a href="https://github.com/mitsuhiko/minijinja"&gt;https://github.com/mitsuhiko/minijinja&lt;/a&gt; against the equivalente template using Python jinja2&lt;/p&gt;
&lt;p&gt;Design and implement a benchmark for this. It should use the latest main checkout of minijinja and the latest stable release of jinja2. The benchmark should use the uv version of Python 3.14 and should test both the regular 3.14 and the 3.14t free threaded version - so four scenarios total&lt;/p&gt;
&lt;p&gt;The benchmark should run against a reasonably complicated example of a template, using template inheritance and loops and such like In the PR include a shell script to run the entire benchmark, plus benchmark implantation, plus markdown file describing the benchmark and the results in detail, plus some illustrative charts created using matplotlib&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I entered this into the Claude iPhone app on my mobile keyboard, hence the typos.&lt;/p&gt;
&lt;p&gt;It churned away for a few minutes and gave me exactly what I asked for. Here's one of the &lt;a href="https://github.com/simonw/research/tree/main/minijinja-vs-jinja2/charts"&gt;four charts&lt;/a&gt; it created:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2025/minijinja-timeline.jpg" alt="Line chart titled &amp;quot;Rendering Time Across Iterations&amp;quot; showing rendering time in milliseconds (y-axis, ranging from approximately 1.0 to 2.5 ms) versus iteration number (x-axis, ranging from 0 to 200+). Four different lines represent different versions: minijinja (3.14t) shown as a solid blue line, jinja2 (3.14) as a solid orange line, minijinja (3.14) as a solid green line, and jinja2 (3.14t) as a dashed red line. The green line (minijinja 3.14) shows consistently higher rendering times with several prominent spikes reaching 2.5ms around iterations 25, 75, and 150. The other three lines show more stable, lower rendering times between 1.0-1.5ms with occasional fluctuations." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;(I was surprised to see MiniJinja out-performed by Jinja2, but I guess Jinja2 has had a decade of clever performance optimizations and doesn't need to deal with any extra overhead of calling out to Rust.)&lt;/p&gt;
&lt;p&gt;Note that I would likely have got the &lt;em&gt;exact same&lt;/em&gt; result running this prompt against Claude CLI on my laptop. The benefit of Claude Code for web is entirely in its convenience as a way of running these tasks in a hosted container managed by Anthropic, with a pleasant web and mobile UI layered over the top.&lt;/p&gt;
&lt;h4 id="anthropic-are-framing-this-as-part-of-their-sandboxing-strategy"&gt;Anthropic are framing this as part of their sandboxing strategy&lt;/h4&gt;
&lt;p&gt;It's interesting how Anthropic chose to announce this new feature: the product launch is buried half way down their new engineering blog post &lt;a href="https://www.anthropic.com/engineering/claude-code-sandboxing"&gt;Beyond permission prompts: making Claude Code more secure and autonomous&lt;/a&gt;, which starts like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Claude Code's new sandboxing features, a bash tool and Claude Code on the web, reduce permission prompts and increase user safety by enabling two boundaries: filesystem and network isolation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm &lt;em&gt;very&lt;/em&gt; excited to hear that Claude Code CLI is taking sandboxing more seriously. I've not yet dug into the details of that - it looks like it's using seatbelt on macOS and &lt;a href="https://github.com/containers/bubblewrap"&gt;Bubblewrap&lt;/a&gt; on Linux.&lt;/p&gt;

&lt;p&gt;Anthropic released a new open source (Apache 2) library, &lt;a href="https://github.com/anthropic-experimental/sandbox-runtime"&gt;anthropic-experimental/sandbox-runtime&lt;/a&gt;, with their implementation of this so far.&lt;/p&gt;

&lt;p&gt;Filesystem sandboxing is relatively easy. The harder problem is network isolation, which they describe like this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Network isolation&lt;/strong&gt;, by only allowing internet access through a unix domain socket connected to a proxy server running outside the sandbox. This proxy server enforces restrictions on the domains that a process can connect to, and handles user confirmation for newly requested domains. And if you’d like further-increased security, we also support customizing this proxy to enforce arbitrary rules on outgoing traffic.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is &lt;em&gt;crucial&lt;/em&gt; to protecting against both prompt injection and &lt;a href="https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/"&gt;lethal trifecta&lt;/a&gt; attacks. The best way to prevent lethal trifecta attacks is to cut off one of the three legs, and network isolation is how you remove the data exfiltration leg that allows successful attackers to steal your data.&lt;/p&gt;
&lt;p&gt;If you run Claude Code for web in "No network access" mode you have nothing to worry about.&lt;/p&gt;
&lt;p&gt;I'm a little bit nervous about their "Trusted network access" environment. It's intended to only allow access to domains relating to dependency installation, but the &lt;a href="https://docs.claude.com/en/docs/claude-code/claude-code-on-the-web#default-allowed-domains"&gt;default domain list&lt;/a&gt; has dozens of entries which makes me nervous about unintended exfiltration vectors sneaking through.&lt;/p&gt;
&lt;p&gt;You can also configure a custom environment with your own allow-list. I have one called "Everything" which allow-lists "*", because for projects like my MiniJinja/Jinja2 comparison above there are no secrets or source code involved that need protecting.&lt;/p&gt;
&lt;p&gt;I see Anthropic's focus on sandboxes as an acknowledgment that coding agents run in YOLO mode (&lt;code&gt;--dangerously-skip-permissions&lt;/code&gt; and the like) are &lt;em&gt;enormously&lt;/em&gt; more valuable and productive than agents where you have to approve their every step.&lt;/p&gt;
&lt;p&gt;The challenge is making it convenient and easy to run them safely. This kind of sandboxing kind is the only approach to safety that feels credible to me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Update&lt;/strong&gt;: A note on cost: I'm currently using a Claude "Max" plan that Anthropic gave me in order to test some of their features, so I don't have a good feeling for how Claude Code would cost for these kinds of projects.&lt;/p&gt;

&lt;p&gt;From running &lt;code&gt;npx ccusage@latest&lt;/code&gt; (an &lt;a href="https://github.com/ryoppippi/ccusage"&gt;unofficial cost estimate tool&lt;/a&gt;) it looks like I'm using between $1 and $5 worth of daily Claude CLI invocations at the moment.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/armin-ronacher"&gt;armin-ronacher&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jinja"&gt;jinja&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sandboxing"&gt;sandboxing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/security"&gt;security&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/prompt-injection"&gt;prompt-injection&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/llms"&gt;llms&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/anthropic"&gt;anthropic&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude"&gt;claude&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/claude-code"&gt;claude-code&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/lethal-trifecta"&gt;lethal-trifecta&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/async-coding-agents"&gt;async-coding-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/disclosures"&gt;disclosures&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="armin-ronacher"/><category term="jinja"/><category term="sandboxing"/><category term="security"/><category term="ai"/><category term="prompt-injection"/><category term="generative-ai"/><category term="llms"/><category term="anthropic"/><category term="claude"/><category term="coding-agents"/><category term="claude-code"/><category term="lethal-trifecta"/><category term="async-coding-agents"/><category term="disclosures"/></entry><entry><title>MiniJinja: Learnings from Building a Template Engine in Rust</title><link href="https://simonwillison.net/2024/Aug/27/minijinja/#atom-tag" rel="alternate"/><published>2024-08-27T15:47:19+00:00</published><updated>2024-08-27T15:47:19+00:00</updated><id>https://simonwillison.net/2024/Aug/27/minijinja/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://lucumr.pocoo.org/2024/8/27/minijinja/"&gt;MiniJinja: Learnings from Building a Template Engine in Rust&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Armin Ronacher's &lt;a href="https://github.com/mitsuhiko/minijinja/"&gt;MiniJinja&lt;/a&gt; is his re-implemenation of the Python &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja2&lt;/a&gt; (originally built by Armin) templating language in Rust.&lt;/p&gt;
&lt;p&gt;It's nearly three years old now and, in Armin's words, "it's at almost feature parity with Jinja2 and quite enjoyable to use".&lt;/p&gt;
&lt;p&gt;The WebAssembly compiled demo in the &lt;a href="https://mitsuhiko.github.io/minijinja-playground/"&gt;MiniJinja Playground&lt;/a&gt; is fun to try out. It includes the ability to output instructions, so you can see how this:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-django"&gt;&lt;pre&gt;&amp;lt;&lt;span class="pl-ent"&gt;ul&lt;/span&gt;&amp;gt;
  &lt;span class="pl-e"&gt;{%&lt;/span&gt;- &lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s"&gt;item&lt;/span&gt; &lt;span class="pl-k"&gt;in&lt;/span&gt; &lt;span class="pl-s"&gt;nav&lt;/span&gt; &lt;span class="pl-e"&gt;%}&lt;/span&gt;
    &amp;lt;&lt;span class="pl-ent"&gt;li&lt;/span&gt;&amp;gt;{{ item.title }}&amp;lt;/&lt;span class="pl-ent"&gt;a&lt;/span&gt;&amp;gt;
  &lt;span class="pl-e"&gt;{%&lt;/span&gt;- &lt;span class="pl-k"&gt;endfor&lt;/span&gt; &lt;span class="pl-e"&gt;%}&lt;/span&gt;
&amp;lt;/&lt;span class="pl-ent"&gt;ul&lt;/span&gt;&amp;gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Becomes this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;0   EmitRaw "&amp;lt;ul&amp;gt;"
1   Lookup  "nav"
2   PushLoop    1
3   Iterate 11
4   StoreLocal  "item"
5   EmitRaw "\n &amp;lt;li&amp;gt;"
6   Lookup  "item"
7   GetAttr "title"
8   Emit    
9   EmitRaw "&amp;lt;/a&amp;gt;"
10  Jump    3
11  PopFrame    
12  EmitRaw "\n&amp;lt;/ul&amp;gt;"&lt;/code&gt;&lt;/pre&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://hachyderm.io/@mitsuhiko/113034016600122789"&gt;@mitsuhiko&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/armin-ronacher"&gt;armin-ronacher&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jinja"&gt;jinja&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/templates"&gt;templates&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rust"&gt;rust&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;&lt;/p&gt;



</summary><category term="armin-ronacher"/><category term="jinja"/><category term="templates"/><category term="rust"/><category term="webassembly"/></entry><entry><title>Datasette Enrichments: a new plugin framework for augmenting your data</title><link href="https://simonwillison.net/2023/Dec/1/datasette-enrichments/#atom-tag" rel="alternate"/><published>2023-12-01T20:14:50+00:00</published><updated>2023-12-01T20:14:50+00:00</updated><id>https://simonwillison.net/2023/Dec/1/datasette-enrichments/#atom-tag</id><summary type="html">
    &lt;p&gt;Today I'm releasing &lt;strong&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments"&gt;datasette-enrichments&lt;/a&gt;&lt;/strong&gt;, a new feature for Datasette which provides a framework for applying "enrichments" that can augment your data.&lt;/p&gt;
&lt;p&gt;An &lt;strong&gt;enrichment&lt;/strong&gt; is code that can be run against rows in a database table. That code can transform existing data or fetch additional data from external sources, then write that augmented data back to the database.&lt;/p&gt;
&lt;p&gt;A good example of an enrichment is &lt;strong&gt;geocoding&lt;/strong&gt;: take a table with an address column, run each address through a geocoding API, then write the resulting location back to &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt; columns on the same table.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2023/enrichments-geocoder.jpg" alt="Datasette screenshot: Enrich data in Film_Locations_in_San_Francisco. 2,084 rows selected. OpenCage geocoder. Geocode to latitude/longitude points using OpenCage. Geocode input: {{ Locations }}, San Francisco, California. Store JSON in column checkbox. Enrich data button." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Each enrichment is itself a plugin. The Datasette enrichments system is designed to be easily extended with new enrichment types, to serve a wide variety of use-cases.&lt;/p&gt;
&lt;h4 id="demonstrating-enrichments"&gt;Demonstrating enrichments&lt;/h4&gt;
&lt;p&gt;I've made &lt;a href="https://www.youtube.com/watch?v=HqKlJCgdjfg"&gt;a video demo&lt;/a&gt; to demonstrate the new capabilities introduced by this plugin.&lt;/p&gt;

&lt;iframe style="max-width: 100%" width="560" height="315" src="https://www.youtube-nocookie.com/embed/HqKlJCgdjfg" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen="allowfullscreen"&gt; &lt;/iframe&gt;

&lt;p&gt;The video shows off two enrichments: &lt;code&gt;datasette-enrichments-gpt&lt;/code&gt; for running prompts against OpenAI's GPT language models, and &lt;code&gt;datasette-enrichments-opencage&lt;/code&gt; for geocoding addresses.&lt;/p&gt;
&lt;p&gt;In the video I demonstrate the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Uploading a CSV file of &lt;a href="https://data.sfgov.org/Culture-and-Recreation/Film-Locations-in-San-Francisco/yitu-d5am"&gt;Film Locations in San Francisco&lt;/a&gt; to create a table&lt;/li&gt;
&lt;li&gt;Running the OpenCage geocoder enrichment against those rows to populate &lt;code&gt;latitude&lt;/code&gt; and &lt;code&gt;longitude&lt;/code&gt; columns&lt;/li&gt;
&lt;li&gt;... which results in a map being displayed on the table page using &lt;a href="https://datasette.io/plugins/datasette-cluster-map"&gt;datasette-cluster-map&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Applying the GPT enrichment to write terrible haikus about every museum on my &lt;a href="https://www.niche-museums.com/"&gt;Niche Museums&lt;/a&gt; website&lt;/li&gt;
&lt;li&gt;Extracting JSON with key people and dates from each museum descriptions&lt;/li&gt;
&lt;li&gt;Using the GPT-4 Vision API to generate detailed descriptions of photographs displayed on the site&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="enrichments-so-far"&gt;Enrichments so far&lt;/h4&gt;
&lt;p&gt;I'm releasing four enrichment plugins today:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments-opencage"&gt;datasette-enrichments-opencage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments-jinja"&gt;datasette-enrichments-jinja&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments-gpt"&gt;datasette-enrichments-gpt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments-re2"&gt;datasette-enrichments-re2&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've also published documentation on &lt;a href="https://enrichments.datasette.io/en/stable/developing.html"&gt;developing a new enrichment&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="datasette-enrichments-gpt"&gt;datasette-enrichments-gpt&lt;/h4&gt;
&lt;p&gt;The most interesting enrichment I'm releasing today is &lt;strong&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments-gpt"&gt;datasette-enrichments-gpt&lt;/a&gt;&lt;/strong&gt;. This enrichment provides access to various OpenAI language models, allowing you to do some really interesting things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Execute a prompt against data pulled from columns in each row of a table and store the result&lt;/li&gt;
&lt;li&gt;Run prompts against URLs to images using the GPT-4 Vision API&lt;/li&gt;
&lt;li&gt;Extract structured data from text&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I demonstrated all three of these in the video. Here's how I used JSON object mode to extract JSON structured data for people and years from the museum descriptions, using this prompt:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Return JSON: {"people": [...], "years": [...]}&lt;/p&gt;
&lt;p&gt;Each person should be {"name": "...", "bio": "One line bio"}&lt;/p&gt;
&lt;p&gt;Each year should be {"year": 1893, "description": "What happened in that year"}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2023/enrich-structured.jpg" alt="Enrich data in museums. 110 rows selected. AI analysis with OpenAI GPT. Model gpt-4-turbo. Prompt {{ description }}. System prompt: Return JSON: {&amp;quot;people&amp;quot;: ..., &amp;quot;years&amp;quot;: ...} Each person should be {&amp;quot;name&amp;quot;: &amp;quot;...&amp;quot;, &amp;quot;bio&amp;quot;: &amp;quot;One line bio&amp;quot;} Each year should be {&amp;quot;year&amp;quot;: 1893, &amp;quot;description&amp;quot;: &amp;quot;What happened in that year&amp;quot;}. JSON output is selected, output column name is extracted." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;I also ran GPT-4 Vision against images, with the prompt "describe this photo". Here's the description it gave for this photograph from the Bigfoot Discovery Museum:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In the photo, we see an elderly man with a full white beard and glasses, wearing a cap and a blue denim shirt, seated behind a cluttered desk. The desk is strewn with various items including papers, books, and what appears to be works of art or prints. The man seems engaged in conversation or explaining something, mid-gesture with his right hand.&lt;/p&gt;
&lt;p&gt;The backdrop is a room filled with bookshelves brimming with books and some items that look like filing organizers, hinting at a vast collection. The shelves are densely packed, giving the space a cozy and somewhat cluttered appearance, likely a reflection of intellectual activity and a personal workspace. Various other items such as a poster and possibly personal memorabilia can be seen on the walls adding to the character of the room.&lt;/p&gt;
&lt;p&gt;Overall, the image portrays a scholarly or artistic atmosphere, suggesting that the man could be a collector, a bookstore owner, an academic, or an artist.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2023/bigfoot-museum.jpeg" alt="The photo exactly matches that description." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;h4 id="datasette-enrichments-opencage"&gt;datasette-enrichments-opencage&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments-opencage"&gt;datasette-enrichments-opencage&lt;/a&gt;&lt;/strong&gt; provides access to the &lt;a href="https://opencagedata.com/"&gt;OpenCage geocoder&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I really like OpenCage. Many geocoders have strict restrictions on what you can do with the data they return - some of them even prohibit storing the results long-term in a database!&lt;/p&gt;
&lt;p&gt;OpenCage avoid this by carefully building on top of open data, and they also &lt;a href="https://opencagedata.com/about#geo-innovation"&gt;financially support&lt;/a&gt; some of the open data projects they rely on.&lt;/p&gt;
&lt;p&gt;This plugin (and &lt;code&gt;datasette-enrichments-gpt&lt;/code&gt;) both implement a pattern where you can configure an API key using &lt;a href="https://docs.datasette.io/en/stable/plugins.html#plugins-configuration-secret"&gt;plugin secrets&lt;/a&gt;, but if you don't do that the key will be requested from you each time you run an enrichment.&lt;/p&gt;
&lt;h4 id="datasette-enrichments-jinja"&gt;datasette-enrichments-jinja&lt;/h4&gt;
&lt;p&gt;I wanted to launch with an example of an enrichment that can execute arbitrary code against each row in a table.&lt;/p&gt;
&lt;p&gt;Running code in a sandbox in Python is notoriously difficult. I decided to use the &lt;a href="https://jinja.palletsprojects.com/en/3.1.x/sandbox/"&gt;Jinja sandbox&lt;/a&gt;, which isn't completely secure against malicious attackers but should be good enough to ensure trustworthy users don't accidentally cause too much damage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments-jinja"&gt;datasette-enrichments-jinja&lt;/a&gt;&lt;/strong&gt; can execute a &lt;a href="https://jinja.palletsprojects.com/"&gt;Jinja template&lt;/a&gt; against each row in a table and store the result.&lt;/p&gt;
&lt;p&gt;It's a small but powerful template language, and should prove useful for a number data manipulation tasks.&lt;/p&gt;
&lt;h4 id="datasette-enrichments-re2"&gt;datasette-enrichments-re2&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasette.io/plugins/datasette-enrichments-re2"&gt;datasette-enrichments-re2&lt;/a&gt;&lt;/strong&gt; provides an enrichment that can run a regular expression against a value from a table and store the result.&lt;/p&gt;
&lt;p&gt;It offers four different modes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Execute a search and replace against a column&lt;/li&gt;
&lt;li&gt;Extract the first matching result and store that in the specified column (adding a column to the table if necessary)&lt;/li&gt;
&lt;li&gt;Extract all matching results and store them as a JSON array in the specified column. If the regular expression uses named capture groups this will be an array of objects, otherwise it will be an array of strings.&lt;/li&gt;
&lt;li&gt;Execute a regular expression with named capture groups and store the results in multiple columns, one for each of those named groups&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's quite a lot of functionality bundled into one enrichment! I haven't used this for much yet myself, but I'm looking forward to exploring it further and documenting some useful patterns.&lt;/p&gt;
&lt;h4 id="writing-your-own-enrichment"&gt;Writing your own enrichment plugin&lt;/h4&gt;
&lt;p&gt;The most exciting thing about enrichments is what they can unlock in the future.&lt;/p&gt;
&lt;p&gt;I've tried to make it as easy as possible for Python developers to build their own enrichment plugins.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://enrichments.datasette.io/en/latest/developing.html"&gt;Developing a new enrichment&lt;/a&gt; documentation walks through the process of building a new enrichment plugin from scratch.&lt;/p&gt;
&lt;p&gt;Enrichments run inside Datasette using Python &lt;code&gt;asyncio&lt;/code&gt;. This is a particularly good fit for enrichments that use external APIs, since &lt;a href="https://www.python-httpx.org/"&gt;HTTPX&lt;/a&gt; makes it easy to run multiple HTTP requests in parallel.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;-opencage&lt;/code&gt; and &lt;code&gt;-gpt&lt;/code&gt; enrichments are two examples of enrichments that use HTTPX.&lt;/p&gt;
&lt;p&gt;Interested in building one? Join the new &lt;a href="https://datasette.io/discord-enrichments"&gt;#enrichments channel&lt;/a&gt; on the Datasette Discord to discuss ideas and talk about the new feature!&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/jinja"&gt;jinja&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/plugins"&gt;plugins&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/enrichments"&gt;enrichments&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="jinja"/><category term="plugins"/><category term="projects"/><category term="datasette"/><category term="enrichments"/></entry><entry><title>New Major Versions Released! Flask 2.0, Werkzeug 2.0, Jinja 3.0, Click 8.0, ItsDangerous 2.0, and MarkupSafe 2.0</title><link href="https://simonwillison.net/2021/May/12/pallets/#atom-tag" rel="alternate"/><published>2021-05-12T17:37:14+00:00</published><updated>2021-05-12T17:37:14+00:00</updated><id>https://simonwillison.net/2021/May/12/pallets/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://palletsprojects.com/blog/flask-2-0-released/"&gt;New Major Versions Released! Flask 2.0, Werkzeug 2.0, Jinja 3.0, Click 8.0, ItsDangerous 2.0, and MarkupSafe 2.0&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Huge set of releases from the Pallets team. Python 3.6+ required and comprehensive type annotations. Flask now supports async views, Jinja async templates (used extensively by Datasette) “no longer requires patching”, Click has a bunch of new code around shell tab completion, ItsDangerous supports key rotation and so much more.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/async"&gt;async&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/flask"&gt;flask&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jinja"&gt;jinja&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;&lt;/p&gt;



</summary><category term="async"/><category term="flask"/><category term="jinja"/><category term="python"/></entry><entry><title>datasette-template-sql</title><link href="https://simonwillison.net/2019/Nov/15/datasette-template-sql/#atom-tag" rel="alternate"/><published>2019-11-15T00:59:50+00:00</published><updated>2019-11-15T00:59:50+00:00</updated><id>https://simonwillison.net/2019/Nov/15/datasette-template-sql/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-template-sql"&gt;datasette-template-sql&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
New Datasette plugin, celebrating the new ability in Datasette 0.32 to have asynchronous custom template functions in Jinja (which was previously blocked by the need to support Python 3.5). The plugin adds a sql() function which can be used to execute SQL queries that are embedded directly in custom templates.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/simonw/status/1195126529304301573"&gt;@simonw&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/jinja"&gt;jinja&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sql"&gt;sql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/templates"&gt;templates&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;



</summary><category term="jinja"/><category term="projects"/><category term="sql"/><category term="templates"/><category term="datasette"/></entry><entry><title>Flask 0.1 Released</title><link href="https://simonwillison.net/2010/Apr/16/flask/#atom-tag" rel="alternate"/><published>2010-04-16T17:12:48+00:00</published><updated>2010-04-16T17:12:48+00:00</updated><id>https://simonwillison.net/2010/Apr/16/flask/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://lucumr.pocoo.org/2010/4/16/flask-0-1-released"&gt;Flask 0.1 Released&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Armin’s Flask (a Python microframework built around Werkzeug and Jinja2) is looking pretty solid for a two week old project—extensive documentation, comprehensive unit test support (and example applications with unit tests) and some very tidy API design.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/armin-ronacher"&gt;armin-ronacher&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/flask"&gt;flask&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jinja"&gt;jinja&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/microframeworks"&gt;microframeworks&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/testing"&gt;testing&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/werkzeug"&gt;werkzeug&lt;/a&gt;&lt;/p&gt;



</summary><category term="armin-ronacher"/><category term="flask"/><category term="jinja"/><category term="microframeworks"/><category term="python"/><category term="testing"/><category term="werkzeug"/></entry><entry><title>Replacing Django's Template Language With Jinja2</title><link href="https://simonwillison.net/2008/Jul/22/loose/#atom-tag" rel="alternate"/><published>2008-07-22T17:18:16+00:00</published><updated>2008-07-22T17:18:16+00:00</updated><id>https://simonwillison.net/2008/Jul/22/loose/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://lethain.com/entry/2008/jul/22/replacing-django-s-template-language-with-jinja2/"&gt;Replacing Django&amp;#x27;s Template Language With Jinja2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Part of Will Larson’s series on taking advantage of Django’s loose coupling.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jinja"&gt;jinja&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/loosecoupling"&gt;loosecoupling&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/will-larson"&gt;will-larson&lt;/a&gt;&lt;/p&gt;



</summary><category term="django"/><category term="jinja"/><category term="loosecoupling"/><category term="python"/><category term="will-larson"/></entry><entry><title>Jinja2 Final aka Jinjavitus Released</title><link href="https://simonwillison.net/2008/Jul/19/jinja2/#atom-tag" rel="alternate"/><published>2008-07-19T23:52:34+00:00</published><updated>2008-07-19T23:52:34+00:00</updated><id>https://simonwillison.net/2008/Jul/19/jinja2/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://lucumr.pocoo.org/cogitations/2008/07/17/jinja2-final-aka-jinjavitus-released/"&gt;Jinja2 Final aka Jinjavitus Released&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The Jinja template engine now has auto-escaping as an optional feature, disabled by default. Worth considering as an almost drop-in replacement for Django’s template language if features such as macros and compilation to Python code appeal to you.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/autoescaping"&gt;autoescaping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/django"&gt;django&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jinja"&gt;jinja&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;&lt;/p&gt;



</summary><category term="autoescaping"/><category term="django"/><category term="jinja"/><category term="python"/></entry></feed>