<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: github-codespaces</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/github-codespaces.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-03-16T20:12:32+00:00</updated><author><name>Simon Willison</name></author><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/data-journalism"&gt;data-journalism&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/speaking"&gt;speaking&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/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/llms"&gt;llms&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/nicar"&gt;nicar&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/codex-cli"&gt;codex-cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/leaflet"&gt;leaflet&lt;/a&gt;&lt;/p&gt;



</summary><category term="data-journalism"/><category term="geospatial"/><category term="python"/><category term="speaking"/><category term="sqlite"/><category term="ai"/><category term="datasette"/><category term="generative-ai"/><category term="llms"/><category term="github-codespaces"/><category term="nicar"/><category term="coding-agents"/><category term="claude-code"/><category term="codex-cli"/><category term="leaflet"/></entry><entry><title>simonw/codespaces-llm</title><link href="https://simonwillison.net/2025/Aug/13/codespaces-llm/#atom-tag" rel="alternate"/><published>2025-08-13T05:39:07+00:00</published><updated>2025-08-13T05:39:07+00:00</updated><id>https://simonwillison.net/2025/Aug/13/codespaces-llm/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/simonw/codespaces-llm"&gt;simonw/codespaces-llm&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;a href="https://github.com/features/codespaces"&gt;GitHub Codespaces&lt;/a&gt; provides full development environments in your browser, and is free to use with anyone with a GitHub account. Each environment has a full Linux container and a browser-based UI using VS Code.&lt;/p&gt;
&lt;p&gt;I found out today that GitHub Codespaces come with a &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; environment variable... and that token works as an API key for accessing LLMs in the &lt;a href="https://docs.github.com/en/github-models"&gt;GitHub Models&lt;/a&gt; collection, which includes &lt;a href="https://github.com/marketplace?type=models"&gt;dozens of models&lt;/a&gt; from OpenAI, Microsoft, Mistral, xAI, DeepSeek, Meta and more.&lt;/p&gt;
&lt;p&gt;Anthony Shaw's &lt;a href="https://github.com/tonybaloney/llm-github-models"&gt;llm-github-models&lt;/a&gt; plugin for my &lt;a href="https://llm.datasette.io/"&gt;LLM tool&lt;/a&gt; allows it to talk directly to GitHub Models. I filed &lt;a href="https://github.com/tonybaloney/llm-github-models/issues/49"&gt;a suggestion&lt;/a&gt; that it could pick up that &lt;code&gt;GITHUB_TOKEN&lt;/code&gt; variable automatically and Anthony &lt;a href="https://github.com/tonybaloney/llm-github-models/releases/tag/0.18.0"&gt;shipped v0.18.0&lt;/a&gt; with that feature a few hours later.&lt;/p&gt;
&lt;p&gt;... which means you can now run the following in any Python-enabled Codespaces container and get a working &lt;code&gt;llm&lt;/code&gt; command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install llm
llm install llm-github-models
llm models default github/gpt-4.1
llm "Fun facts about pelicans"
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Setting the default model to &lt;code&gt;github/gpt-4.1&lt;/code&gt; means you get free (albeit rate-limited) access to that OpenAI model.&lt;/p&gt;
&lt;p&gt;To save you from needing to even run that sequence of commands I've created a new GitHub repository, &lt;a href="https://github.com/simonw/codespaces-llm"&gt;simonw/codespaces-llm&lt;/a&gt;, which pre-installs and runs those commands for you.&lt;/p&gt;
&lt;p&gt;Anyone with a GitHub account can use this URL to launch a new Codespaces instance with a configured &lt;code&gt;llm&lt;/code&gt; terminal command ready to use:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://codespaces.new/simonw/codespaces-llm?quickstart=1"&gt;codespaces.new/simonw/codespaces-llm?quickstart=1&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of a GitHub Codespaces VS Code interface showing a README.md file for codespaces-llm repository. The file describes a GitHub Codespaces environment with LLM, Python 3.13, uv and the GitHub Copilot VS Code extension. It has a &amp;quot;Launch Codespace&amp;quot; button. Below shows a terminal tab with the command &amp;quot;llm 'Fun facts about pelicans'&amp;quot; which has generated output listing 5 pelican facts: 1. **Huge Beaks:** about their enormous beaks and throat pouches for scooping fish and water, some over a foot long; 2. **Fishing Technique:** about working together to herd fish into shallow water; 3. **Great Fliers:** about being strong fliers that migrate great distances and soar on thermals; 4. **Buoyant Bodies:** about having air sacs beneath skin and bones making them extra buoyant; 5. **Dive Bombing:** about Brown Pelicans diving dramatically from air into water to catch fish." src="https://static.simonwillison.net/static/2025/codespaces-llm.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;While putting this together I wrote up what I've learned about devcontainers so far as a TIL: &lt;a href="https://til.simonwillison.net/github/codespaces-devcontainers"&gt;Configuring GitHub Codespaces using devcontainers&lt;/a&gt;.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/til"&gt;til&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/openai"&gt;openai&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/llm"&gt;llm&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/anthony-shaw"&gt;anthony-shaw&lt;/a&gt;&lt;/p&gt;



</summary><category term="github"/><category term="projects"/><category term="python"/><category term="ai"/><category term="til"/><category term="openai"/><category term="generative-ai"/><category term="llms"/><category term="llm"/><category term="github-codespaces"/><category term="anthony-shaw"/></entry><entry><title>Weeknotes: Datasette Studio and a whole lot of blogging</title><link href="https://simonwillison.net/2024/Jun/19/datasette-studio/#atom-tag" rel="alternate"/><published>2024-06-19T04:30:26+00:00</published><updated>2024-06-19T04:30:26+00:00</updated><id>https://simonwillison.net/2024/Jun/19/datasette-studio/#atom-tag</id><summary type="html">
    &lt;p&gt;I'm still spinning back up after my trip back to the UK, so actual time spent building things has been less than I'd like. I presented &lt;a href="https://simonwillison.net/2024/Jun/17/cli-language-models/"&gt;an hour long workshop on command-line LLM usage&lt;/a&gt;, wrote five full blog entries (since my last weeknotes) and I've also been leaning more into short-form link blogging - a lot more prominent on this site now since my &lt;a href="https://simonwillison.net/2024/Jun/12/homepage-redesign/"&gt;homepage redesign&lt;/a&gt; last week.&lt;/p&gt;
