<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: jupyter</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/jupyter.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-10-31T13:57:51+00:00</updated><author><name>Simon Willison</name></author><entry><title>CoreWeave adds Marimo to their 2025 acquisition spree</title><link href="https://simonwillison.net/2025/Oct/31/coreweave-acquires-marimo/#atom-tag" rel="alternate"/><published>2025-10-31T13:57:51+00:00</published><updated>2025-10-31T13:57:51+00:00</updated><id>https://simonwillison.net/2025/Oct/31/coreweave-acquires-marimo/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://marimo.io/blog/joining-coreweave"&gt;Marimo is Joining CoreWeave&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I don't usually cover startup acquisitions here, but this one feels relevant to several of my interests.&lt;/p&gt;
&lt;p&gt;Marimo (&lt;a href="https://simonwillison.net/tags/marimo/"&gt;previously&lt;/a&gt;) provide an open source (Apache 2 licensed) notebook tool for Python, with first-class support for an additional WebAssembly build plus an optional hosted service. It's effectively a reimagining of Jupyter notebooks as a reactive system, where cells automatically update based on changes to other cells - similar to how &lt;a href="https://observablehq.com/"&gt;Observable&lt;/a&gt; JavaScript notebooks work.&lt;/p&gt;
&lt;p&gt;The first public Marimo release was in January 2024 and the tool has "been in development since 2022" (&lt;a href="https://news.ycombinator.com/item?id=44304607#44330375"&gt;source&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;CoreWeave are a &lt;em&gt;big&lt;/em&gt; player in the AI data center space. They started out as an Ethereum mining company in 2017, then pivoted to cloud computing infrastructure for AI companies after the 2018 cryptocurrency crash. They IPOd in March 2025 and today they operate more than 30 data centers worldwide and have announced a number of eye-wateringly sized deals with companies such as Cohere and OpenAI. I found &lt;a href="https://en.wikipedia.org/wiki/CoreWeave"&gt;their Wikipedia page&lt;/a&gt; very helpful.&lt;/p&gt;
&lt;p&gt;They've also been on an acquisition spree this year, including:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Weights &amp;amp; Biases &lt;a href="https://www.coreweave.com/blog/coreweave-completes-acquisition-of-weights-biases"&gt;in March 2025&lt;/a&gt; (deal closed in May), the AI training observability platform.&lt;/li&gt;
&lt;li&gt;OpenPipe &lt;a href="https://www.coreweave.com/news/coreweave-to-acquire-openpipe-leader-in-reinforcement-learning"&gt;in September 2025&lt;/a&gt; - a reinforcement learning platform, authors of the &lt;a href="https://github.com/OpenPipe/ART"&gt;Agent Reinforcement Trainer&lt;/a&gt; Apache 2 licensed open source RL framework.&lt;/li&gt;
&lt;li&gt;Monolith AI &lt;a href="https://investors.coreweave.com/news/news-details/2025/CoreWeave-to-Acquire-Monolith-Expanding-AI-Cloud-Platform-into-Industrial-Innovation/default.aspx"&gt;in October 2025&lt;/a&gt;, a UK-based AI model SaaS platform focused on AI for engineering and industrial manufacturing.&lt;/li&gt;
&lt;li&gt;And now Marimo.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Marimo's own announcement emphasizes continued investment in that tool:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Marimo is joining CoreWeave. We’re continuing to build the open-source marimo notebook, while also leveling up molab with serious compute. Our long-term mission remains the same: to build the world’s best open-source programming environment for working with data.&lt;/p&gt;
&lt;p&gt;marimo is, and always will be, free, open-source, and permissively licensed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Give CoreWeave's buying spree only really started this year it's impossible to say how well these acquisitions are likely to play out - they haven't yet established a track record.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://x.com/marimo_io/status/1983916371869364622"&gt;@marimo_io&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/entrepreneurship"&gt;entrepreneurship&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/open-source"&gt;open-source&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/startups"&gt;startups&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/marimo"&gt;marimo&lt;/a&gt;&lt;/p&gt;



</summary><category term="entrepreneurship"/><category term="open-source"/><category term="python"/><category term="startups"/><category term="ai"/><category term="jupyter"/><category term="marimo"/></entry><entry><title>Announcing Deno 2</title><link href="https://simonwillison.net/2024/Oct/10/announcing-deno-2/#atom-tag" rel="alternate"/><published>2024-10-10T04:11:02+00:00</published><updated>2024-10-10T04:11:02+00:00</updated><id>https://simonwillison.net/2024/Oct/10/announcing-deno-2/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://deno.com/blog/v2.0"&gt;Announcing Deno 2&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The big focus of Deno 2 is compatibility with the existing Node.js and npm ecosystem:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Deno 2 takes all of the features developers love about Deno 1.x — zero-config, all-in-one toolchain for JavaScript and TypeScript development, web standard API support, secure by default — and makes it fully backwards compatible with Node and npm (in ESM).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The npm support &lt;a href="https://docs.deno.com/runtime/fundamentals/node/#using-npm-packages"&gt;is documented here&lt;/a&gt;. You can write a script like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-js"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-s1"&gt;emoji&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"npm:node-emoji"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-smi"&gt;console&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;log&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;emoji&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;emojify&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s"&gt;`:sauropod: :heart:  npm`&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;And when you run it Deno will automatically fetch and cache the required dependencies:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno run main.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Another new feature that caught my eye was this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;deno jupyter&lt;/code&gt; now supports outputting images, graphs, and HTML&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Deno has apparently shipped with &lt;a href="https://docs.deno.com/runtime/reference/cli/jupyter/"&gt;a Jupyter notebook kernel&lt;/a&gt; for a while, and it's had a major upgrade in this release.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://www.youtube.com/watch?v=d35SlRgVxT8&amp;amp;t=1829s"&gt;Ryan Dahl's demo&lt;/a&gt; of the new notebook support in his Deno 2 release video.&lt;/p&gt;
&lt;p&gt;I tried this out myself, and it's really neat. First you need to install the kernel:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;deno juptyer --install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was curious to find out what this actually did, so I dug around &lt;a href="https://github.com/denoland/deno/blob/251840a60d1e2ba4ceca85029bd8cc342b6cd038/cli/tools/jupyter/install.rs#L48-L57"&gt;in the code&lt;/a&gt; and then further &lt;a href="https://github.com/runtimed/runtimed/blob/e2cd9b1d88e44842e1b1076d3a1d1f202fcf7879/runtimelib/src/jupyter/dirs.rs#L81-L99"&gt;in the Rust runtimed dependency&lt;/a&gt;. It turns out installing Jupyter kernels, at least on macOS, involves creating a directory in &lt;code&gt;~/Library/Jupyter/kernels/deno&lt;/code&gt; and writing a &lt;code&gt;kernel.json&lt;/code&gt; file containing the following:&lt;/p&gt;
&lt;div class="highlight highlight-source-json"&gt;&lt;pre&gt;{
  &lt;span class="pl-ent"&gt;"argv"&lt;/span&gt;: [
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;/opt/homebrew/bin/deno&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;jupyter&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;--kernel&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;--conn&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
    &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;{connection_file}&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
  ],
  &lt;span class="pl-ent"&gt;"display_name"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Deno&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
  &lt;span class="pl-ent"&gt;"language"&lt;/span&gt;: &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;typescript&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
}&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That file is picked up by any Jupyter servers running on your machine, and tells them to run &lt;code&gt;deno jupyter --kernel ...&lt;/code&gt; to start a kernel.&lt;/p&gt;
&lt;p&gt;I started Jupyter like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jupyter-notebook /tmp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then started a new notebook, selected the Deno kernel and it worked as advertised:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Jupyter notebook running the Deno kernel. I run 4 + 5 and get 9, then Deno.version and get back 2.0.0. I import Observable Plot and the penguins data, then render a plot which shows as a scatter chart." src="https://static.simonwillison.net/static/2024/deno-jupyter.jpg" /&gt;&lt;/p&gt;
&lt;div class="highlight highlight-source-ts"&gt;&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-c1"&gt;*&lt;/span&gt; &lt;span class="pl-k"&gt;as&lt;/span&gt; &lt;span class="pl-smi"&gt;Plot&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"npm:@observablehq/plot"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt; &lt;span class="pl-smi"&gt;document&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-s1"&gt;penguins&lt;/span&gt; &lt;span class="pl-kos"&gt;}&lt;/span&gt; &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s"&gt;"jsr:@ry/jupyter-helper"&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;
&lt;span class="pl-k"&gt;let&lt;/span&gt; &lt;span class="pl-s1"&gt;p&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-k"&gt;await&lt;/span&gt; &lt;span class="pl-en"&gt;penguins&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;

&lt;span class="pl-smi"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;plot&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;{&lt;/span&gt;
  &lt;span class="pl-c1"&gt;marks&lt;/span&gt;: &lt;span class="pl-kos"&gt;[&lt;/span&gt;
    &lt;span class="pl-smi"&gt;Plot&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;dot&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-s1"&gt;p&lt;/span&gt;&lt;span class="pl-kos"&gt;.&lt;/span&gt;&lt;span class="pl-en"&gt;toRecords&lt;/span&gt;&lt;span class="pl-kos"&gt;(&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt; &lt;span class="pl-kos"&gt;{&lt;/span&gt;
      &lt;span class="pl-c1"&gt;x&lt;/span&gt;: &lt;span class="pl-s"&gt;"culmen_depth_mm"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;y&lt;/span&gt;: &lt;span class="pl-s"&gt;"culmen_length_mm"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
      &lt;span class="pl-c1"&gt;fill&lt;/span&gt;: &lt;span class="pl-s"&gt;"species"&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
    &lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  &lt;span class="pl-kos"&gt;]&lt;/span&gt;&lt;span class="pl-kos"&gt;,&lt;/span&gt;
  document&lt;span class="pl-kos"&gt;,&lt;/span&gt;
&lt;span class="pl-kos"&gt;}&lt;/span&gt;&lt;span class="pl-kos"&gt;)&lt;/span&gt;&lt;span class="pl-kos"&gt;;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/nodejs"&gt;nodejs&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/npm"&gt;npm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/typescript"&gt;typescript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/deno"&gt;deno&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable-plot"&gt;observable-plot&lt;/a&gt;&lt;/p&gt;



