<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: rich</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/rich.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2025-09-02T11:05:23+00:00</updated><author><name>Simon Willison</name></author><entry><title>Rich Pixels</title><link href="https://simonwillison.net/2025/Sep/2/rich-pixels/#atom-tag" rel="alternate"/><published>2025-09-02T11:05:23+00:00</published><updated>2025-09-02T11:05:23+00:00</updated><id>https://simonwillison.net/2025/Sep/2/rich-pixels/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/darrenburns/rich-pixels"&gt;Rich Pixels&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Neat Python library by Darren Burns adding pixel image support to the Rich terminal library, using tricks to render an image using full or half-height colored blocks.&lt;/p&gt;
&lt;p&gt;Here's &lt;a href="https://github.com/darrenburns/rich-pixels/blob/a0745ebcc26b966d9dbac5875720364ee5c6a1d3/rich_pixels/_renderer.py#L123C25-L123C26"&gt;the key trick&lt;/a&gt; - it renders Unicode ▄ (U+2584, "lower half block") characters after setting a foreground and background color for the two pixels it needs to display.&lt;/p&gt;
&lt;p&gt;I got GPT-5 to &lt;a href="https://chatgpt.com/share/68b6c443-2408-8006-8f4a-6862755cd1e4"&gt;vibe code up&lt;/a&gt; a &lt;code&gt;show_image.py&lt;/code&gt; terminal command which resizes the provided image to fit the width and height of the current terminal and displays it using Rich Pixels. That &lt;a href="https://github.com/simonw/tools/blob/main/python/show_image.py"&gt;script is here&lt;/a&gt;, you can run it with &lt;code&gt;uv&lt;/code&gt; like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uv run https://tools.simonwillison.net/python/show_image.py \
  image.jpg
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's what I got when I ran it against my V&amp;amp;A East Storehouse photo from &lt;a href="https://simonwillison.net/2025/Aug/27/london-culture/"&gt;this post&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Terminal window. I ran that command and it spat out quite a pleasing and recognizable pixel art version of the photograph." src="https://static.simonwillison.net/static/2025/pixel-storehouse.jpg" /&gt;


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/ascii-art"&gt;ascii-art&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/unicode"&gt;unicode&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&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/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/vibe-coding"&gt;vibe-coding&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gpt-5"&gt;gpt-5&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rich"&gt;rich&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/gpt"&gt;gpt&lt;/a&gt;&lt;/p&gt;