&lt;h4 id="datasette-studio"&gt;Datasette Studio&lt;/h4&gt;
&lt;p&gt;I ran a workshop for a data journalism class recently which included having students try running structured data extraction using &lt;a href="https://github.com/datasette/datasette-extract"&gt;datasette-extract&lt;/a&gt;. I didn't want to talk them through installing Python etc on their own machines, so I instead took advantage of a project I've been tinkering with for a little while called &lt;strong&gt;Datasette Studio&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Datasette Studio is actually two things. The first is a &lt;a href="https://github.com/datasette/datasette-studio"&gt;distribution of Datasette&lt;/a&gt; which bundles the core application along with a selection of plugins that greatly increase its capabilities as a tool for cleaning and analyzing data. You can install that like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;pipx install datasette-studio&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then run &lt;code&gt;datasette-studio&lt;/code&gt; to start the server or &lt;code&gt;datasette-studio install xyz&lt;/code&gt; to install additional plugins.&lt;/p&gt;
&lt;p&gt;Datasette Studio runs the &lt;a href="https://docs.datasette.io/en/1.0a13/"&gt;latest Datasette 1.0 alpha&lt;/a&gt;, and will upgrade to 1.0 stable as soon as that is released.&lt;/p&gt;
&lt;p&gt;Quoting the &lt;a href="https://github.com/datasette/datasette-studio/blob/main/pyproject.toml"&gt;pyproject.toml file&lt;/a&gt;, the current list of plugins is this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/simonw/datasette-edit-schema"&gt;datasette-edit-schema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-write-ui"&gt;datasette-write-ui&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/simonw/datasette-configure-fts"&gt;datasette-configure-fts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/simonw/datasette-write"&gt;datasette-write&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/simonw/datasette-upload-csvs"&gt;datasette-upload-csvs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-enrichments"&gt;datasette-enrichments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-enrichments-quickjs"&gt;datasette-enrichments-quickjs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-enrichments-re2"&gt;datasette-enrichments-re2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-enrichments-jinja"&gt;datasette-enrichments-jinja&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/simonw/datasette-copyable"&gt;datasette-copyable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-export-database"&gt;datasette-export-database&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-enrichments-gpt"&gt;datasette-enrichments-gpt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-import"&gt;datasette-import&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-extract"&gt;datasette-extract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/datasette/datasette-secrets"&gt;datasette-secrets&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I plan to grow this list over time. A neat thing about &lt;code&gt;datasette-studio&lt;/code&gt; is that the entire application is defined by a single &lt;code&gt;pyproject.toml&lt;/code&gt; that lists those dependecies and &lt;a href="https://github.com/datasette/datasette-studio/blob/b4bdc2ceadabc3b184ff960effb4de59506c2ee2/pyproject.toml#L37-L38"&gt;sets up&lt;/a&gt; the &lt;code&gt;datasette-studio&lt;/code&gt; CLI console script, which is then &lt;a href="https://pypi.org/project/datasette-studio/"&gt;published to PyPI&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The second part of Datasette Studio is a GitHub repository that's designed to help run it in GitHub Codespaces, with a very pleasing URL:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/datasette/studio"&gt;https://github.com/datasette/studio&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Visit that page, click the green "Code" button and click "Create codespace on main" to launch a virtual machine running in GitHub's Azure environment, preconfigured to launch a private instance of Datasette as soon as the Codespace has started running.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/datasette-studio.jpg" alt="Screenshot of the GitHub Codespaces UI running Datasette Studio" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;You can then start using it directly - uploading CSVs or JSON data, or even set your own OpenAI key (using the "Manage secrets" menu item) to enable OpenAI features such as GPT enrichments and structured data extraction.&lt;/p&gt;
&lt;p&gt;I'm still fleshing out the idea, but I really like this as a starting point for a completely free Datasette trial environment that's entirely hosted (and paid for) by Microsoft/GitHub!&lt;/p&gt;
&lt;h4 id="more-blog-improvements"&gt;More blog improvements&lt;/h4&gt;
&lt;p&gt;In addition to the &lt;a href="https://simonwillison.net/2024/Jun/12/homepage-redesign/"&gt;redesign of the homepage&lt;/a&gt; - moving my linkblog and quotations out of the sidebar and into the main content, at least on desktop - I've made a couple of other tweaks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I added &lt;a href="https://simonwillison.net/2024/Jun/18/tags-with-descriptions/"&gt;optional descriptions to my tags&lt;/a&gt;, so now pages like &lt;a href="https://simonwillison.net/tags/datasette/"&gt;/tags/datasette/&lt;/a&gt; or &lt;a href="https://simonwillison.net/tags/sqliteutils/"&gt;/tags/sqliteutils/&lt;/a&gt; can clarify themselves and link to the relevant projects.&lt;/li&gt;
&lt;li&gt;I &lt;a href="https://github.com/simonw/simonwillisonblog/issues/444"&gt;started displaying images in more places&lt;/a&gt;. I've been creating "social media card" images for many of my posts for a few years, to show up when those URLs are shared in places like Mastodon or Twitter or Discord or Slack. Those images now display in various places on my blog as well, including the homepage, search results and the tag pages. My &lt;a href="https://simonwillison.net/tags/annotatedtalks/"&gt;annotatedtalks tag page&lt;/a&gt; looks a whole lot more interesting with accompanying presentation title slides.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="weeknotes-182-blog-entries"&gt;Blog entries&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://simonwillison.net/2024/Jun/17/cli-language-models/"&gt;Language models on the command-line&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonwillison.net/2024/Jun/12/homepage-redesign/"&gt;A homepage redesign for my blog's 22nd birthday&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonwillison.net/2024/Jun/10/apple-intelligence/"&gt;Thoughts on the WWDC 2024 keynote on Apple Intelligence&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonwillison.net/2024/Jun/6/accidental-prompt-injection/"&gt;Accidental prompt injection against RAG applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://simonwillison.net/2024/May/29/training-not-chatting/"&gt;Training is not the same as chatting: ChatGPT and other LLMs don't remember everything you say&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="weeknotes-182-releases"&gt;Releases&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-faiss/releases/tag/0.2.1"&gt;datasette-faiss 0.2.1&lt;/a&gt;&lt;/strong&gt; - 2024-06-17&lt;br /&gt;Maintain a FAISS index for specified Datasette tables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-cluster-map/releases/tag/0.18.2"&gt;datasette-cluster-map 0.18.2&lt;/a&gt;&lt;/strong&gt; - 2024-06-13&lt;br /&gt;Datasette plugin that shows a map for any data with latitude/longitude columns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette/releases/tag/0.64.7"&gt;datasette 0.64.7&lt;/a&gt;&lt;/strong&gt; - 2024-06-12&lt;br /&gt;An open source multi-tool for exploring and publishing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/datasette/datasette-studio/releases/tag/0.1a4"&gt;datasette-studio 0.1a4&lt;/a&gt;&lt;/strong&gt; - 2024-06-05&lt;br /&gt;Datasette pre-configured with useful plugins. Experimental alpha.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="weeknotes-182-tils"&gt;TILs&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://til.simonwillison.net/postgresql/upgrade-postgres-app"&gt;Upgrade Postgres.app on macOS&lt;/a&gt; - 2024-06-16&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://til.simonwillison.net/cloudflare/redirect-rules"&gt;Cloudflare redirect rules with dynamic expressions&lt;/a&gt; - 2024-05-29&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/blogging"&gt;blogging&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github"&gt;github&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/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="blogging"/><category term="github"/><category term="projects"/><category term="datasette"/><category term="weeknotes"/><category term="github-codespaces"/></entry><entry><title>datasette/studio</title><link href="https://simonwillison.net/2024/Mar/10/datasette-studio-on-codespaces/#atom-tag" rel="alternate"/><published>2024-03-10T03:03:42+00:00</published><updated>2024-03-10T03:03:42+00:00</updated><id>https://simonwillison.net/2024/Mar/10/datasette-studio-on-codespaces/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/datasette/studio"&gt;datasette/studio&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I’m trying a new way to make Datasette available for small personal data manipulation projects, using GitHub Codespaces.&lt;/p&gt;