</summary><category term="javascript"/><category term="nodejs"/><category term="npm"/><category term="jupyter"/><category term="typescript"/><category term="deno"/><category term="observable-plot"/></entry><entry><title>Anthropic's Prompt Engineering Interactive Tutorial</title><link href="https://simonwillison.net/2024/Aug/30/anthropic-prompt-engineering-interactive-tutorial/#atom-tag" rel="alternate"/><published>2024-08-30T02:52:04+00:00</published><updated>2024-08-30T02:52:04+00:00</updated><id>https://simonwillison.net/2024/Aug/30/anthropic-prompt-engineering-interactive-tutorial/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/anthropics/courses/tree/master/prompt_engineering_interactive_tutorial"&gt;Anthropic&amp;#x27;s Prompt Engineering Interactive Tutorial&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Anthropic continue their trend of offering the best documentation of any of the leading LLM vendors. This tutorial is delivered as a set of Jupyter notebooks - I used it as an excuse to try &lt;a href="https://docs.astral.sh/uv/guides/tools/"&gt;uvx&lt;/a&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;git clone https://github.com/anthropics/courses
uvx --from jupyter-core jupyter notebook courses&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This installed a working Jupyter system, started the server and launched my browser within a few seconds.&lt;/p&gt;
&lt;p&gt;The first few chapters are pretty basic, demonstrating simple prompts run through the Anthropic API. I used &lt;code&gt;%pip install anthropic&lt;/code&gt; instead of &lt;code&gt;!pip install anthropic&lt;/code&gt; to make sure the package was installed in the correct virtual environment, &lt;a href="https://github.com/anthropics/courses/issues/30"&gt;then filed an issue and a PR&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One new-to-me trick: in the first chapter the tutorial suggests running this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-v"&gt;API_KEY&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;"your_api_key_here"&lt;/span&gt;
&lt;span class="pl-c1"&gt;%&lt;/span&gt;&lt;span class="pl-s1"&gt;store&lt;/span&gt; &lt;span class="pl-v"&gt;API_KEY&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;This stashes your Anthropic API key in the &lt;a href="https://ipython.readthedocs.io/en/stable/config/extensions/storemagic.html"&gt;IPython store&lt;/a&gt;. In subsequent notebooks you can restore the &lt;code&gt;API_KEY&lt;/code&gt; variable like this:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;%&lt;/span&gt;&lt;span class="pl-s1"&gt;store&lt;/span&gt; &lt;span class="pl-c1"&gt;-&lt;/span&gt;&lt;span class="pl-s1"&gt;r&lt;/span&gt; &lt;span class="pl-v"&gt;API_KEY&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;I poked around and on macOS those variables are stored in files of the same name in &lt;code&gt;~/.ipython/profile_default/db/autorestore&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/anthropics/courses/blob/master/prompt_engineering_interactive_tutorial/Anthropic%201P/04_Separating_Data_and_Instructions.ipynb"&gt;Chapter 4: Separating Data and Instructions&lt;/a&gt; included some interesting notes on Claude's support for content wrapped in XML-tag-style delimiters:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; While Claude can recognize and work with a wide range of separators and delimeters, we recommend that you &lt;strong&gt;use specifically XML tags as separators&lt;/strong&gt; for Claude, as Claude was trained specifically to recognize XML tags as a prompt organizing mechanism. Outside of function calling, &lt;strong&gt;there are no special sauce XML tags that Claude has been trained on that you should use to maximally boost your performance&lt;/strong&gt;. We have purposefully made Claude very malleable and customizable this way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Plus this note on the importance of avoiding typos, with a nod back to the &lt;a href="https://simonwillison.net/2023/Apr/5/sycophancy-sandbagging/"&gt;problem of sandbagging&lt;/a&gt; where models match their intelligence and tone to that of their prompts:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is an important lesson about prompting: &lt;strong&gt;small details matter&lt;/strong&gt;! It's always worth it to &lt;strong&gt;scrub your prompts for typos and grammatical errors&lt;/strong&gt;. Claude is sensitive to patterns (in its early years, before finetuning, it was a raw text-prediction tool), and it's more likely to make mistakes when you make mistakes, smarter when you sound smart, sillier when you sound silly, and so on.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/anthropics/courses/blob/master/prompt_engineering_interactive_tutorial/Anthropic%201P/05_Formatting_Output_and_Speaking_for_Claude.ipynb"&gt;Chapter 5: Formatting Output and Speaking for Claude&lt;/a&gt; includes notes on one of Claude's most interesting features: &lt;em&gt;prefill&lt;/em&gt;, where you can tell it how to start its response:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-s1"&gt;client&lt;/span&gt;.&lt;span class="pl-s1"&gt;messages&lt;/span&gt;.&lt;span class="pl-en"&gt;create&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;model&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"claude-3-haiku-20240307"&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;max_tokens&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;100&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;messages&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;[
        {&lt;span class="pl-s"&gt;"role"&lt;/span&gt;: &lt;span class="pl-s"&gt;"user"&lt;/span&gt;, &lt;span class="pl-s"&gt;"content"&lt;/span&gt;: &lt;span class="pl-s"&gt;"JSON facts about cats"&lt;/span&gt;},
        {&lt;span class="pl-s"&gt;"role"&lt;/span&gt;: &lt;span class="pl-s"&gt;"assistant"&lt;/span&gt;, &lt;span class="pl-s"&gt;"content"&lt;/span&gt;: &lt;span class="pl-s"&gt;"{"&lt;/span&gt;}
    ]
)&lt;/pre&gt;

&lt;p&gt;Things start to get really interesting in &lt;a href="https://github.com/anthropics/courses/blob/master/prompt_engineering_interactive_tutorial/Anthropic%201P/06_Precognition_Thinking_Step_by_Step.ipynb"&gt;Chapter 6: Precognition (Thinking Step by Step)&lt;/a&gt;, which suggests using XML tags to help the model consider different arguments prior to generating a final answer:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Is this review sentiment positive or negative? First, write the best arguments for each side in &amp;lt;positive-argument&amp;gt; and &amp;lt;negative-argument&amp;gt; XML tags, then answer.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The tags make it easy to strip out the "thinking out loud" portions of the response.&lt;/p&gt;
&lt;p&gt;It also warns about Claude's sensitivity to ordering. If you give Claude two options (e.g. for sentiment analysis):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In most situations (but not all, confusingly enough), &lt;strong&gt;Claude is more likely to choose the second of two options&lt;/strong&gt;, possibly because in its training data from the web, second options were more likely to be correct.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This effect can be reduced using the thinking out loud / brainstorming prompting techniques.&lt;/p&gt;
&lt;p&gt;A related tip is proposed in &lt;a href="https://github.com/anthropics/courses/blob/master/prompt_engineering_interactive_tutorial/Anthropic%201P/08_Avoiding_Hallucinations.ipynb"&gt;Chapter 8: Avoiding Hallucinations&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;How do we fix this? Well, a great way to reduce hallucinations on long documents is to &lt;strong&gt;make Claude gather evidence first.&lt;/strong&gt; &lt;/p&gt;
&lt;p&gt;In this case, we &lt;strong&gt;tell Claude to first extract relevant quotes, then base its answer on those quotes&lt;/strong&gt;. Telling Claude to do so here makes it correctly notice that the quote does not answer the question.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I really like the example prompt they provide here, for answering complex questions against a long document:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;question&amp;gt;What was Matterport's subscriber base on the precise date of May 31, 2020?&amp;lt;/question&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Please read the below document. Then, in &amp;lt;scratchpad&amp;gt; tags, pull the most relevant quote from the document and consider whether it answers the user's question or whether it lacks sufficient detail. Then write a brief numerical answer in &amp;lt;answer&amp;gt; tags.&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=41395921"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/xml"&gt;xml&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/prompt-engineering"&gt;prompt-engineering&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/uv"&gt;uv&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="xml"/><category term="ai"/><category term="jupyter"/><category term="prompt-engineering"/><category term="generative-ai"/><category term="llms"/><category term="anthropic"/><category term="claude"/><category term="uv"/></entry><entry><title>Anthropic cookbook: multimodal</title><link href="https://simonwillison.net/2024/Jul/10/anthropic-cookbook-multimodal/#atom-tag" rel="alternate"/><published>2024-07-10T18:38:10+00:00</published><updated>2024-07-10T18:38:10+00:00</updated><id>https://simonwillison.net/2024/Jul/10/anthropic-cookbook-multimodal/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/anthropics/anthropic-cookbook/tree/main/multimodal"&gt;Anthropic cookbook: multimodal&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I'm currently on the lookout for high quality sources of information about vision LLMs, including prompting tricks for getting the most out of them.&lt;/p&gt;
&lt;p&gt;This set of Jupyter notebooks from Anthropic (published four months ago to accompany the original Claude 3 models) is the best I've found so far. &lt;a href="https://github.com/anthropics/anthropic-cookbook/blob/main/multimodal/best_practices_for_vision.ipynb"&gt;Best practices for using vision with Claude&lt;/a&gt; includes advice on multi-shot prompting with example, plus this interesting think step-by-step style prompt for improving Claude's ability to count the dogs in an image:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You have perfect vision and pay great attention to detail which makes you an expert at counting objects in images. How many dogs are in this picture? Before providing the answer in &lt;code&gt;&amp;lt;answer&amp;gt;&lt;/code&gt; tags, think step by step in &lt;code&gt;&amp;lt;thinking&amp;gt;&lt;/code&gt; tags and analyze every part of the image.&lt;/p&gt;
&lt;/blockquote&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&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/vision-llms"&gt;vision-llms&lt;/a&gt;&lt;/p&gt;