</summary><category term="ascii-art"/><category term="cli"/><category term="python"/><category term="unicode"/><category term="ai"/><category term="generative-ai"/><category term="llms"/><category term="uv"/><category term="vibe-coding"/><category term="gpt-5"/><category term="rich"/><category term="gpt"/></entry><entry><title>Building Python tools with a one-shot prompt using uv run and Claude Projects</title><link href="https://simonwillison.net/2024/Dec/19/one-shot-python-tools/#atom-tag" rel="alternate"/><published>2024-12-19T07:00:37+00:00</published><updated>2024-12-19T07:00:37+00:00</updated><id>https://simonwillison.net/2024/Dec/19/one-shot-python-tools/#atom-tag</id><summary type="html">
    &lt;p&gt;I've written a lot about how I've been using Claude to build one-shot HTML+JavaScript applications &lt;a href="https://simonwillison.net/tags/claude-artifacts/"&gt;via Claude Artifacts&lt;/a&gt;. I recently started using a similar pattern to create one-shot Python utilities, using a custom Claude Project combined with the dependency management capabilities of &lt;a href="https://github.com/astral-sh/uv"&gt;uv&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(In LLM jargon a "one-shot" prompt is a prompt that produces the complete desired result on the first attempt. Confusingly it also sometimes means a prompt that includes a single example of the desired output format. Here I'm using the first of those two definitions.)&lt;/p&gt;
&lt;p&gt;I'll start with an example of a tool I built that way.&lt;/p&gt;
&lt;p&gt;I had another round of battle with Amazon S3 today trying to figure out why a file in one of my buckets couldn't be accessed via a public URL.&lt;/p&gt;
&lt;p&gt;Out of frustration I prompted Claude with a variant of the following (&lt;a href="https://gist.github.com/simonw/9f69cf35889b0445b80eeed691d44504"&gt;full transcript here&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;I can't access the file at EXAMPLE_S3_URL. Write me a Python CLI tool using Click and boto3 which takes a URL of that form and then uses EVERY single boto3 trick in the book to try and debug why the file is returning a 404&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It wrote me &lt;a href="https://github.com/simonw/tools/blob/main/python/debug_s3_access.py"&gt;this script&lt;/a&gt;, which gave me exactly what I needed. I ran it like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run debug_s3_access.py \
  https://test-public-bucket-simonw.s3.us-east-1.amazonaws.com/0f550b7b28264d7ea2b3d360e3381a95.jpg&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2024/debug-s3.jpg" alt="Terminal screenshot showing S3 access analysis results. Command: '$ uv run http://tools.simonwillison.net/python/debug_s3_access.py url-to-image' followed by detailed output showing bucket exists (Yes), region (default), key exists (Yes), bucket policy (AllowAllGetObject), bucket owner (swillison), versioning (Not enabled), content type (image/jpeg), size (71683 bytes), last modified (2024-12-19 03:43:30+00:00) and public access settings (all False)" style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;You can &lt;a href="https://github.com/simonw/tools/tree/main/python#debug_s3_accesspy"&gt;see the text output here&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="inline-dependencies-and-uv-run"&gt;Inline dependencies and uv run&lt;/h4&gt;
&lt;p&gt;Crucially, I didn't have to take any extra steps to install any of the dependencies that the script needed. That's because the script starts with this magic comment:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;# /// script&lt;/span&gt;
&lt;span class="pl-c"&gt;# requires-python = "&amp;gt;=3.12"&lt;/span&gt;
&lt;span class="pl-c"&gt;# dependencies = [&lt;/span&gt;
&lt;span class="pl-c"&gt;#     "click",&lt;/span&gt;
&lt;span class="pl-c"&gt;#     "boto3",&lt;/span&gt;
&lt;span class="pl-c"&gt;#     "urllib3",&lt;/span&gt;
&lt;span class="pl-c"&gt;#     "rich",&lt;/span&gt;
&lt;span class="pl-c"&gt;# ]&lt;/span&gt;
&lt;span class="pl-c"&gt;# ///&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;This is an example of &lt;a href="https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies"&gt;inline script dependencies&lt;/a&gt;, a feature described in &lt;a href="https://peps.python.org/pep-0723/"&gt;PEP 723&lt;/a&gt; and implemented by &lt;code&gt;uv run&lt;/code&gt;. Running the script causes &lt;code&gt;uv&lt;/code&gt; to create a temporary virtual environment with those dependencies installed, a process that takes just a few milliseconds once the &lt;code&gt;uv&lt;/code&gt; cache has been populated.&lt;/p&gt;
&lt;p&gt;This even works if the script is specified by a URL! Anyone with &lt;code&gt;uv&lt;/code&gt; installed can run the following command (provided you trust me not to have replaced the script with something malicious) to debug one of their own S3 buckets:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run http://tools.simonwillison.net/python/debug_s3_access.py \
  https://test-public-bucket-simonw.s3.us-east-1.amazonaws.com/0f550b7b28264d7ea2b3d360e3381a95.jpg&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="writing-these-with-the-help-of-a-claude-project"&gt;Writing these with the help of a Claude Project&lt;/h4&gt;
&lt;p&gt;The reason I can one-shot scripts like this now is that I've set up a &lt;a href="https://www.anthropic.com/news/projects"&gt;Claude Project&lt;/a&gt; called "Python app". Projects can have custom instructions, and I used those to "teach" Claude how to take advantage of inline script dependencies:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You write Python tools as single files. They always start with this comment:&lt;/p&gt;
&lt;pre&gt;&lt;span&gt;# /// script&lt;/span&gt;
&lt;span&gt;# requires-python = "&amp;gt;=3.12"&lt;/span&gt;
&lt;span&gt;# ///&lt;/span&gt;&lt;/pre&gt;
&lt;p&gt;These files can include dependencies on libraries such as Click. If they do, those dependencies are included in a list like this one in that same comment (here showing two dependencies):&lt;/p&gt;
&lt;pre&gt;&lt;span&gt;# /// script&lt;/span&gt;
&lt;span&gt;# requires-python = "&amp;gt;=3.12"&lt;/span&gt;
&lt;span&gt;# dependencies = [&lt;/span&gt;
&lt;span&gt;#     "click",&lt;/span&gt;
&lt;span&gt;#     "sqlite-utils",&lt;/span&gt;
&lt;span&gt;# ]&lt;/span&gt;
&lt;span&gt;# ///&lt;/span&gt;&lt;/pre&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's everything Claude needs to reliably knock out full-featured Python tools as single scripts which can be run directly using whatever dependencies Claude chose to include.&lt;/p&gt;
&lt;p&gt;I didn't suggest that Claude use &lt;a href="https://github.com/Textualize/rich"&gt;rich&lt;/a&gt; for the &lt;code&gt;debug_s3_access.py&lt;/code&gt; script earlier but it decided to use it anyway!&lt;/p&gt;
&lt;p&gt;I've only recently started experimenting with this pattern but it seems to work &lt;em&gt;really&lt;/em&gt; well. Here's another example - my prompt was:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;Starlette web app that provides an API where you pass in ?url= and it strips all HTML tags and returns just the text, using beautifulsoup&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here's &lt;a href="https://gist.github.com/simonw/08957a1490ebde1ea38b4a8374989cf8"&gt;the chat transcript&lt;/a&gt; and &lt;a href="https://gist.githubusercontent.com/simonw/08957a1490ebde1ea38b4a8374989cf8/raw/143ee24dc65ca109b094b72e8b8c494369e763d6/strip_html.py"&gt;the raw code it produced&lt;/a&gt;. You can run that server directly on your machine (it uses port 8000) like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run https://gist.githubusercontent.com/simonw/08957a1490ebde1ea38b4a8374989cf8/raw/143ee24dc65ca109b094b72e8b8c494369e763d6/strip_html.py&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then visit &lt;code&gt;http://127.0.0.1:8000/?url=https://simonwillison.net/&lt;/code&gt; to see it in action.&lt;/p&gt;
&lt;h4 id="custom-instructions"&gt;Custom instructions&lt;/h4&gt;
&lt;p&gt;The pattern here that's most interesting to me is using custom instructions or system prompts to show LLMs how to implement new patterns that may not exist in their training data. &lt;code&gt;uv run&lt;/code&gt; is less than a year old, but providing just a short example is enough to get the models to write code that takes advantage of its capabilities.&lt;/p&gt;
&lt;p&gt;I have a similar set of custom instructions I use for creating single page HTML and JavaScript tools, again running in a Claude Project:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never use React in artifacts - always plain HTML and vanilla JavaScript and CSS with minimal dependencies.&lt;/p&gt;
&lt;p&gt;CSS should be indented with two spaces and should start like this:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;style&lt;/span&gt;&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
* {
  box-sizing: border-box;
}&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Inputs and textareas should be font size 16px. Font should always prefer Helvetica.&lt;/p&gt;
&lt;p&gt;JavaScript should be two space indents and start like this:&lt;/p&gt;
&lt;div class="highlight highlight-text-html-basic"&gt;&lt;pre&gt;&lt;span class="pl-kos"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pl-ent"&gt;script&lt;/span&gt; &lt;span class="pl-c1"&gt;type&lt;/span&gt;="&lt;span class="pl-s"&gt;module&lt;/span&gt;"&lt;span class="pl-kos"&gt;&amp;gt;&lt;/span&gt;
// code in here should not be indented at the first level&lt;/pre&gt;&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most of the tools on my &lt;a href="https://tools.simonwillison.net/"&gt;tools.simonwillison.net&lt;/a&gt; site were created using versions of this custom instructions prompt.&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/aws"&gt;aws&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/s3"&gt;s3&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/ai"&gt;ai&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/ai-assisted-programming"&gt;ai-assisted-programming&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude"&gt;claude&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/claude-artifacts"&gt;claude-artifacts&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rich"&gt;rich&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/prompt-to-app"&gt;prompt-to-app&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/starlette"&gt;starlette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="aws"/><category term="cli"/><category term="python"/><category term="s3"/><category term="ai"/><category term="prompt-engineering"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="claude"/><category term="claude-artifacts"/><category term="uv"/><category term="rich"/><category term="prompt-to-app"/><category term="starlette"/></entry><entry><title>Anatomy of a Textual User Interface</title><link href="https://simonwillison.net/2024/Sep/2/anatomy-of-a-textual-user-interface/#atom-tag" rel="alternate"/><published>2024-09-02T16:39:51+00:00</published><updated>2024-09-02T16:39:51+00:00</updated><id>https://simonwillison.net/2024/Sep/2/anatomy-of-a-textual-user-interface/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://textual.textualize.io/blog/2024/09/15/anatomy-of-a-textual-user-interface/"&gt;Anatomy of a Textual User Interface&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Will McGugan used &lt;a href="https://textual.textualize.io/"&gt;Textual&lt;/a&gt; and my &lt;a href="https://llm.datasette.io/en/stable/python-api.html"&gt;LLM Python library&lt;/a&gt; to build a delightful TUI for talking to a simulation of &lt;a href="https://alienanthology.fandom.com/wiki/MU-TH-UR_6000"&gt;Mother&lt;/a&gt;, the AI from the Aliens movies:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Animated screenshot of a terminal app called MotherApp. Mother: INTERFACE 2037 READY FOR INQUIRY. I type: Who is onboard? Mother replies, streaming content to the screen:  The crew of the Nostromo consists of the following personnel: 1. Captain Arthur Dallas - commanding officer. 2. Executive Officer Thomas Kane - second-in-command. 3. Warrant Officer Ellen Ripley - third-in-command. 4. Navigator Joan Lambert - responsible for navigation and communications. 5. Science Officer Ash - responsible for scientific analysis. 6. Engineering Technician Brett - maintenance and repair. 7. Chief Engineer Parker - head of the engineering department. All crew members are currently accounted for. How may I assist you further?" src="https://static.simonwillison.net/static/2024/llm-mother-onboard.gif" /&gt;&lt;/p&gt;
&lt;p&gt;The entire implementation is just &lt;a href="https://gist.github.com/willmcgugan/648a537c9d47dafa59cb8ece281d8c2c"&gt;77 lines of code&lt;/a&gt;. It includes &lt;a href="https://peps.python.org/pep-0723/"&gt;PEP 723&lt;/a&gt; inline dependency information:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-c"&gt;# /// script&lt;/span&gt;
&lt;span class="pl-c"&gt;# requires-python = "&amp;gt;=3.12"&lt;/span&gt;
&lt;span class="pl-c"&gt;# dependencies = [&lt;/span&gt;
&lt;span class="pl-c"&gt;#     "llm",&lt;/span&gt;
&lt;span class="pl-c"&gt;#     "textual",&lt;/span&gt;
&lt;span class="pl-c"&gt;# ]&lt;/span&gt;
&lt;span class="pl-c"&gt;# ///&lt;/span&gt;&lt;/pre&gt;

&lt;p&gt;Which means you can run it in a dedicated environment with the correct dependencies installed using &lt;a href="https://docs.astral.sh/uv/guides/scripts/"&gt;uv run&lt;/a&gt; like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;wget &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;https://gist.githubusercontent.com/willmcgugan/648a537c9d47dafa59cb8ece281d8c2c/raw/7aa575c389b31eb041ae7a909f2349a96ffe2a48/mother.py&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;export&lt;/span&gt; OPENAI_API_KEY=&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;'&lt;/span&gt;sk-...&lt;span class="pl-pds"&gt;'&lt;/span&gt;&lt;/span&gt;
uv run mother.py&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;I found the &lt;code&gt;send_prompt()&lt;/code&gt; method particularly interesting. Textual uses &lt;code&gt;asyncio&lt;/code&gt; for its event loop, but LLM currently only supports synchronous execution and can block for several seconds while retrieving a prompt.&lt;/p&gt;
&lt;p&gt;Will used the Textual &lt;code&gt;@work(thread=True)&lt;/code&gt; decorator, &lt;a href="https://textual.textualize.io/guide/workers/#thread-workers"&gt;documented here&lt;/a&gt;, to run that operation in a thread:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;@&lt;span class="pl-en"&gt;work&lt;/span&gt;(&lt;span class="pl-s1"&gt;thread&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-c1"&gt;True&lt;/span&gt;)&lt;/span&gt;
&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;send_prompt&lt;/span&gt;(&lt;span class="pl-s1"&gt;self&lt;/span&gt;, &lt;span class="pl-s1"&gt;prompt&lt;/span&gt;: &lt;span class="pl-s1"&gt;str&lt;/span&gt;, &lt;span class="pl-s1"&gt;response&lt;/span&gt;: &lt;span class="pl-v"&gt;Response&lt;/span&gt;) &lt;span class="pl-c1"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="pl-c1"&gt;None&lt;/span&gt;:
    &lt;span class="pl-s1"&gt;response_content&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s"&gt;""&lt;/span&gt;
    &lt;span class="pl-s1"&gt;llm_response&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-s1"&gt;self&lt;/span&gt;.&lt;span class="pl-s1"&gt;model&lt;/span&gt;.&lt;span class="pl-en"&gt;prompt&lt;/span&gt;(&lt;span class="pl-s1"&gt;prompt&lt;/span&gt;, &lt;span class="pl-s1"&gt;system&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-v"&gt;SYSTEM&lt;/span&gt;)
    &lt;span class="pl-k"&gt;for&lt;/span&gt; &lt;span class="pl-s1"&gt;chunk&lt;/span&gt; &lt;span class="pl-c1"&gt;in&lt;/span&gt; &lt;span class="pl-s1"&gt;llm_response&lt;/span&gt;:
        &lt;span class="pl-s1"&gt;response_content&lt;/span&gt; &lt;span class="pl-c1"&gt;+=&lt;/span&gt; &lt;span class="pl-s1"&gt;chunk&lt;/span&gt;
        &lt;span class="pl-s1"&gt;self&lt;/span&gt;.&lt;span class="pl-en"&gt;call_from_thread&lt;/span&gt;(&lt;span class="pl-s1"&gt;response&lt;/span&gt;.&lt;span class="pl-s1"&gt;update&lt;/span&gt;, &lt;span class="pl-s1"&gt;response_content&lt;/span&gt;)&lt;/pre&gt;

&lt;p&gt;Looping through the response like that and calling &lt;code&gt;self.call_from_thread(response.update, response_content)&lt;/code&gt; with an accumulated string is all it takes to implement streaming responses in the Textual UI, and that &lt;code&gt;Response&lt;/code&gt; object sublasses &lt;code&gt;textual.widgets.Markdown&lt;/code&gt; so any Markdown is rendered using Rich.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/will-mcgugan"&gt;will-mcgugan&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/textual"&gt;textual&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/llm"&gt;llm&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rich"&gt;rich&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="will-mcgugan"/><category term="textual"/><category term="llm"/><category term="uv"/><category term="rich"/></entry><entry><title>uvtrick</title><link href="https://simonwillison.net/2024/Sep/1/uvtrick/#atom-tag" rel="alternate"/><published>2024-09-01T05:03:23+00:00</published><updated>2024-09-01T05:03:23+00:00</updated><id>https://simonwillison.net/2024/Sep/1/uvtrick/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/koaning/uvtrick"&gt;uvtrick&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
This "fun party trick" by Vincent D. Warmerdam is absolutely brilliant and a little horrifying. The following code:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;uvtrick&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-v"&gt;Env&lt;/span&gt;

&lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;uses_rich&lt;/span&gt;():
    &lt;span class="pl-k"&gt;from&lt;/span&gt; &lt;span class="pl-s1"&gt;rich&lt;/span&gt; &lt;span class="pl-k"&gt;import&lt;/span&gt; &lt;span class="pl-s1"&gt;print&lt;/span&gt;
    &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s"&gt;"hi :vampire:"&lt;/span&gt;)

&lt;span class="pl-v"&gt;Env&lt;/span&gt;(&lt;span class="pl-s"&gt;"rich"&lt;/span&gt;, &lt;span class="pl-s1"&gt;python&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s"&gt;"3.12"&lt;/span&gt;).&lt;span class="pl-en"&gt;run&lt;/span&gt;(&lt;span class="pl-s1"&gt;uses_rich&lt;/span&gt;)&lt;/pre&gt;

&lt;p&gt;Executes that &lt;code&gt;uses_rich()&lt;/code&gt; function in a fresh virtual environment managed by &lt;a href="https://docs.astral.sh/uv/"&gt;uv&lt;/a&gt;, running the specified Python version (3.12) and ensuring the &lt;a href="https://github.com/Textualize/rich"&gt;rich&lt;/a&gt; package is available - even if it's not installed in the current environment.&lt;/p&gt;
&lt;p&gt;It's taking advantage of the fact that &lt;code&gt;uv&lt;/code&gt; is &lt;em&gt;so fast&lt;/em&gt; that the overhead of getting this to work is low enough for it to be worth at least playing with the idea.&lt;/p&gt;
&lt;p&gt;The real magic is in how &lt;code&gt;uvtrick&lt;/code&gt; works. It's &lt;a href="https://github.com/koaning/uvtrick/blob/9531006e77e099eada8847d1333087517469d26a/uvtrick/__init__.py"&gt;only 127 lines of code&lt;/a&gt; with some truly devious trickery going on.&lt;/p&gt;
&lt;p&gt;That &lt;code&gt;Env.run()&lt;/code&gt; method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creates a temporary directory&lt;/li&gt;
&lt;li&gt;Pickles the &lt;code&gt;args&lt;/code&gt; and &lt;code&gt;kwargs&lt;/code&gt; and saves them to &lt;code&gt;pickled_inputs.pickle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Uses &lt;code&gt;inspect.getsource()&lt;/code&gt; to retrieve the source code of the function passed to &lt;code&gt;run()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Writes &lt;em&gt;that&lt;/em&gt; to a &lt;code&gt;pytemp.py&lt;/code&gt; file, along with a generated &lt;code&gt;if __name__ == "__main__":&lt;/code&gt; block that calls the function with the pickled inputs and saves its output to another pickle file called &lt;code&gt;tmp.pickle&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Having created the temporary Python file it executes the program using a command something like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;uv run --with rich --python 3.12 --quiet pytemp.py&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It reads the output from &lt;code&gt;tmp.pickle&lt;/code&gt; and returns it to the caller!

    &lt;p&gt;&lt;small&gt;&lt;/small&gt;Via &lt;a href="https://twitter.com/fishnets88/status/1829847133878432067"&gt;@fishnets88&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/uv"&gt;uv&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/vincent-d-warmerdam"&gt;vincent-d-warmerdam&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rich"&gt;rich&lt;/a&gt;&lt;/p&gt;



</summary><category term="python"/><category term="uv"/><category term="vincent-d-warmerdam"/><category term="rich"/></entry><entry><title>How to get Rich with Python (a terminal rendering library)</title><link href="https://simonwillison.net/2020/May/4/rich/#atom-tag" rel="alternate"/><published>2020-05-04T23:27:08+00:00</published><updated>2020-05-04T23:27:08+00:00</updated><id>https://simonwillison.net/2020/May/4/rich/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.willmcgugan.com/blog/tech/post/how-to-get-rich-with-python-a-terminal-rendering-library/"&gt;How to get Rich with Python (a terminal rendering library)&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Will McGugan introduces Rich, his new Python library for rendering content on the terminal. This is a very cool piece of software—out of the box it supports coloured text, emoji, tables, rendering Markdown, syntax highlighting code, rendering Python tracebacks, progress bars and more. “pip install rich” and then “python -m rich” to render a “test card” demo demonstrating the features of the library.


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/cli"&gt;cli&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/will-mcgugan"&gt;will-mcgugan&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/rich"&gt;rich&lt;/a&gt;&lt;/p&gt;



</summary><category term="cli"/><category term="python"/><category term="will-mcgugan"/><category term="rich"/></entry></feed>