&lt;p&gt;This repository is designed to be opened directly in Codespaces—detailed instructions in the README.&lt;/p&gt;

&lt;p&gt;When the container starts it installs the datasette-studio family of plugins—including CSV upload, some enrichments and a few other useful feature—then starts the server running and provides a big green button to click to access the server via GitHub’s port forwarding mechanism.


    &lt;p&gt;Tags: &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/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;



</summary><category term="projects"/><category term="datasette"/><category term="github-codespaces"/></entry><entry><title>Exploring codespaces as temporary dev containers</title><link href="https://simonwillison.net/2024/Jan/26/exploring-codespaces-as-temporary-dev-containers/#atom-tag" rel="alternate"/><published>2024-01-26T18:46:13+00:00</published><updated>2024-01-26T18:46:13+00:00</updated><id>https://simonwillison.net/2024/Jan/26/exploring-codespaces-as-temporary-dev-containers/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://qmacro.org/blog/posts/2024/01/26/exploring-codespaces-as-temporary-dev-containers/"&gt;Exploring codespaces as temporary dev containers&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
DJ Adams shows how to use GitHub Codespaces without interacting with their web UI at all: you can run “gh codespace create --repo ...” to create a new instance, then SSH directly into it using “gh codespace ssh --codespace codespacename”.&lt;/p&gt;

&lt;p&gt;This turns Codespaces into an extremely convenient way to spin up a scratch on-demand Linux container where you pay for just the time that the machine spends running.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://lobste.rs/s/39tkja/exploring_codespaces_as_temporary_dev"&gt;lobste.rs&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;



</summary><category term="github"/><category term="github-codespaces"/></entry><entry><title>Exploration de données avec Datasette</title><link href="https://simonwillison.net/2023/May/27/exploration-de-donnees-avec-datasette/#atom-tag" rel="alternate"/><published>2023-05-27T00:36:52+00:00</published><updated>2023-05-27T00:36:52+00:00</updated><id>https://simonwillison.net/2023/May/27/exploration-de-donnees-avec-datasette/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://meetup-python-grenoble.github.io/datasette-workshop/"&gt;Exploration de données avec Datasette&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
One of the great delights of open source development is seeing people run workshops on your project, even more so when they’re in a language other than English! Romain Clement presented this French workshop for the Python Grenoble meetup on 25th May 2023, using GitHub Codespaces as the environment. It’s pretty comprehensive, including a 300,000+ row example table which illustrates Datasette plugins such as datasette-cluster-map and datasette-leaflet-geojson.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/tutorials"&gt;tutorials&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&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/leaflet"&gt;leaflet&lt;/a&gt;&lt;/p&gt;



</summary><category term="tutorials"/><category term="datasette"/><category term="github-codespaces"/><category term="leaflet"/></entry><entry><title>codespaces-jupyter</title><link href="https://simonwillison.net/2023/Apr/14/codespaces-jupyter/#atom-tag" rel="alternate"/><published>2023-04-14T22:38:21+00:00</published><updated>2023-04-14T22:38:21+00:00</updated><id>https://simonwillison.net/2023/Apr/14/codespaces-jupyter/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/github/codespaces-jupyter"&gt;codespaces-jupyter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
This is really neat. Click “Use this template” -&amp;gt; “Open in a codespace” and you get a full in-browser VS Code interface where you can open existing notebook files (or create new ones) and start playing with them straight away.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://fedi.simonwillison.net/@simon/110199563721187965"&gt;@simon thread about online notebooks&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;



</summary><category term="github"/><category term="python"/><category term="jupyter"/><category term="github-codespaces"/></entry><entry><title>Teaching News Apps with Codespaces</title><link href="https://simonwillison.net/2023/Mar/23/teaching-news-apps-with-codespaces/#atom-tag" rel="alternate"/><published>2023-03-23T00:39:33+00:00</published><updated>2023-03-23T00:39:33+00:00</updated><id>https://simonwillison.net/2023/Mar/23/teaching-news-apps-with-codespaces/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.thescoop.org/archives/2023/03/22/teaching-newsapps-with-codespaces/"&gt;Teaching News Apps with Codespaces&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Derek Willis used GitHub Codespaces for the latest data journalism class he taught, and it eliminated the painful process of trying to get students on an assortment of Mac, Windows and Chromebook laptops all to a point where they could start working and learning together.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://newsie.social/@derekwillis/110069743946512179"&gt;@derekwillis&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/data-journalism"&gt;data-journalism&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/derek-willis"&gt;derek-willis&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/teaching"&gt;teaching&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;