</summary><category term="ai"/><category term="jupyter"/><category term="generative-ai"/><category term="llms"/><category term="anthropic"/><category term="claude"/><category term="vision-llms"/></entry><entry><title>marimo.app</title><link href="https://simonwillison.net/2024/Jun/29/marimo-app/#atom-tag" rel="alternate"/><published>2024-06-29T23:07:42+00:00</published><updated>2024-06-29T23:07:42+00:00</updated><id>https://simonwillison.net/2024/Jun/29/marimo-app/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://marimo.app/"&gt;marimo.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
The Marimo reactive notebook (&lt;a href="https://simonwillison.net/2024/Jan/12/marimo/"&gt;previously&lt;/a&gt;) - a Python notebook that's effectively a cross between Jupyter and Observable - now also has a version that runs entirely in your browser using WebAssembly and Pyodide. Here's &lt;a href="https://docs.marimo.io/guides/wasm.html"&gt;the documentation&lt;/a&gt;.


    &lt;p&gt;Tags: &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/observable"&gt;observable&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pyodide"&gt;pyodide&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/marimo"&gt;marimo&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="jupyter"/><category term="observable"/><category term="webassembly"/><category term="pyodide"/><category term="marimo"/></entry><entry><title>fastlite</title><link href="https://simonwillison.net/2024/May/27/fastlite/#atom-tag" rel="alternate"/><published>2024-05-27T21:14:01+00:00</published><updated>2024-05-27T21:14:01+00:00</updated><id>https://simonwillison.net/2024/May/27/fastlite/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://answerdotai.github.io/fastlite/"&gt;fastlite&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
New Python library from Jeremy Howard that adds some neat utility functions and syntactic sugar to my &lt;a href="https://sqlite-utils.datasette.io/"&gt;sqlite-utils&lt;/a&gt; Python library, specifically for interactive use in Jupyter notebooks.&lt;/p&gt;
&lt;p&gt;The autocomplete support through newly exposed dynamic properties is particularly neat, as is the &lt;code&gt;diagram(db.tables)&lt;/code&gt; utility for rendering a graphviz diagram showing foreign key relationships between all of the tables.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&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/jeremy-howard"&gt;jeremy-howard&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="sqlite"/><category term="jupyter"/><category term="sqlite-utils"/><category term="jeremy-howard"/></entry><entry><title>Interesting ideas in Observable Framework</title><link href="https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#atom-tag" rel="alternate"/><published>2024-03-03T17:54:21+00:00</published><updated>2024-03-03T17:54:21+00:00</updated><id>https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#atom-tag</id><summary type="html">
    &lt;p&gt;Mike Bostock, &lt;a href="https://observablehq.com/blog/observable-2-0"&gt;Announcing: Observable Framework&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Today we’re launching &lt;a href="https://observablehq.com/product"&gt;Observable 2.0&lt;/a&gt; with a bold new vision: an open-source static site generator for building fast, beautiful data apps, dashboards, and reports.&lt;/p&gt;
&lt;p&gt;Our mission is to help teams communicate more effectively with data. Effective presentation of data is critical for deep insight, nuanced understanding, and informed decisions. Observable notebooks are great for ephemeral, &lt;em&gt;ad hoc&lt;/em&gt; data exploration. But notebooks aren't well-suited for polished dashboards and apps.&lt;/p&gt;
&lt;p&gt;Enter &lt;a href="https://observablehq.com/framework/"&gt;Observable Framework&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There are a lot of &lt;em&gt;really&lt;/em&gt; interesting ideas in Observable Framework.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#static-site-dashboards"&gt;A static site generator for data projects and dashboards&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#javascript-in-markdown"&gt;JavaScript in Markdown&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#everything-reactive"&gt;Everything is still reactive&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#only-code-you-use"&gt;Only include the code that you use&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#cache-data-at-build"&gt;Cache your data at build time&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#comparison-to-observable-notebooks"&gt;Comparison to Observable Notebooks&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://simonwillison.net/2024/Mar/3/interesting-ideas-in-observable-framework/#change-in-strategy"&gt;A change in strategy&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id="static-site-dashboards"&gt;A static site generator for data projects and dashboards&lt;/h4&gt;
&lt;p&gt;At its heart, Observable Framework is a static site generator. You give it a mixture of Markdown and JavaScript (and potentially other languages too) and it compiles them all together into fast loading interactive pages.&lt;/p&gt;
&lt;p&gt;It ships with a full featured hot-reloading server, so you can edit those files in your editor, hit save and see the changes reflected instantly in your browser.&lt;/p&gt;
&lt;p&gt;Once you're happy with your work you can run a build command to turn it into a set of static files ready to deploy to a server - or you can use the &lt;code&gt;npm run deploy&lt;/code&gt; command to deploy it directly to Observable's own authenticated sharing platform.&lt;/p&gt;
&lt;h4 id="javascript-in-markdown"&gt;JavaScript in Markdown&lt;/h4&gt;
&lt;p&gt;The key to the design of Observable Framework is the way it uses JavaScript in Markdown to create interactive documents.&lt;/p&gt;
&lt;p&gt;Here's what that looks like:&lt;/p&gt;
&lt;div class="highlight highlight-text-md"&gt;&lt;pre&gt;&lt;span class="pl-mh"&gt;# &lt;span class="pl-en"&gt;This is a document&lt;/span&gt;&lt;/span&gt;

Markdown content goes here.

This will output 1870:

&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;span class="pl-en"&gt;js&lt;/span&gt;
&lt;span class="pl-c1"&gt;34&lt;/span&gt; &lt;span class="pl-k"&gt;*&lt;/span&gt; &lt;span class="pl-c1"&gt;55&lt;/span&gt;
&lt;span class="pl-s"&gt;```&lt;/span&gt;

And here's the current date and time, updating constantly:

&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;span class="pl-en"&gt;js&lt;/span&gt;
&lt;span class="pl-k"&gt;new&lt;/span&gt; &lt;span class="pl-en"&gt;Date&lt;/span&gt;(now)
&lt;span class="pl-s"&gt;```&lt;/span&gt;

The same thing as an inline string: ${new Date(now)}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Any Markdown code block tagged &lt;code&gt;js&lt;/code&gt; will be executed as JavaScript in the user's browser. This is an &lt;em&gt;incredibly&lt;/em&gt; powerful abstraction - anything you can do in JavaScript (which these days is effectively anything at all) can now be seamlessly integrated into your document.&lt;/p&gt;
&lt;p&gt;In the above example the &lt;code&gt;now&lt;/code&gt; value is interesting - it's a special variable that provides the current time in milliseconds since the epoch, updating constantly. Because &lt;code&gt;now&lt;/code&gt; updates constantly, the display value of the cell and that inline expression will update constantly as well.&lt;/p&gt;
&lt;p&gt;If you've used Observable Notebooks before this will feel familiar - but notebooks involve code and markdown authored in separate cells. With Framework they are all now part of a single text document.&lt;/p&gt;
&lt;p&gt;Aside: when I tried the above example I found that the &lt;code&gt;${new Date(now)}&lt;/code&gt; inline expression displayed as &lt;code&gt;Mon Feb 19 2024 20:46:02 GMT-0800 (Pacific Standard Time)&lt;/code&gt; while the &lt;code&gt;js&lt;/code&gt; block displayed as &lt;code&gt;2024-02-20T04:46:02.641Z&lt;/code&gt;. That's because inline expressions use the JavaScript default string representation of the object, while the &lt;code&gt;js&lt;/code&gt; block uses the Observable &lt;code&gt;display()&lt;/code&gt; function which has its own rules for how to display different types of objects, &lt;a href="https://github.com/observablehq/inspector/blob/main/src/inspect.js"&gt;visible in inspect/src/inspect.js&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="everything-reactive"&gt;Everything is still reactive&lt;/h4&gt;
&lt;p&gt;The best feature of Observable Notebooks is their &lt;em&gt;reactivity&lt;/em&gt; - the way cells automatically refresh when other cells they depend on change. This is a big difference to Python's popular Jupyter notebooks, and is the signature feature of &lt;a href="https://marimo.io/"&gt;marimo&lt;/a&gt;, a new Python notebook tool.&lt;/p&gt;
&lt;p&gt;Observable Framework retains this feature in its new JavaScript Markdown documents.&lt;/p&gt;
&lt;p&gt;This is particularly useful when working with form inputs. You can drop an input onto a page and refer its value throughout the rest of the document, adding realtime interactivity to documents incredibly easily.&lt;/p&gt;
&lt;p&gt;Here's an example. I ported one of my &lt;a href="https://observablehq.com/@simonw/datasette-downloads-per-day-with-observable-plot"&gt;favourite notebooks&lt;/a&gt; to Framework, which provides a tool for viewing download statistics for my various Python packages.&lt;/p&gt;
&lt;p&gt;The Observable Framework version can be found at &lt;a href="https://simonw.github.io/observable-framework-experiments/package-downloads"&gt;https://simonw.github.io/observable-framework-experiments/package-downloads&lt;/a&gt; - source code &lt;a href="https://github.com/simonw/observable-framework-experiments/blob/main/docs/package-downloads.md?plain=1"&gt;here on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/pypi-dashboard.gif" alt="Animated demo showing PyPI download stats for Datasette projects - as I switch a select menu between sqlite-utils and csv-diff and shot-scraper the displayed chart updates to match." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;This entire thing is just 57 lines of Markdown. Here's the code with additional comments (and presented in a slightly different order - the order of code blocks doesn't matter in Observable thanks to reactivity).&lt;/p&gt;
&lt;div class="highlight highlight-text-md"&gt;&lt;pre&gt;&lt;span class="pl-mh"&gt;# &lt;span class="pl-en"&gt;PyPI download stats for Datasette projects&lt;/span&gt;&lt;/span&gt;