</summary><category term="data-journalism"/><category term="derek-willis"/><category term="github"/><category term="teaching"/><category term="github-codespaces"/></entry><entry><title>Using Datasette in GitHub Codespaces</title><link href="https://simonwillison.net/2023/Feb/24/using-datasette-in-github-codespaces/#atom-tag" rel="alternate"/><published>2023-02-24T00:40:11+00:00</published><updated>2023-02-24T00:40:11+00:00</updated><id>https://simonwillison.net/2023/Feb/24/using-datasette-in-github-codespaces/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://datasette.io/tutorials/codespaces"&gt;Using Datasette in GitHub Codespaces&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
A new Datasette tutorial showing how it can be run inside GitHub Codespaces—GitHub’s browser-based development environments—in order to explore and analyze data. I’ve been using Codespaces to run tutorials recently and it’s absolutely fantastic, because it puts every tutorial attendee on a level playing field with respect to their development environments.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tutorials"&gt;tutorials&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;



</summary><category term="github"/><category term="tutorials"/><category term="datasette"/><category term="github-codespaces"/></entry><entry><title>Weeknotes: CDC vaccination history fixes, developing in GitHub Codespaces</title><link href="https://simonwillison.net/2021/Sep/28/weeknotes/#atom-tag" rel="alternate"/><published>2021-09-28T01:53:49+00:00</published><updated>2021-09-28T01:53:49+00:00</updated><id>https://simonwillison.net/2021/Sep/28/weeknotes/#atom-tag</id><summary type="html">
    &lt;p&gt;I spent the last week mostly surrounded by boxes: we're completing our move to the new place and life is mostly unpacking now. I did find some time to fix some issues with my &lt;a href="https://cdc-vaccination-history.datasette.io/"&gt;CDC vaccination history&lt;/a&gt; Datasette instance though.&lt;/p&gt;
&lt;h4&gt;Fixing my CDC vaccination history site&lt;/h4&gt;
&lt;p&gt;I started tracking changes made to the &lt;a href="https://covid.cdc.gov/covid-data-tracker/#vaccinations_vacc-total-admin-rate-total"&gt;CDC's COVID Data Tracker&lt;/a&gt; website back in Feburary. I created &lt;a href="https://github.com/simonw/cdc-vaccination-history"&gt;a git scraper repository&lt;/a&gt; for it as part of my &lt;a href="https://simonwillison.net/2021/Mar/5/git-scraping/"&gt;five minute lightning talk on git scraping&lt;/a&gt; (notes and video) at this year's NICAR data journalism conference.&lt;/p&gt;
&lt;p&gt;Since then it's been quietly ticking along, recording the latest data in a git repository that now has &lt;a href="https://github.com/simonw/cdc-vaccination-history/commits/main"&gt;335 commits&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In March I &lt;a href="https://github.com/simonw/cdc-vaccination-history/commit/bf88c1e6cc3e5b6344a7dfea5d2a70dcb0552847#diff-87ee5504a3e25ac558b343724c905f2f7949e8cec3d92b9c4300bb922afa164f"&gt;added a script&lt;/a&gt; to build the collected historic data into a SQLite database and publish it to Vercel using GitHub. That started breaking a few weeks ago, and it turnoud out that was because the database file had grown in size to the point where it was too large to deploy to Vercel (~100MB).&lt;/p&gt;
&lt;p&gt;I got a bug report about this, so I took some time to &lt;a href="https://github.com/simonw/cdc-vaccination-history/issues/8"&gt;move the deployment over&lt;/a&gt; to Google Cloud Run which doesn't have a documented size limit (though in my experience starts to creak once you go above about 2GB.)&lt;/p&gt;
&lt;p&gt;I also started publishing the raw collected data &lt;a href="https://github.com/simonw/cdc-vaccination-history/issues/9"&gt;directly as a CSV file&lt;/a&gt;, partly as an excuse to learn &lt;a href="https://til.simonwillison.net/googlecloud/gsutil-bucket"&gt;how to publish to Google Cloud Storage&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;datasette-template-request&lt;/h4&gt;
&lt;p&gt;I released an extremely simple plugin this week called &lt;a href="https://datasette.io/plugins/datasette-template-request"&gt;datasette-template-request&lt;/a&gt; - all it does is expose Datasette's &lt;a href="https://docs.datasette.io/en/stable/internals.html#request-object"&gt;request object&lt;/a&gt; in the context passed to &lt;a href="https://docs.datasette.io/en/stable/custom_templates.html"&gt;custom templates&lt;/a&gt;, for people who want to update their custom page based on incoming request parameters.&lt;/p&gt;
&lt;p&gt;More notable is how I built the plugin: this is the first plugin I've developed, tested and released entirely in my browser using the new &lt;a href="https://github.com/features/codespaces"&gt;GitHub Codespaces&lt;/a&gt; online development environment.&lt;/p&gt;
&lt;p&gt;I created the new repo using my &lt;a href="https://github.com/simonw/datasette-plugin-template-repository"&gt;Datasette plugin template repository&lt;/a&gt;, opened it up in Codespaces, implemented the plugin and tests, tried it out using the port forwarding feature and then published it to PyPI using the &lt;a href="https://github.com/simonw/datasette-template-request/blob/0.1/.github/workflows/publish.yml"&gt;publish.yml&lt;/a&gt; workflow.&lt;/p&gt;
&lt;p&gt;Not having to even open a text editor on my laptop (let alone get a new Python development environment up and running) felt really good. I should turn this into a tutorial.&lt;/p&gt;
&lt;h4&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-template-request"&gt;datasette-template-request&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-template-request/releases/tag/0.1"&gt;0.1&lt;/a&gt; - 2021-09-23
&lt;br /&gt;Expose the Datasette request object to custom templates&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-notebook"&gt;datasette-notebook&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-notebook/releases/tag/0.1a1"&gt;0.1a1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-notebook/releases"&gt;2 releases total&lt;/a&gt;) - 2021-09-22
&lt;br /&gt;A markdown wiki and dashboarding system for Datasette&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-render-markdown"&gt;datasette-render-markdown&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-render-markdown/releases/tag/2.0"&gt;2.0&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-render-markdown/releases"&gt;8 releases total&lt;/a&gt;) - 2021-09-22
&lt;br /&gt;Datasette plugin for rendering Markdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/sqlite-utils/releases/tag/3.17.1"&gt;3.17.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/sqlite-utils/releases"&gt;87 releases total&lt;/a&gt;) - 2021-09-22
&lt;br /&gt;Python CLI utility and library for manipulating SQLite databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/dogsheep/twitter-to-sqlite"&gt;twitter-to-sqlite&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/dogsheep/twitter-to-sqlite/releases/tag/0.22"&gt;0.22&lt;/a&gt; - (&lt;a href="https://github.com/dogsheep/twitter-to-sqlite/releases"&gt;28 releases total&lt;/a&gt;) - 2021-09-21
&lt;br /&gt;Save data from Twitter to a SQLite database&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/til/til/googlecloud_gsutil-bucket.md"&gt;Publishing to a public Google Cloud bucket with gsutil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/til/til/javascript_lit-with-skypack.md"&gt;Loading lit from Skypack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/covid19"&gt;covid19&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/git-scraping"&gt;git-scraping&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="github"/><category term="projects"/><category term="weeknotes"/><category term="covid19"/><category term="git-scraping"/><category term="github-codespaces"/></entry><entry><title>Datasette on Codespaces, sqlite-utils API reference documentation and other weeknotes</title><link href="https://simonwillison.net/2021/Aug/14/datasette-on-codespaces/#atom-tag" rel="alternate"/><published>2021-08-14T04:57:12+00:00</published><updated>2021-08-14T04:57:12+00:00</updated><id>https://simonwillison.net/2021/Aug/14/datasette-on-codespaces/#atom-tag</id><summary type="html">
    &lt;p&gt;This week I &lt;a href="https://datasette.substack.com/p/everything-new-in-datasette-since"&gt;broke my streak&lt;/a&gt; of &lt;em&gt;not&lt;/em&gt; sending out the Datasette newsletter, figured out how to use Sphinx for Python class documentation, worked out how to run Datasette on GitHub Codespaces, implemented Datasette column metadata and got tantalizingly close to a solution for an elusive Datasette feature.&lt;/p&gt;