Showing downloads for &lt;span class="pl-s"&gt;**&lt;/span&gt;${packageName}&lt;span class="pl-s"&gt;**&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It starts with a Markdown &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; heading and text that shows the name of the selected package.&lt;/p&gt;
&lt;div class="highlight highlight-text-md"&gt;&lt;pre&gt;&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;span class="pl-en"&gt;js&lt;/span&gt; echo
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-c1"&gt;packageName&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;view&lt;/span&gt;(&lt;span class="pl-smi"&gt;Inputs&lt;/span&gt;.&lt;span class="pl-c1"&gt;select&lt;/span&gt;(packages, {
  value&lt;span class="pl-k"&gt;:&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;sqlite-utils&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;,
  label&lt;span class="pl-k"&gt;:&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;Package&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
}));
&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This block displays the select widget allowing the user to pick one of the items from the &lt;code&gt;packages&lt;/code&gt; array (defined later on).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Inputs.select()&lt;/code&gt; is a built-in method provided by Framework, described in the &lt;a href="https://observablehq.com/framework/lib/inputs"&gt;Observable Inputs&lt;/a&gt; documentation.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;view()&lt;/code&gt; function is new in Observable Framework - it's the thing that enables the reactivity, ensuring that updates to the input selection are acted on by other code blocks in the document.&lt;/p&gt;
&lt;p&gt;Because &lt;code&gt;packageName&lt;/code&gt; is defined with &lt;code&gt;const&lt;/code&gt; it becomes a variable that is visible to other &lt;code&gt;js&lt;/code&gt; blocks on the page. It's used by this next block:&lt;/p&gt;
&lt;div class="highlight highlight-text-md"&gt;&lt;pre&gt;&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;span class="pl-en"&gt;js&lt;/span&gt; echo
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-c1"&gt;data&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-smi"&gt;d3&lt;/span&gt;.&lt;span class="pl-en"&gt;json&lt;/span&gt;(
  &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;`&lt;/span&gt;https://datasette.io/content/stats.json?_size=max&amp;amp;package=&lt;span class="pl-s1"&gt;&lt;span class="pl-pse"&gt;${&lt;/span&gt;packageName&lt;span class="pl-pse"&gt;}&lt;/span&gt;&lt;/span&gt;&amp;amp;_sort_desc=date&amp;amp;_shape=array&lt;span class="pl-pds"&gt;`&lt;/span&gt;&lt;/span&gt;
);&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here we are fetching the data that we need for the chart. I'm using &lt;code&gt;d3.json()&lt;/code&gt; (all of D3 is available in Framework) to fetch the data from a URL that includes the selected package name.&lt;/p&gt;
&lt;p&gt;The data is coming from &lt;a href="https://datasette.io/"&gt;Datasette&lt;/a&gt;, using the Datasette JSON API. I have a SQLite table at &lt;a href="https://datasette.io/content/stats"&gt;datasette.io/content/stats&lt;/a&gt; that's updated once a day with the latest PyPI package statistics via a convoluted series of GitHub Actions workflows, &lt;a href="https://simonwillison.net/2021/Jul/28/baked-data/#baked-data-datasette-io"&gt;described previously&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Adding &lt;code&gt;.json&lt;/code&gt; to that URL returns the JSON, then I ask for rows for that particular package, sorted descending by date and returning the maximum number of rows (1,000) as a JSON array of objects.&lt;/p&gt;
&lt;p&gt;Now that we have &lt;code&gt;data&lt;/code&gt; as a variable we can manipulate it slightly for use with Observable Plot - parsing the SQLite string dates into JavaScript &lt;code&gt;Date&lt;/code&gt; objects:&lt;/p&gt;
&lt;div class="highlight highlight-text-md"&gt;&lt;pre&gt;&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;span class="pl-en"&gt;js&lt;/span&gt; echo
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-c1"&gt;data_with_dates&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-smi"&gt;data&lt;/span&gt;.&lt;span class="pl-en"&gt;map&lt;/span&gt;(&lt;span class="pl-k"&gt;function&lt;/span&gt;(&lt;span class="pl-smi"&gt;d&lt;/span&gt;) {
  d.date = d3.&lt;span class="pl-en"&gt;timeParse&lt;/span&gt;(&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;%Y-%m-%d&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;)(d.date);
  return d;
})
```&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This code is ready to render as a chart. I'm using &lt;a href="https://observablehq.com/plot"&gt;Observable Plot&lt;/a&gt; - also packaged with Framework:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;```js echo
Plot.plot({
  y: {
    grid: true,
    label: `${packageName} PyPI downloads per day`
  },
  width: width,
  marginLeft: 60,
  marks: [
    Plot.line(data_with_dates, {
      x: "date",
      y: "downloads",
      title: "downloads",
      tip: true
    })
  ]
})
```
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So we have one cell that lets the user pick the package they want, a cell that fetches that data, a cell that processes it and a cell that renders it as a chart.&lt;/p&gt;
&lt;p&gt;There's one more piece of the puzzle: where does that list of packages come from? I fetch that with another API call to Datasette. Here I'm using a SQL query executed against the &lt;a href="https://datasette.io/content"&gt;/content&lt;/a&gt; database directly:&lt;/p&gt;
&lt;div class="highlight highlight-text-md"&gt;&lt;pre&gt;&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;span class="pl-en"&gt;js&lt;/span&gt; echo
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-c1"&gt;packages_sql&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;select package from stats group by package order by max(downloads) desc&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;```&lt;/span&gt;
&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;span class="pl-en"&gt;js&lt;/span&gt; echo
&lt;span class="pl-k"&gt;const&lt;/span&gt; &lt;span class="pl-c1"&gt;packages&lt;/span&gt; &lt;span class="pl-k"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;fetch&lt;/span&gt;(
  &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;`&lt;/span&gt;https://datasette.io/content.json?sql=&lt;span class="pl-s1"&gt;&lt;span class="pl-pse"&gt;${&lt;/span&gt;&lt;span class="pl-c1"&gt;encodeURIComponent&lt;/span&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;span class="pl-s1"&gt;    packages_sql&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;span class="pl-s1"&gt;  )&lt;span class="pl-pse"&gt;}&lt;/span&gt;&lt;/span&gt;&amp;amp;_size=max&amp;amp;_shape=arrayfirst&lt;span class="pl-pds"&gt;`&lt;/span&gt;&lt;/span&gt;
).&lt;span class="pl-c1"&gt;then&lt;/span&gt;((&lt;span class="pl-smi"&gt;r&lt;/span&gt;) &lt;span class="pl-k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pl-smi"&gt;r&lt;/span&gt;.&lt;span class="pl-en"&gt;json&lt;/span&gt;());
&lt;span class="pl-s"&gt;```&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;_shape=arrayfirst&lt;/code&gt; is a shortcut for getting back a JSON array of the first column of the resulting rows.&lt;/p&gt;
&lt;p&gt;That's all there is to it! It's a pretty tiny amount of code for a full interactive dashboard.&lt;/p&gt;
&lt;h4 id="only-code-you-use"&gt;Only include the code that you use&lt;/h4&gt;
&lt;p&gt;You may have noticed that my dashboard example uses several additional libraries - &lt;code&gt;Inputs&lt;/code&gt; for the form element, &lt;code&gt;d3&lt;/code&gt; for the data fetching and &lt;code&gt;Plot&lt;/code&gt; for the chart rendering.&lt;/p&gt;
&lt;p&gt;Observable Framework is smart about these. It implements lazy loading in development mode, so code is only loaded the first time you attempt to use it in a cell.&lt;/p&gt;
&lt;p&gt;When you build and deploy your application, Framework automatically loads just the referenced library code from the &lt;a href="https://www.jsdelivr.com/"&gt;jsdelivr CDN&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="cache-data-at-build"&gt;Cache your data at build time&lt;/h4&gt;
&lt;p&gt;One of the most interesting features of Framework is its &lt;a href="https://observablehq.com/framework/loaders"&gt;Data loader&lt;/a&gt; mechanism.&lt;/p&gt;
&lt;p&gt;Dashboards built using Framework can load data at runtime from anywhere using &lt;code&gt;fetch()&lt;/code&gt; requests (or wrappers around them). This is how Observable Notebooks work too, but it leaves the performance of your dashboard at the mercy of whatever backends you are talking to.&lt;/p&gt;
&lt;p&gt;Dashboards benefit from fast loading times. Framework encourages a pattern where you build the data for the dashboard at deploy time, bundling it together into static files containing just the subset of the data needed for the dashboard. These can be served lightning fast from the same static hosting as the dashboard code itself.&lt;/p&gt;
&lt;p&gt;The design of the data loaders is beautifully simple and powerful. A data loader is a script that can be written in &lt;em&gt;any&lt;/em&gt; programming language. At build time, Framework executes that script and saves whatever is outputs to a file.&lt;/p&gt;
&lt;p&gt;A data loader can be as simple as the following, saved as &lt;code&gt;quakes.json.sh&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;curl https://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/all_day.geojson&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;When the application is built, that filename tells Framework the destination file (&lt;code&gt;quakes.json&lt;/code&gt;) and the loader to execute (&lt;code&gt;.sh&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This means you can load data from any source using any technology you like, provided it has the ability to output JSON or CSV or some other useful format to standard output.&lt;/p&gt;
&lt;h4 id="comparison-to-observable-notebooks"&gt;Comparison to Observable Notebooks&lt;/h4&gt;
&lt;p&gt;Mike introduced Observable Framework as &lt;em&gt;Observable 2.0&lt;/em&gt;. It's worth reviewing how the this system compares to the original Observable Notebook platform.&lt;/p&gt;
&lt;p&gt;I've been a huge fan of Observable Notebooks for years - &lt;a href="https://simonwillison.net/tags/observable/"&gt;38 blog posts and counting&lt;/a&gt;! The most obvious comparison is to Jupyter Notebooks, where they have some key differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Observable notebooks use JavaScript, not Python.&lt;/li&gt;
&lt;li&gt;The notebook editor itself isn't open source - it's a hosted product provided on &lt;a href="https://observablehq.com/"&gt;observablehq.com&lt;/a&gt;. You can export the notebooks as static files and run them anywhere you like, but the editor itself is a proprietary product.&lt;/li&gt;
&lt;li&gt;Observable cells are &lt;em&gt;reactive&lt;/em&gt;. This is the key difference with Jupyter: any time you change a cell all other cells that depend on that cell are automatically re-evaluated, similar to Excel.&lt;/li&gt;
&lt;li&gt;The JavaScript syntax they use isn't quite standard JavaScript - they had to invent a new &lt;code&gt;viewof&lt;/code&gt; keyword to support their reactivity model.&lt;/li&gt;
&lt;li&gt;Editable notebooks are a pretty complex proprietary file format. They don't play well with tools like Git, to the point that Observable ended up implementing their own custom version control and collaboration systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Observable Framework reuses many of the ideas (and code) from Observable Notebooks, but with some crucial differences:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Notebooks (really documents) are now &lt;strong&gt;single text files&lt;/strong&gt; - Markdown files with embedded JavaScript blocks. It's all still reactive, but the file format is much simpler and can be edited using any text editor, and checked into Git.&lt;/li&gt;
&lt;li&gt;It's &lt;strong&gt;all open source&lt;/strong&gt;. Everything is under an ISC license (OSI approved) and you can run the full editing stack on your own machine.&lt;/li&gt;
&lt;li&gt;It's all just standard JavaScript now - &lt;strong&gt;no custom syntax&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="change-in-strategy"&gt;A change in strategy&lt;/h4&gt;
&lt;p&gt;Reading the tea leaves a bit, this also looks to me like a strategic change of direction for Observable as a company. Their previous focus was on building great collaboration tools for data science and analytics teams, based around the proprietary Observable Notebook editor.&lt;/p&gt;
&lt;p&gt;With Framework they appear to be leaning more into the developer tools space.&lt;/p&gt;
&lt;p&gt;On Twitter &lt;a href="http://twitter.com/observablehq"&gt;@observablehq&lt;/a&gt; describes itself as "The end-to-end solution for developers who want to build and host dashboards that don’t suck" - the Internet Archive copy &lt;a href="https://web.archive.org/web/20231003212202/https://twitter.com/observablehq"&gt;from October 3rd 2023&lt;/a&gt; showed "Build data visualizations, dashboards, and data apps that impact your business — faster."&lt;/p&gt;
&lt;p&gt;I'm excited to see where this goes. I've limited my usage of Observable Notebooks a little in the past purely due to the proprietary nature of their platform and the limitations placed on free accounts (mainly the lack of free private notebooks), while still having enormous respect for the technology and enthusiastically adopting their open source libraries such as &lt;a href="https://observablehq.com/plot/"&gt;Observable Plot&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Observable Framework addresses basically all of my reservations. It's a fantastic new expression of the ideas that made Observable Notebooks so compelling, and I expect to use it for all sorts of interesting projects in the future.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/javascript"&gt;javascript&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/open-source"&gt;open-source&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pypi"&gt;pypi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/d3"&gt;d3&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable"&gt;observable&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/mike-bostock"&gt;mike-bostock&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable-framework"&gt;observable-framework&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable-plot"&gt;observable-plot&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="javascript"/><category term="open-source"/><category term="pypi"/><category term="d3"/><category term="jupyter"/><category term="observable"/><category term="mike-bostock"/><category term="observable-framework"/><category term="observable-plot"/></entry><entry><title>Marimo</title><link href="https://simonwillison.net/2024/Jan/12/marimo/#atom-tag" rel="alternate"/><published>2024-01-12T21:17:57+00:00</published><updated>2024-01-12T21:17:57+00:00</updated><id>https://simonwillison.net/2024/Jan/12/marimo/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://marimo.io/"&gt;Marimo&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
This is a really interesting new twist on Python notebooks.&lt;/p&gt;

&lt;p&gt;The most powerful feature is that these notebooks are reactive: if you change the value or code in a cell (or change the value in an input widget) every other cell that depends on that value will update automatically. It’s the same pattern implemented by Observable JavaScript notebooks, but now it works for Python.&lt;/p&gt;

&lt;p&gt;There are a bunch of other nice touches too. The notebook file format is a regular Python file, and those files can be run as “applications” in addition to being edited in the notebook interface. The interface is very nicely built, especially for such a young project—they even have GitHub Copilot integration for their CodeMirror cell editors.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=38971966"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/open-source"&gt;open-source&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/observable"&gt;observable&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/github-copilot"&gt;github-copilot&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/marimo"&gt;marimo&lt;/a&gt;&lt;/p&gt;



</summary><category term="open-source"/><category term="python"/><category term="jupyter"/><category term="observable"/><category term="github-copilot"/><category term="marimo"/></entry><entry><title>Bottleneck T5 Text Autoencoder</title><link href="https://simonwillison.net/2023/Oct/10/bottleneck/#atom-tag" rel="alternate"/><published>2023-10-10T02:12:38+00:00</published><updated>2023-10-10T02:12:38+00:00</updated><id>https://simonwillison.net/2023/Oct/10/bottleneck/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://linus.zone/contra-colab"&gt;Bottleneck T5 Text Autoencoder&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Colab notebook by Linus Lee demonstrating his Contra Bottleneck T5 embedding model, which can take up to 512 tokens of text, convert that into a 1024 floating point number embedding vector... and then then reconstruct the original text (or a close imitation) from the embedding again.&lt;/p&gt;

&lt;p&gt;This allows for some fascinating tricks, where you can do things like generate embeddings for two completely different sentences and then reconstruct a new sentence that combines the weights from both.

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


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



</summary><category term="python"/><category term="ai"/><category term="jupyter"/><category term="generative-ai"/><category term="llms"/><category term="embeddings"/></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>Grokking Stable Diffusion</title><link href="https://simonwillison.net/2022/Sep/4/grokking-stable-diffusion/#atom-tag" rel="alternate"/><published>2022-09-04T18:50:29+00:00</published><updated>2022-09-04T18:50:29+00:00</updated><id>https://simonwillison.net/2022/Sep/4/grokking-stable-diffusion/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://colab.research.google.com/drive/1dlgggNa5Mz8sEAGU0wFCHhGLFooW_pf1?usp=sharing"&gt;Grokking Stable Diffusion&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Jonathan Whitaker built this interactive Jupyter notebook that walks through how to use Stable Diffusion from Python step-by-step, and then dives deep into helping understand the different components of the implementation, including how text is encoded, how the diffusion loop works and more. This is by far the most useful tool I’ve seen yet for understanding how this model actually works. You can run Jonathan’s notebook directly on Google Colab, with a GPU.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/stable-diffusion"&gt;stable-diffusion&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/text-to-image"&gt;text-to-image&lt;/a&gt;&lt;/p&gt;



</summary><category term="jupyter"/><category term="stable-diffusion"/><category term="generative-ai"/><category term="text-to-image"/></entry><entry><title>storysniffer</title><link href="https://simonwillison.net/2022/Aug/1/storysniffer/#atom-tag" rel="alternate"/><published>2022-08-01T23:40:13+00:00</published><updated>2022-08-01T23:40:13+00:00</updated><id>https://simonwillison.net/2022/Aug/1/storysniffer/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://palewi.re/docs/storysniffer/"&gt;storysniffer&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Ben Welsh built a small Python library that guesses if a URL points to an article on a news website, or if it’s more likely to be a category page or /about page or similar. I really like this as an example of what you can do with a tiny machine learning model: the model is bundled as a ~3MB pickle file as part of the package, and the repository includes the Jupyter notebook that was used to train it.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/machine-learning"&gt;machine-learning&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/ben-welsh"&gt;ben-welsh&lt;/a&gt;&lt;/p&gt;



</summary><category term="machine-learning"/><category term="python"/><category term="jupyter"/><category term="ben-welsh"/></entry><entry><title>Weeknotes: datasette-jupyterlite, s3-credentials and a Python packaging talk</title><link href="https://simonwillison.net/2021/Nov/5/datasette-jupyterlite/#atom-tag" rel="alternate"/><published>2021-11-05T05:04:58+00:00</published><updated>2021-11-05T05:04:58+00:00</updated><id>https://simonwillison.net/2021/Nov/5/datasette-jupyterlite/#atom-tag</id><summary type="html">
    &lt;p&gt;My big project this week was &lt;a href="https://simonwillison.net/2021/Nov/3/s3-credentials/"&gt;s3-credentials, described yesterday&lt;/a&gt; - but I also put together a fun expermiental Datasette plugin bundling JupyterLite and wrote up my PyGotham talk on Python packaging.&lt;/p&gt;
&lt;h4&gt;datasette-jupyterlite&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://github.com/jupyterlite/jupyterlite"&gt;JupyterLite&lt;/a&gt; is absolutely incredible: it's a full, working distribution of Jupyter that runs entirely in a browser, thanks to a Python interpreter (and various other parts of the scientific Python stack) that has been compiled to WebAssembly by the &lt;a href="https://pyodide.org//"&gt;Pyodide&lt;/a&gt; project.&lt;/p&gt;
&lt;p&gt;Since it's just static JavaScript (and WASM modules) it's possible to host it anywhere that can run a web server.&lt;/p&gt;
&lt;p&gt;Datasette runs a web server...&lt;/p&gt;
&lt;p&gt;So, I built &lt;a href="https://datasette.io/plugins/datasette-jupyterlite"&gt;datasette-jupyterlite&lt;/a&gt; - a Datasette plugin that bundles JupyterLite and serves it up as part of the Datasette instance.&lt;/p&gt;
&lt;p&gt;You can try &lt;a href="https://latest-with-plugins.datasette.io/jupyterlite/"&gt;a live demo here&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2021/datasette-jupyterlite.png" alt="Screenshot showing JupyterLite hosted by Datasette" style="max-width:100%;" /&gt;&lt;/p&gt;
&lt;p&gt;Here's some Python code that will retrieve data from the associated Datasette instance and pull it into a Pandas DataFrame:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;pandas&lt;/span&gt;, &lt;span class="pl-s1"&gt;pyodide&lt;/span&gt;
&lt;span class="pl-s1"&gt;pandas&lt;/span&gt;.&lt;span class="pl-en"&gt;read_csv&lt;/span&gt;(&lt;span class="pl-s1"&gt;pyodide&lt;/span&gt;.&lt;span class="pl-en"&gt;open_url&lt;/span&gt;(
  &lt;span class="pl-s"&gt;"https://latest-with-plugins.datasette.io/github/stars.csv"&lt;/span&gt;)
)&lt;/pre&gt;
&lt;p&gt;(I haven't yet found a way to do this with a relative rather than absolute URL.)&lt;/p&gt;
&lt;p&gt;The best part of this is that it works in &lt;a href="https://datasette.io/desktop"&gt;Datasette Desktop&lt;/a&gt;! You can install the plugin using the "Install and manage plugins" menu item to get a version of Jupyter running in Python running in WebAssembly running in V8 running in Chromium running in Electron.&lt;/p&gt;
&lt;p&gt;The plugin implementation is &lt;a href="https://github.com/simonw/datasette-jupyterlite/blob/0.1a0/datasette_jupyterlite/__init__.py"&gt;just 30 lines of code&lt;/a&gt; - it uses the &lt;a href="https://pypi.org/project/jupyterlite/"&gt;jupyterlite&lt;/a&gt; Python package which bundles a &lt;code&gt;.tgz&lt;/code&gt; file containing all of the required static assets, then serves files directly out of that tarfile.&lt;/p&gt;
&lt;h4&gt;Also this week&lt;/h4&gt;
&lt;p&gt;My other projects from this week are already written about on the blog:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://simonwillison.net/2021/Nov/3/s3-credentials/"&gt;s3-credentials: a tool for creating credentials for S3 buckets&lt;/a&gt; introduces a new CLI tool I built that automates the process of creating new, long-lived credentials that provide read-only, read-write or write-only access to just a single specified S3 bucket.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://simonwillison.net/2021/Nov/4/publish-open-source-python-library/"&gt;How to build, test and publish an open source Python library&lt;/a&gt; is a detailed write-up of the 10 minute workshop I presented at PyGotham this year showing how to create a Python library, bundle it up as a package using &lt;code&gt;setup.py&lt;/code&gt;, publish it to &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt; and then set up GitHub Actions to test and publish future releases.&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/s3-credentials"&gt;s3-credentials&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/s3-credentials/releases/tag/0.4"&gt;0.4&lt;/a&gt; - (&lt;a href="https://github.com/simonw/s3-credentials/releases"&gt;4 releases total&lt;/a&gt;) - 2021-11-04
&lt;br /&gt;A tool for creating credentials for accessing S3 buckets&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.2a0"&gt;0.2a0&lt;/a&gt; - (&lt;a href="https://github.com/simonw/datasette-notebook/releases"&gt;4 releases total&lt;/a&gt;) - 2021-11-02
&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-jupyterlite"&gt;datasette-jupyterlite&lt;/a&gt;&lt;/strong&gt;: &lt;a href="https://github.com/simonw/datasette-jupyterlite/releases/tag/0.1a0"&gt;0.1a0&lt;/a&gt; - 2021-11-01
&lt;br /&gt;JupyterLite as a Datasette plugin&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/pytest/pytest-recording-vcr"&gt;Using VCR and pytest with pytest-recording&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://til.simonwillison.net/pytest/pytest-mock-calls"&gt;Quick and dirty mock testing with mock_calls&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/webassembly"&gt;webassembly&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pyodide"&gt;pyodide&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="projects"/><category term="jupyter"/><category term="datasette"/><category term="webassembly"/><category term="weeknotes"/><category term="pyodide"/></entry><entry><title>Proof of concept: sqlite_utils magic for Jupyter</title><link href="https://simonwillison.net/2020/Oct/21/magic/#atom-tag" rel="alternate"/><published>2020-10-21T17:26:43+00:00</published><updated>2020-10-21T17:26:43+00:00</updated><id>https://simonwillison.net/2020/Oct/21/magic/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://gist.github.com/psychemedia/3187bce18ffdd79bf258d6011ec301b3"&gt;Proof of concept: sqlite_utils magic for Jupyter&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Tony Hirst has been experimenting with building a Jupyter “magic” that adds special syntax for using sqlite-utils to insert data and run queries. Query results come back as a Pandas DataFrame, which Jupyter then displays as a table.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/pandas"&gt;pandas&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tony-hirst"&gt;tony-hirst&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite-utils"&gt;sqlite-utils&lt;/a&gt;&lt;/p&gt;



</summary><category term="pandas"/><category term="sqlite"/><category term="tony-hirst"/><category term="jupyter"/><category term="sqlite-utils"/></entry><entry><title>Estimating COVID-19's Rt in Real-Time</title><link href="https://simonwillison.net/2020/Apr/20/estimating-covid-19s-rt-real-time/#atom-tag" rel="alternate"/><published>2020-04-20T15:06:53+00:00</published><updated>2020-04-20T15:06:53+00:00</updated><id>https://simonwillison.net/2020/Apr/20/estimating-covid-19s-rt-real-time/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/k-sys/covid-19/blob/master/Realtime%20R0.ipynb"&gt;Estimating COVID-19&amp;#x27;s Rt in Real-Time&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I’m not qualified to comment on the mathematical approach, but this is a really nice example of a Jupyter Notebook explanatory essay by Kevin Systrom.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/covid19"&gt;covid19&lt;/a&gt;&lt;/p&gt;



</summary><category term="jupyter"/><category term="covid19"/></entry><entry><title>Weeknotes: Datasette 0.39 and many other projects</title><link href="https://simonwillison.net/2020/Mar/25/weeknotes/#atom-tag" rel="alternate"/><published>2020-03-25T05:33:19+00:00</published><updated>2020-03-25T05:33:19+00:00</updated><id>https://simonwillison.net/2020/Mar/25/weeknotes/#atom-tag</id><summary type="html">
    &lt;p&gt;This week's theme: Well, I'm not going anywhere. So a ton of progress to report on various projects.&lt;/p&gt;

&lt;h3 id="weeknotes-datasette-0-39"&gt;Datasette 0.39&lt;/h3&gt;

&lt;p&gt;This evening I shipped &lt;a href="https://datasette.readthedocs.io/en/stable/changelog.html#v0-39"&gt;Datasette 0.39&lt;/a&gt;. The two big features are a mechanism for setting the default sort order for tables and a new &lt;code&gt;base_url&lt;/code&gt; configuration setting.&lt;/p&gt;

&lt;p&gt;You can see the new default sort order in action &lt;a href="https://covid-19.datasettes.com/covid/daily_reports"&gt;on my Covid-19 project&lt;/a&gt; - the daily reports now default to sort by day descending so the most recent figures show up first. Here's &lt;a href="https://covid-19.datasettes.com/-/metadata.json"&gt;the metadata&lt;/a&gt; that makes it happen, and here's the &lt;a href="https://datasette.readthedocs.io/en/stable/metadata.html#setting-a-default-sort-order"&gt;new documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I had to do some extra work on that project this morning when the underlying data &lt;a href="https://github.com/simonw/covid-19-datasette/issues/4"&gt;changed its CSV column headings&lt;/a&gt; without warning.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;base_url&lt;/code&gt; feature has been &lt;a href="https://github.com/simonw/datasette/issues/394"&gt;an open issue&lt;/a&gt; since Janunary 2019. It lets you run Datasette behind a proxy on a different URL prefix - &lt;code&gt;/tools/datasette/&lt;/code&gt; for example. The trigger for finally getting this solved was &lt;a href="https://twitter.com/betatim/status/1242217777282285572"&gt;a Twitter conversation&lt;/a&gt; about running Datasette on Binder in coordination with a Jupyter notebook.&lt;/p&gt;

&lt;p&gt;Tony Hirst &lt;a href="https://github.com/psychemedia/jupyterserverproxy-datasette-demo"&gt;did some work on this&lt;/a&gt; last year, but was stumped by the lack of a &lt;code&gt;base_url&lt;/code&gt; equivalent. Terry Jones &lt;a href="https://github.com/simonw/datasette/pull/652"&gt;shared an implementation&lt;/a&gt; in December. I finally found the inspiration to pull it all together, and ended up wih &lt;a href="https://github.com/simonw/jupyterserverproxy-datasette-demo"&gt;a working fork&lt;/a&gt; of Tony's project which does indeed launch Datasette on Binder - &lt;a href="https://mybinder.org/v2/gh/simonw/jupyterserverproxy-datasette-demo/master?urlpath=datasette"&gt;try launching your own here&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="weeknotes-github-to-sqlite"&gt;github-to-sqlite&lt;/h3&gt;

&lt;p&gt;I've not done much work on my &lt;a href="https://simonwillison.net/tags/dogsheep/"&gt;Dogsheep&lt;/a&gt; family of tools in a while. That changed this week: in particular, I shipped a &lt;a href="https://github.com/dogsheep/github-to-sqlite/releases/tag/1.0"&gt;1.0&lt;/a&gt; of &lt;a href="https://github.com/dogsheep/github-to-sqlite"&gt;github-to-sqlite&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;As you might expect, it's a tool for importing GitHub data into a SQLite database. Today it can handle repositories, releases, release assets, commits, issues and issue comments. You can see a live demo built from &lt;a href="https://github.com/dogsheep"&gt;Dogsheep organization&lt;/a&gt; data at &lt;a href="https://github-to-sqlite.dogsheep.net/"&gt;github-to-sqlite.dogsheep.net&lt;/a&gt; (deployed by &lt;a href="https://github.com/dogsheep/github-to-sqlite/blob/master/.github/workflows/deploy-demo.yml"&gt;this GitHub action&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;I built this tool primarily to help me better keep track of all of my projects. Pulling the issues into a single database means I can run queries against all open issues across all of my repositories, and imporing commits and releases is handy for when I want to write my weeknotes and need to figure out what I've worked on lately.&lt;/p&gt;

&lt;h3 id="weeknotes-datasette-render-markdown"&gt;datasette-render-markdown&lt;/h3&gt;

&lt;p&gt;GitHub issues use Markdown. To correctly display them it's useful to be able to render that Markdown. I built &lt;a href="https://github.com/simonw/datasette-render-markdown"&gt;datasette-render-markdown&lt;/a&gt; back &lt;a href="https://simonwillison.net/2019/Nov/11/weeknotes-8/#datasetterendermarkdown_81"&gt;in November&lt;/a&gt;, but this week I made some substantial upgrades: you can now &lt;a href="https://github.com/simonw/datasette-render-markdown/blob/1.1.1/README.md#usage"&gt;configure which columns should be rendered&lt;/a&gt;, and it includes &lt;a href="https://github.com/simonw/datasette-render-markdown/blob/1.1.1/README.md#markdown-extensions"&gt;support for Markdown extensions&lt;/a&gt; including GitHub-Flavored Markdown.&lt;/p&gt;

&lt;p&gt;You can see it in action on &lt;a href="https://github-to-sqlite.dogsheep.net/github/issues"&gt;the github-to-sqlite demo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I also upgraded &lt;a href="https://github.com/simonw/datasette-render-timestamps"&gt;datasette-render-timestamps&lt;/a&gt; with the same explicit column configuration pattern.&lt;/p&gt;

&lt;h3 id="weeknotes-datasette-publish-fly"&gt;datasette-publish-fly&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://fly.io/"&gt;Fly&lt;/a&gt; is a relatively new hosting provider which lets you host applications bundled as Docker containers in load-balanced data centers geographically close to your users.&lt;/p&gt;

&lt;p&gt;It has a couple of characteristics that make it a really good fit for Datasette.&lt;/p&gt;

&lt;p&gt;Firstly, the &lt;a href="https://fly.io/docs/pricing/"&gt;pricing model&lt;/a&gt;: Fly will currently host a tiny (128MB of RAM) container for $2.67/month - and they give you $10/month of free service credit, enough for 3 containers.&lt;/p&gt;

&lt;p&gt;It turns out Datasette runs just fine in 128MB of RAM, so that's three always-on Datasette containers! (Unlike Heroku and Cloud Run, Fly keeps your containers running rather than scaling them to zero).&lt;/p&gt;

&lt;p&gt;Secondly, it works by shipping it a Dockerfile. This means building &lt;a href="https://datasette.readthedocs.io/en/stable/publish.html"&gt;datasette publish&lt;/a&gt; support for it is really easy.&lt;/p&gt;

&lt;p&gt;I added the &lt;a href="https://datasette.readthedocs.io/en/stable/plugins.html#publish-subcommand-publish"&gt;publish_subcommand&lt;/a&gt; plugin hook to Datasette all the way back in &lt;a href="https://datasette.readthedocs.io/en/stable/changelog.html#v0-25"&gt;0.25&lt;/a&gt; in September 2018, but I've never actually built anything with it. That's now changed: &lt;a href="https://github.com/simonw/datasette-publish-fly"&gt;datasette-publish-fly&lt;/a&gt; uses the hook to add a &lt;code&gt;datasette publish fly&lt;/code&gt; command for publishing databases directly to your Fly account.&lt;/p&gt;

&lt;h3 id="weeknotes-hacker-news-to-sqlite"&gt;hacker-news-to-sqlite&lt;/h3&gt;

&lt;p&gt;It turns out I created my &lt;a href="https://news.ycombinator.com/"&gt;Hacker News&lt;/a&gt; account in 2007, and I've posted 2,167 comments and submitted 131 stories since then. Since my personal Dogsheep project is about pulling my data from multiple sources into a single place it made sense to build a tool for importing from Hacker News.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/dogsheep/hacker-news-to-sqlite"&gt;hacker-news-to-sqlite&lt;/a&gt; uses the official &lt;a href="https://github.com/HackerNews/API"&gt;Hacker News API&lt;/a&gt; to import every comment and story posted by a specific user. It can also use one or more item IDs to suck the entire discussion tree around those items.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/dogsheep/hacker-news-to-sqlite/blob/c8697b3e4ef044412209b52c70548fedbcb346c7/README.md#browsing-your-data-with-datasette"&gt;README&lt;/a&gt; includes detailed documentation on how to best browse your data using Datasette once you have imported it.&lt;/p&gt;

&lt;h3 id="weeknotes-other-projects"&gt;Other projects&lt;/h3&gt;

&lt;ul&gt;&lt;li&gt;&lt;a href="https://github.com/simonw/sqlite-utils"&gt;sqlite-utils&lt;/a&gt; gained some improvements to the way it suggests types for existing columns.&lt;/li&gt;&lt;li&gt;&lt;a href="https://github.com/dogsheep/twitter-to-sqlite"&gt;twitter-to-sqlite&lt;/a&gt; now offers &lt;code&gt;--sql&lt;/code&gt; and &lt;code&gt;--attach&lt;/code&gt; for more of its subcommands.&lt;/li&gt;&lt;li&gt;&lt;a href="https://github.com/simonw/datasette-show-errors"&gt;datasette-show-errors&lt;/a&gt; is a new plugin which exposes 500 errors as tracebacks, like Django does with &lt;code&gt;DEBUG=True&lt;/code&gt;. It's built on top of &lt;a href="https://www.starlette.io/middleware/"&gt;Starlette's ServerErrorMiddleware&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;I upgraded &lt;a href="https://github.com/dogsheep/inaturalist-to-sqlite"&gt;inaturalist-to-sqlite&lt;/a&gt; to work with &lt;code&gt;sqlite-utils&lt;/code&gt; 2.x.&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/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/markdown"&gt;markdown&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/dogsheep"&gt;dogsheep&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/weeknotes"&gt;weeknotes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/fly"&gt;fly&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="github"/><category term="projects"/><category term="sqlite"/><category term="markdown"/><category term="jupyter"/><category term="datasette"/><category term="dogsheep"/><category term="weeknotes"/><category term="fly"/></entry><entry><title>selenium-demoscraper</title><link href="https://simonwillison.net/2019/Nov/4/selenium-demoscraper/#atom-tag" rel="alternate"/><published>2019-11-04T15:05:38+00:00</published><updated>2019-11-04T15:05:38+00:00</updated><id>https://simonwillison.net/2019/Nov/4/selenium-demoscraper/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/psychemedia/selenium-demoscraper"&gt;selenium-demoscraper&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Really useful minimal example of a Binder project. Click the button to launch a Jupyter notebook in Binder that can take screenshots of URLs using Selenium-controlled headless Firefox. The binder/ folder uses an apt.txt file to install Firefox, requirements.txt to get some Python dependencies and a postBuild Python script to download the Gecko Selenium driver.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/psychemedia/status/1191360534832205824"&gt;Tony Hirst&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/firefox"&gt;firefox&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/selenium"&gt;selenium&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/tony-hirst"&gt;tony-hirst&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;&lt;/p&gt;



</summary><category term="firefox"/><category term="selenium"/><category term="tony-hirst"/><category term="jupyter"/></entry><entry><title>Los Angeles Weedmaps analysis</title><link href="https://simonwillison.net/2019/May/30/los-angeles-weedmaps-analysis/#atom-tag" rel="alternate"/><published>2019-05-30T04:35:42+00:00</published><updated>2019-05-30T04:35:42+00:00</updated><id>https://simonwillison.net/2019/May/30/los-angeles-weedmaps-analysis/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://nbviewer.jupyter.org/github/datadesk/la-weedmaps-analysis/blob/master/notebook.ipynb"&gt;Los Angeles Weedmaps analysis&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Ben Welsh at the LA Times published this Jupyter notebook showing the full working behind a story they published about LA’s black market weed dispensaries. I picked up several useful tricks from it—including how to load points into a geopandas GeoDataFrame (in epsg:4326 aka WGS 84) and how to then join that against the LA Times neighborhoods GeoJSON boundaries file.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/palewire/status/1133723284116086784"&gt;Ben Welsh&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/geospatial"&gt;geospatial&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/latimes"&gt;latimes&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pandas"&gt;pandas&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ben-welsh"&gt;ben-welsh&lt;/a&gt;&lt;/p&gt;



</summary><category term="data-journalism"/><category term="geospatial"/><category term="latimes"/><category term="pandas"/><category term="jupyter"/><category term="ben-welsh"/></entry><entry><title>Fast Autocomplete Search for Your Website</title><link href="https://simonwillison.net/2018/Dec/19/fast-autocomplete-search-your-website/#atom-tag" rel="alternate"/><published>2018-12-19T00:26:32+00:00</published><updated>2018-12-19T00:26:32+00:00</updated><id>https://simonwillison.net/2018/Dec/19/fast-autocomplete-search-your-website/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://24ways.org/2018/fast-autocomplete-search-for-your-website/"&gt;Fast Autocomplete Search for Your Website&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
I wrote a tutorial for the 24 ways advent calendar on building fast autocomplete search for a website on top of Datasette and SQLite. I built the demo against 24 ways itself—I used wget to recursively fetch all 330 articles as HTML, then wrote code in a Jupyter notebook to extract the raw data from them (with BeautifulSoup) and load them into SQLite using my sqlite-utils Python library. I deployed the resulting database using Datasette, then wrote some vanilla JavaScript to implement autocomplete using fast SQL queries against the Datasette JSON API.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/24-ways"&gt;24-ways&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/autocomplete"&gt;autocomplete&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/beautifulsoup"&gt;beautifulsoup&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/search"&gt;search&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/sqlite"&gt;sqlite&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;&lt;/p&gt;



</summary><category term="24-ways"/><category term="autocomplete"/><category term="beautifulsoup"/><category term="search"/><category term="sqlite"/><category term="jupyter"/><category term="datasette"/></entry><entry><title>The _repr_html_ method in Jupyter notebooks</title><link href="https://simonwillison.net/2018/Dec/12/repr-html-jupyter/#atom-tag" rel="alternate"/><published>2018-12-12T18:09:23+00:00</published><updated>2018-12-12T18:09:23+00:00</updated><id>https://simonwillison.net/2018/Dec/12/repr-html-jupyter/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://ipython.readthedocs.io/en/stable/config/integrating.html#rich-display"&gt;The _repr_html_ method in Jupyter notebooks&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Today I learned that if you add a _repr_html_ method returning a string of HTML to any Python class Jupyter notebooks will render that HTML inline to represent that object.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://gist.github.com/ontouchstart//ea1631f69e507a81a9d9ec56b79e4d11"&gt;A Gist by Sam Liu&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;&lt;/p&gt;



</summary><category term="jupyter"/></entry><entry><title>repo2docker</title><link href="https://simonwillison.net/2018/Nov/28/repo2docker/#atom-tag" rel="alternate"/><published>2018-11-28T22:06:42+00:00</published><updated>2018-11-28T22:06:42+00:00</updated><id>https://simonwillison.net/2018/Nov/28/repo2docker/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/jupyter/repo2docker"&gt;repo2docker&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Neat tool from the Jupyter project team: run “jupyter-repo2docker https://github.com/norvig/pytudes” and it will pull a GitHub repository, create a new Docker container for it, install Jupyter and launch a Jupyter instance for you to start trying out the library. I’ve been doing this by hand using virtual environments, but using Docker for even cleaner isolation seems like a smart improvement.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/WillingCarol/status/1067893024821321737"&gt;Carol Willing&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


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



</summary><category term="docker"/><category term="jupyter"/></entry><entry><title>Helicopter accident analysis notebook</title><link href="https://simonwillison.net/2018/Nov/19/helicopter-accident-analysis/#atom-tag" rel="alternate"/><published>2018-11-19T18:25:07+00:00</published><updated>2018-11-19T18:25:07+00:00</updated><id>https://simonwillison.net/2018/Nov/19/helicopter-accident-analysis/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://nbviewer.jupyter.org/github/datadesk/helicopter-accident-analysis/blob/master/notebook.ipynb"&gt;Helicopter accident analysis notebook&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Ben Welsh worked on an article for the LA Times about helicopter accident rates, and has published the underlying analysis as an extremely detailed Jupyter notebook. Lots of neat new (to me) notebook tricks in here as well.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/palewire/status/1064189967289659392"&gt;Ben Welsh&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/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ben-welsh"&gt;ben-welsh&lt;/a&gt;&lt;/p&gt;



</summary><category term="data-journalism"/><category term="jupyter"/><category term="ben-welsh"/></entry><entry><title>Tracking Jupyter: Newsletter, the Third...</title><link href="https://simonwillison.net/2018/Nov/9/tracking-jupyter-newsletter/#atom-tag" rel="alternate"/><published>2018-11-09T17:42:28+00:00</published><updated>2018-11-09T17:42:28+00:00</updated><id>https://simonwillison.net/2018/Nov/9/tracking-jupyter-newsletter/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://tinyletter.com/TrackingJupyter/letters/tracking-jupyter-newsletter-the-third"&gt;Tracking Jupyter: Newsletter, the Third...&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Tony Hirst’s tracking Jupyter newsletter is fantastic. The Jupyter ecosystem is incredibly exciting and fast moving at the moment as more and more groups discover how productive it is, and Tony’s newsletter is a wealth of information on what’s going on out there.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/mrchrisadams/status/1060861087594307585"&gt;Chris Adams&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


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



</summary><category term="tony-hirst"/><category term="jupyter"/></entry><entry><title>Computational and Inferential Thinking: The Foundations of Data Science</title><link href="https://simonwillison.net/2018/Aug/25/computational-and-inferential-thinking-foundations-data-science/#atom-tag" rel="alternate"/><published>2018-08-25T22:13:51+00:00</published><updated>2018-08-25T22:13:51+00:00</updated><id>https://simonwillison.net/2018/Aug/25/computational-and-inferential-thinking-foundations-data-science/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.inferentialthinking.com/"&gt;Computational and Inferential Thinking: The Foundations of Data Science&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Free online textbook written for the UC Berkeley Foundations of Data Science class. The examples are all provided as Jupyter notebooks, using the mybinder web application to allow students to launch interactive notebooks for any of the examples without having to install any software on their own machines.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/education"&gt;education&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/data-science"&gt;data-science&lt;/a&gt;&lt;/p&gt;



</summary><category term="education"/><category term="jupyter"/><category term="data-science"/></entry><entry><title>The Future of Notebooks: Lessons from JupyterCon</title><link href="https://simonwillison.net/2018/Aug/25/future-notebooks-lessons-jupytercon/#atom-tag" rel="alternate"/><published>2018-08-25T21:55:20+00:00</published><updated>2018-08-25T21:55:20+00:00</updated><id>https://simonwillison.net/2018/Aug/25/future-notebooks-lessons-jupytercon/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="http://willcrichton.net/notes/lessons-from-jupytercon/"&gt;The Future of Notebooks: Lessons from JupyterCon&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
It sounds like reactive notebooks (where cells keep track of their dependencies on other cells and re-evaluate when those update) were a hot topic at JupyterCon this year.

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://news.ycombinator.com/item?id=17839188"&gt;Hacker News&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;&lt;/p&gt;



</summary><category term="jupyter"/></entry><entry><title>Quoting Jake VanderPlas</title><link href="https://simonwillison.net/2018/Aug/25/jake-vanderplas/#atom-tag" rel="alternate"/><published>2018-08-25T03:16:26+00:00</published><updated>2018-08-25T03:16:26+00:00</updated><id>https://simonwillison.net/2018/Aug/25/jake-vanderplas/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://twitter.com/jakevdp/status/1032810402227187712"&gt;&lt;p&gt;In case you missed it: @GoogleColab can open any @ProjectJupyter notebook directly from @github!&lt;/p&gt;
&lt;p&gt;To run the notebook, just replace "github.com" with "colab.research.google.com/github/" in the notebook URL, and it will be loaded into Colab.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://twitter.com/jakevdp/status/1032810402227187712"&gt;Jake VanderPlas&lt;/a&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/jupyter"&gt;jupyter&lt;/a&gt;&lt;/p&gt;



</summary><category term="github"/><category term="jupyter"/></entry><entry><title>I don't like Jupyter Notebooks - a presentation by Joel Grus</title><link href="https://simonwillison.net/2018/Aug/25/i-dont-jupyter-notebooks/#atom-tag" rel="alternate"/><published>2018-08-25T03:04:45+00:00</published><updated>2018-08-25T03:04:45+00:00</updated><id>https://simonwillison.net/2018/Aug/25/i-dont-jupyter-notebooks/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://docs.google.com/presentation/d/1n2RlMdmv1p25Xy5thJUhkKGvjtV-dkAIsUXP-AL4ffI/edit"&gt;I don&amp;#x27;t like Jupyter Notebooks - a presentation by Joel Grus&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Fascinating talk by Joel Grus at the Jupyter conference in New York. He highlights some of the drawbacks of he Jupyter way of working, including the huge confusion that can come from the ability to execute cells out of order (something Observable notebooks solve brilliantly using spreadsheet-style reactive cell associations). He also makes strong arguments that notebooks encourage a way of working that discourages people from producing stable, repeatable and well tested code.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/observable"&gt;observable&lt;/a&gt;&lt;/p&gt;



</summary><category term="jupyter"/><category term="observable"/></entry><entry><title>Beyond Interactive: Notebook Innovation at Netflix</title><link href="https://simonwillison.net/2018/Aug/18/netflix-jupyter/#atom-tag" rel="alternate"/><published>2018-08-18T17:55:42+00:00</published><updated>2018-08-18T17:55:42+00:00</updated><id>https://simonwillison.net/2018/Aug/18/netflix-jupyter/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://medium.com/netflix-techblog/notebook-innovation-591ee3221233"&gt;Beyond Interactive: Notebook Innovation at Netflix&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Netflix have been investing heavily in their internal Jupyter notebooks infrastructure: it’s now the most popular tool for working with data at Netflix. They also use parameterized notebooks to make it easy to create templates for reusable operations, and scheduled notebooks for recurring tasks. “When a Spark or Presto job executes from the scheduler, the source code is injected into a newly-created notebook and executed. That notebook then becomes an immutable historical record, containing all related artifacts — including source code, parameters, runtime config, execution logs, error messages, and so on.”


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/netflix"&gt;netflix&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;&lt;/p&gt;



</summary><category term="netflix"/><category term="jupyter"/></entry><entry><title>Quoting Netflix Technology Blog</title><link href="https://simonwillison.net/2018/Aug/18/netflix-technology-blog/#atom-tag" rel="alternate"/><published>2018-08-18T17:35:45+00:00</published><updated>2018-08-18T17:35:45+00:00</updated><id>https://simonwillison.net/2018/Aug/18/netflix-technology-blog/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://medium.com/netflix-techblog/notebook-innovation-591ee3221233"&gt;&lt;p&gt;Every day more than 1 trillion events are written into a streaming ingestion pipeline, which is processed and written to a 100PB cloud-native data warehouse. And every day, our users run more than 150,000 jobs against this data, spanning everything from reporting and analysis to machine learning and recommendation algorithms.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://medium.com/netflix-techblog/notebook-innovation-591ee3221233"&gt;Netflix Technology Blog&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/netflix"&gt;netflix&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/big-data"&gt;big-data&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/jupyter"&gt;jupyter&lt;/a&gt;&lt;/p&gt;



</summary><category term="netflix"/><category term="big-data"/><category term="jupyter"/></entry><entry><title>Quoting Chris Rogers</title><link href="https://simonwillison.net/2018/Jun/5/chris-rogers/#atom-tag" rel="alternate"/><published>2018-06-05T19:37:10+00:00</published><updated>2018-06-05T19:37:10+00:00</updated><id>https://simonwillison.net/2018/Jun/5/chris-rogers/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://news.ycombinator.com/item?id=17237318"&gt;&lt;p&gt;At Harvard we've built out an infrastructure to allow us to deploy JupyterHub to courses with authentication managed by Canvas. It has allowed us to easily deploy complex set-ups to students so they can do really cool stuff without having to spend hours walking them through setup. Instructors are writing their lectures as IPython notebooks, and distributing them to students, who then work through them in their JupyterHub environment. Our most ambitious so far has been setting up each student in the course with a p2.xlarge machine with cuda and TensorFlow so they could do deep learning work for their final projects. We supported 15 courses last year, and got deployment time for an implementation down to only 2-3 hours.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://news.ycombinator.com/item?id=17237318"&gt;Chris Rogers&lt;/a&gt;&lt;/p&gt;

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



</summary><category term="education"/><category term="python"/><category term="jupyter"/></entry></feed>