&lt;h4&gt;API reference documentation for sqlite-utils using Sphinx&lt;/h4&gt;
&lt;p&gt;I've never been a big fan of Javadoc-style API documentation: I usually find that documentation structured around classes and methods fails to show me how to actually use those classes to solve real-world problems. I've tended to avoid it for my own projects.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils Python library&lt;/a&gt; has a ton of functionality, but it mainly boils down to two classes: &lt;code&gt;Database&lt;/code&gt; and &lt;code&gt;Table&lt;/code&gt;. Since it  already has pretty comprehesive narrative documentation explaining the different problems it can solve, I decided to try experimenting with the &lt;a href="https://www.sphinx-doc.org/en/master/usage/extensions/autodoc.html"&gt;Sphinx autodoc&lt;/a&gt; module to produce some classic &lt;a href="https://sqlite-utils.datasette.io/en/stable/reference.html"&gt;API reference documentation&lt;/a&gt; for it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the new API reference documentation" src="https://static.simonwillison.net/static/2021/sqlite-utils-api-doc.png" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Since autodoc works from docstrings, this was also a great excuse to add more comprehensive docstrings and type hints to the library. This helps tools like Jupyter notebooks and VS Code display more useful inline help.&lt;/p&gt;
&lt;p&gt;This proved to be time well spent! Here's what &lt;code&gt;sqlite-utils&lt;/code&gt; looks like in VS Code now:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of VS Code showing inline help for the enable_fts() method" src="https://static.simonwillison.net/static/2021/vs-code-hints.png" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;mypy&lt;/code&gt; against the type hints also helped me identify and fix a couple of obscure edge-case bugs in the existing methods, detailed in &lt;a href="https://sqlite-utils.datasette.io/en/stable/changelog.html#v3-15-1"&gt;the 3.15.1 release notes&lt;/a&gt;. It's taken me a few years but I'm finally starting to come round to Python's optional typing as being worth the additional effort!&lt;/p&gt;
&lt;p&gt;Figuring out how to use autodoc in Sphinx, and then how to get the documentation to build correctly on &lt;a href="https://readthedocs.org/"&gt;Read The Docs&lt;/a&gt; took some effort. I wrote up what I learned in &lt;a href="https://til.simonwillison.net/sphinx/sphinx-autodoc"&gt;this TIL&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Datasette on GitHub Codespaces&lt;/h4&gt;
&lt;p&gt;GitHub released their new &lt;a href="https://github.com/features/codespaces"&gt;Codespaces&lt;/a&gt; online development environments to general availability this week and I'm really excited about it. I ran a team at Eventbrite for a while resonsible for development environment tooling and it really was shocking how much time and money was lost to broken local development environments, even with a significant amount of engineering effort applied to the problem.&lt;/p&gt;
&lt;p&gt;Codespaces promises a fresh, working development environment on-demand any time you need it. That's a very exciting premise! Their detailed write-up of how they convinced GitHub's own internal engineers to move to it is full of &lt;a href="https://github.blog/2021-08-11-githubs-engineering-team-moved-codespaces/"&gt;intriguing details&lt;/a&gt; - getting an existing application working with it is no small feat, but the pay-off looks very promising indeed.&lt;/p&gt;
&lt;p&gt;So... I decided to try and get Datasette running on it. It works really well!&lt;/p&gt;
&lt;p&gt;You can run Datasette in any Codespace environment using the following steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open the terminal. Three-bar-menu-icon, View, Terminal does the trick.&lt;/li&gt;
&lt;li&gt;In the terminal run &lt;code&gt;pip install datasette datasette-x-forwarded-host&lt;/code&gt; (more on this in a moment).&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;datasette&lt;/code&gt; - Codespaces will automatically setup port forwarding and give you a link to "Open in Browser" - click the link and you're done!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You can &lt;code&gt;pip install sqlite-utils&lt;/code&gt; and then use &lt;a href="https://sqlite-utils.datasette.io/en/stable/cli.html#inserting-csv-or-tsv-data"&gt;sqlite-utils insert&lt;/a&gt; to create SQLite databases to use with Datasette.&lt;/p&gt;
&lt;p&gt;There was one catch: the first time I ran Datasette, clicking on any of the internal links within the web application took me to &lt;code&gt;http://localhost/&lt;/code&gt; pages that broke with a 404.&lt;/p&gt;
&lt;p&gt;It turns out the Codespaces proxy sends a &lt;code&gt;host: localhost&lt;/code&gt; header - which Datasette then uses to incorrectly construct internal URLs.&lt;/p&gt;
&lt;p&gt;So I wrote a tiny ASGI plugin, &lt;a href="https://datasette.io/plugins/datasette-x-forwarded-host"&gt;datasette-x-forwarded-host&lt;/a&gt;, which takes the incoming &lt;code&gt;X-Forwarded-Host&lt;/code&gt; provided by Codespaces and uses that as the &lt;code&gt;Host&lt;/code&gt; header within Datasette itself. After that everything worked fine.&lt;/p&gt;
&lt;h4&gt;sqlite-utils insert --flatten&lt;/h4&gt;
&lt;p&gt;Early this week I finally figured out &lt;a href="https://cloud.google.com/run/docs/logging"&gt;Cloud Run logging&lt;/a&gt;. It's actually really good! In doing so, I worked out &lt;a href="https://til.simonwillison.net/cloudrun/tailing-cloud-run-request-logs"&gt;a convoluted recipe&lt;/a&gt; for tailing the JSON logs locally and piping them into a SQLite database so that I could analyze them with Datasette.&lt;/p&gt;
&lt;p&gt;Part of the reason it was convoluted is that Cloud Run logs feature nested JSON, but &lt;a href="https://sqlite-utils.datasette.io/en/stable/cli.html#inserting-json-data"&gt;sqlite-utils insert&lt;/a&gt; only works against an array of flat JSON objects. I had to use &lt;a href="https://til.simonwillison.net/jq/flatten-nested-json-objects-jq"&gt;this jq monstrosity&lt;/a&gt; to flatten the nested JSON into key/value pairs.&lt;/p&gt;
&lt;p&gt;Since I've had to solve this problem a few times now I decided to improve &lt;code&gt;sqlite-utils&lt;/code&gt; to have it do the work instead. You can now use the new &lt;code&gt;--flatten&lt;/code&gt; option like so:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sqlite-utils insert logs.db logs log.json --flatten
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To create a schema that flattens nested objects into a &lt;code&gt;topkey_nextkey&lt;/code&gt; structure like so:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;CREATE TABLE [logs] (
   [httpRequest_latency] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;,
   [httpRequest_requestMethod] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;,
   [httpRequest_requestSize] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;,
   [httpRequest_status] &lt;span class="pl-k"&gt;INTEGER&lt;/span&gt;,
   [insertId] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;,
   [labels_service] &lt;span class="pl-k"&gt;TEXT&lt;/span&gt;
);&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Full &lt;a href="https://sqlite-utils.datasette.io/en/stable/cli.html#flattening-nested-json-objects"&gt;documentation for --flatten&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;Datasette column metadata&lt;/h4&gt;
&lt;p&gt;I've been wanting to add this for a while: Datasette's main branch now includes an implementation of &lt;a href="https://docs.datasette.io/en/latest/metadata.html#column-descriptions"&gt;column descriptions metadata&lt;/a&gt; for Datasette tables. This is best illustrated by a screenshot (of &lt;a href="https://latest.datasette.io/fixtures/roadside_attractions"&gt;this live demo&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot showing column metadata displayed both at the top of the Datasette table page and in the context menu that shows up for a column" src="https://static.simonwillison.net/static/2021/column-metadata.png" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;You can add the following to &lt;code&gt;metadata.yml&lt;/code&gt; (or &lt;code&gt;.json&lt;/code&gt;) to specify descriptions for the columns of a given table:&lt;/p&gt;
&lt;div class="highlight highlight-source-yaml"&gt;&lt;pre&gt;&lt;span class="pl-ent"&gt;databases&lt;/span&gt;:
  &lt;span class="pl-ent"&gt;fixtures&lt;/span&gt;:
    &lt;span class="pl-ent"&gt;roadside_attractions&lt;/span&gt;:
      &lt;span class="pl-ent"&gt;columns&lt;/span&gt;:
        &lt;span class="pl-ent"&gt;name&lt;/span&gt;: &lt;span class="pl-s"&gt;The name of the attraction&lt;/span&gt;
        &lt;span class="pl-ent"&gt;address&lt;/span&gt;: &lt;span class="pl-s"&gt;The street address for the attraction&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Column descriptions will be shown in a &lt;code&gt;&amp;lt;dl&amp;gt;&lt;/code&gt; at the top of the page, and will also be added to the menu that appears when you click on the cog icon at the top of a column.&lt;/p&gt;
&lt;h4 id="column-metadata"&gt;Getting closer to query column metadata, too&lt;/h4&gt;
&lt;p&gt;Datasette lets you execute arbitrary SQL queries, like this one:&lt;/p&gt;
&lt;div class="highlight highlight-source-sql"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;select&lt;/span&gt;
  &lt;span class="pl-c1"&gt;roadside_attractions&lt;/span&gt;.&lt;span class="pl-c1"&gt;name&lt;/span&gt;,
  &lt;span class="pl-c1"&gt;roadside_attractions&lt;/span&gt;.&lt;span class="pl-c1"&gt;address&lt;/span&gt;,
  &lt;span class="pl-c1"&gt;attraction_characteristic&lt;/span&gt;.&lt;span class="pl-c1"&gt;name&lt;/span&gt;
&lt;span class="pl-k"&gt;from&lt;/span&gt;
  roadside_attraction_characteristics
  &lt;span class="pl-k"&gt;join&lt;/span&gt; roadside_attractions &lt;span class="pl-k"&gt;on&lt;/span&gt; &lt;span class="pl-c1"&gt;roadside_attractions&lt;/span&gt;.&lt;span class="pl-c1"&gt;pk&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;roadside_attraction_characteristics&lt;/span&gt;.&lt;span class="pl-c1"&gt;attraction_id&lt;/span&gt;
  &lt;span class="pl-k"&gt;join&lt;/span&gt; attraction_characteristic &lt;span class="pl-k"&gt;on&lt;/span&gt; &lt;span class="pl-c1"&gt;attraction_characteristic&lt;/span&gt;.&lt;span class="pl-c1"&gt;pk&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-c1"&gt;roadside_attraction_characteristics&lt;/span&gt;.&lt;span class="pl-c1"&gt;characteristic_id&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can &lt;a href="https://latest.datasette.io/fixtures?sql=select%0D%0A++roadside_attractions.name%2C%0D%0A++roadside_attractions.address%2C%0D%0A++attraction_characteristic.name%0D%0Afrom%0D%0A++roadside_attraction_characteristics%0D%0A++join+roadside_attractions+on+roadside_attractions.pk+%3D+roadside_attraction_characteristics.attraction_id%0D%0A++join+attraction_characteristic+on+attraction_characteristic.pk+%3D+roadside_attraction_characteristics.characteristic_id"&gt;try that here&lt;/a&gt;. It returns the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;address&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The Mystery Spot&lt;/td&gt;
&lt;td&gt;465 Mystery Spot Road, Santa Cruz, CA 95065&lt;/td&gt;
&lt;td&gt;Paranormal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Winchester Mystery House&lt;/td&gt;
&lt;td&gt;525 South Winchester Boulevard, San Jose, CA 95128&lt;/td&gt;
&lt;td&gt;Paranormal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bigfoot Discovery Museum&lt;/td&gt;
&lt;td&gt;5497 Highway 9, Felton, CA 95018&lt;/td&gt;
&lt;td&gt;Paranormal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Burlingame Museum of PEZ Memorabilia&lt;/td&gt;
&lt;td&gt;214 California Drive, Burlingame, CA 94010&lt;/td&gt;
&lt;td&gt;Museum&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bigfoot Discovery Museum&lt;/td&gt;
&lt;td&gt;5497 Highway 9, Felton, CA 95018&lt;/td&gt;
&lt;td&gt;Museum&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The columns it returns have names... but I've long wanted to do more with these results. If I could derive &lt;em&gt;which&lt;/em&gt; source columns each of those output columns were, there are a bunch of interesting things I could do, most notably:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If the output column is a known foreign key relationship, I could turn it into a hyperlink (as seen on &lt;a href="https://latest.datasette.io/fixtures/roadside_attraction_characteristics"&gt;this table page&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;If the original table column has the new column metadata, I could display that as additional documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The challenge is: given an abitrary SQL query, how can I figure out what the resulting columns are going to be and how to tie those back to the original tables?&lt;/p&gt;
&lt;p&gt;Thanks to &lt;a href="https://sqlite.org/forum/forumpost/482abd2e0f119555?t=h"&gt;a hint&lt;/a&gt; from the SQLite forum I'm getting &lt;a href="https://github.com/simonw/datasette/issues/1293"&gt;tantalizingly close&lt;/a&gt; to a solution.&lt;/p&gt;
&lt;p&gt;The trick is to horribly abuse SQLite's &lt;code&gt;explain&lt;/code&gt; output. Here's &lt;a href="https://latest.datasette.io/fixtures?sql=explain+select%0D%0A++roadside_attractions.name%2C%0D%0A++roadside_attractions.address%2C%0D%0A++attraction_characteristic.name%0D%0Afrom%0D%0A++roadside_attraction_characteristics%0D%0A++join+roadside_attractions+on+roadside_attractions.pk+%3D+roadside_attraction_characteristics.attraction_id%0D%0A++join+attraction_characteristic+on+attraction_characteristic.pk+%3D+roadside_attraction_characteristics.characteristic_id"&gt;what it looks like&lt;/a&gt; for the example query above:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;addr&lt;/th&gt;
&lt;th&gt;opcode&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;p2&lt;/th&gt;
&lt;th&gt;p3&lt;/th&gt;
&lt;th&gt;p4&lt;/th&gt;
&lt;th&gt;p5&lt;/th&gt;
&lt;th&gt;comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Init&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;OpenRead&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;OpenRead&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;OpenRead&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Rewind&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;SeekRowid&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;SeekRowid&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Column&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;ResultRow&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Next&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Halt&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Transaction&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;Goto&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The magic is on line 12: &lt;code&gt;ResultRow 3 3&lt;/code&gt; means "return a result that spans three columns, starting at register 3" - so that's register 3, 4 and 5. Those three registers are populated by the &lt;code&gt;Column&lt;/code&gt; operations on line 9, 10 and 11 (the register they write into is in the &lt;code&gt;p3&lt;/code&gt; column). Each &lt;code&gt;Column&lt;/code&gt; operation specifies the table (as &lt;code&gt;p1&lt;/code&gt;) and the column index within that table (&lt;code&gt;p2&lt;/code&gt;). And those table references map back to the &lt;code&gt;OpenRead&lt;/code&gt; lines at the start, where &lt;code&gt;p1&lt;/code&gt; is that table register (referered to by &lt;code&gt;Column&lt;/code&gt;) and &lt;code&gt;p1&lt;/code&gt; is the root page of the table within the schema.&lt;/p&gt;
&lt;p&gt;Running &lt;code&gt;select rootpage, name from sqlite_master where rootpage in (45, 46, 47)&lt;/code&gt; produces &lt;a href="https://latest.datasette.io/fixtures?sql=select+rootpage%2C+name+from+sqlite_master+where+rootpage+in+%2845%2C+46%2C+47%29"&gt;the following&lt;/a&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;rootpage&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;45&lt;/td&gt;
&lt;td&gt;roadside_attractions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;46&lt;/td&gt;
&lt;td&gt;attraction_characteristic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;roadside_attraction_characteristics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Tie all of this together, and it may be possible to use &lt;code&gt;explain&lt;/code&gt; to derive the original tables and columns for each of the outputs of an arbitrary query!&lt;/p&gt;
&lt;p&gt;I was almost ready to declare victory, until I tried running it against a query with an &lt;code&gt;order by column&lt;/code&gt; at the end... and the results no longer matched up.&lt;/p&gt;
&lt;p&gt;You can follow &lt;a href="https://github.com/simonw/datasette/issues/1293#issuecomment-898524057"&gt;my ongoing investigation here&lt;/a&gt; - the short version is that I think I'm going to have to learn to decode a whole bunch more opcodes before I can get this to work.&lt;/p&gt;
&lt;p&gt;This is also a very risk way of attacking this problem. The SQLite &lt;a href="https://www.sqlite.org/opcode.html"&gt;documentation for the bytecode engine&lt;/a&gt; includes the following warning:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This document describes SQLite internals. The information provided here is not needed for routine application development using SQLite. This document is intended for people who want to delve more deeply into the internal operation of SQLite.&lt;/p&gt;
&lt;p&gt;The bytecode engine is not an API of SQLite. Details about the bytecode engine change from one release of SQLite to the next. Applications that use SQLite should not depend on any of the details found in this document.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So it's pretty clear that this is a highly unsupported way of working with SQLite!&lt;/p&gt;
&lt;p&gt;I'm still tempted to try it though. This feature is very much a nice-to-have: if it breaks and the additional column context stops displaying it's not a critical bug - and hopefully I'll be able to ship a Datasette update that takes into account those breaking SQLite changes relatively shortly afterwards.&lt;/p&gt;
&lt;p&gt;If I can find another, more supported way to solve this I'll jump on it!&lt;/p&gt;
&lt;p&gt;In the meantime, I did use this technque to solve a simpler problem. Datasette extracts &lt;code&gt;:named&lt;/code&gt; parameters from arbitrary SQL queries and turns them &lt;a href="https://latest.datasette.io/fixtures/neighborhood_search?_show_sql=1"&gt;into form fields&lt;/a&gt; - but since it uses a simple regular expression for this it could be confused by things like a literal &lt;code&gt;00:04:05&lt;/code&gt; time string contained in a SQL query.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;explain&lt;/code&gt; output for that query includes the following:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;addr&lt;/th&gt;
&lt;th&gt;opcode&lt;/th&gt;
&lt;th&gt;p1&lt;/th&gt;
&lt;th&gt;p2&lt;/th&gt;
&lt;th&gt;p3&lt;/th&gt;
&lt;th&gt;p4&lt;/th&gt;
&lt;th&gt;p5&lt;/th&gt;
&lt;th&gt;comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;Variable&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;:text&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So I wrote some code which uses &lt;code&gt;explain&lt;/code&gt; to extract just the &lt;code&gt;p4&lt;/code&gt; operands from &lt;code&gt;Variable&lt;/code&gt; columns and treats those as the extracted parameters! This feels a lot safer than the more complex &lt;code&gt;ResultRow&lt;/code&gt;/&lt;code&gt;Column&lt;/code&gt; logic - and it also falls back to the regular expression if it runs into any SQL errors. More &lt;a href="https://github.com/simonw/datasette/issues/1421"&gt;in the issue&lt;/a&gt;.&lt;/p&gt;
&lt;h4&gt;TIL this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/cloudrun/tailing-cloud-run-request-logs"&gt;Tailing Google Cloud Run request logs and importing them into SQLite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/python/find-local-variables-in-exception-traceback"&gt;Find local variables in the traceback for an exception&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/sphinx/sphinx-autodoc"&gt;Adding Sphinx autodoc to a project, and configuring Read The Docs to build it&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Releases this week&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-x-forwarded-host"&gt;datasette-x-forwarded-host&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-x-forwarded-host/releases/tag/0.1"&gt;0.1&lt;/a&gt; - 2021-08-12
&lt;br /&gt;Treat the X-Forwarded-Host header as the Host header&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/sqlite-utils/releases/tag/3.15.1"&gt;3.15.1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/sqlite-utils/releases"&gt;84 releases total&lt;/a&gt;) - 2021-08-10
&lt;br /&gt;Python CLI utility and library for manipulating SQLite databases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-query-links"&gt;datasette-query-links&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-query-links/releases/tag/0.1.2"&gt;0.1.2&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-query-links/releases"&gt;3 releases total&lt;/a&gt;) - 2021-08-09
&lt;br /&gt;Turn SELECT queries returned by a query into links to execute them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette"&gt;datasette&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette/releases/tag/0.59a1"&gt;0.59a1&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette/releases"&gt;96 releases total&lt;/a&gt;) - 2021-08-09
&lt;br /&gt;An open source multi-tool for exploring and publishing data&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/simonw/datasette-pyinstrument"&gt;datasette-pyinstrument&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-pyinstrument/releases/tag/0.1"&gt;0.1&lt;/a&gt; - 2021-08-08
&lt;br /&gt;Use pyinstrument to analyze Datasette page performance&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/documentation"&gt;documentation&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sql"&gt;sql&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mypy"&gt;mypy&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="documentation"/><category term="github"/><category term="sql"/><category term="sqlite"/><category term="datasette"/><category term="weeknotes"/><category term="sqlite-utils"/><category term="mypy"/><category term="github-codespaces"/></entry><entry><title>GitHub’s Engineering Team has moved to Codespaces</title><link href="https://simonwillison.net/2021/Aug/11/codespaces/#atom-tag" rel="alternate"/><published>2021-08-11T16:53:06+00:00</published><updated>2021-08-11T16:53:06+00:00</updated><id>https://simonwillison.net/2021/Aug/11/codespaces/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.blog/2021-08-11-githubs-engineering-team-moved-codespaces/"&gt;GitHub’s Engineering Team has moved to Codespaces&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
My absolute dream development environment is one where I can spin up a new, working development environment in seconds—to try something new on a branch, or because I broke something and don’t want to spend time figuring out how to fix it. This article from GitHub explains how they got there: from a half-day setup to a 45 minute bootstrap in a codespace, then to five minutes through shallow cloning and a nightly pre-built Docker image and finally to 10 seconds be setting up “pools of codespaces, fully cloned and bootstrapped, waiting to be connected with a developer who wants to get to work”.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/github"&gt;github&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/docker"&gt;docker&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-codespaces"&gt;github-codespaces&lt;/a&gt;&lt;/p&gt;



</summary><category term="github"/><category term="docker"/><category term="github-codespaces"/></entry></feed>