<?xml version="1.0" encoding="utf-8"?>
<feed xml:lang="en-us" xmlns="http://www.w3.org/2005/Atom"><title>Simon Willison's Weblog: kim-christie</title><link href="http://simonwillison.net/" rel="alternate"/><link href="http://simonwillison.net/tags/kim-christie.atom" rel="self"/><id>http://simonwillison.net/</id><updated>2026-03-22T23:57:44+00:00</updated><author><name>Simon Willison</name></author><entry><title>Experimenting with Starlette 1.0 with Claude skills</title><link href="https://simonwillison.net/2026/Mar/22/starlette/#atom-tag" rel="alternate"/><published>2026-03-22T23:57:44+00:00</published><updated>2026-03-22T23:57:44+00:00</updated><id>https://simonwillison.net/2026/Mar/22/starlette/#atom-tag</id><summary type="html">
    &lt;p&gt;&lt;a href="https://marcelotryle.com/blog/2026/03/22/starlette-10-is-here/"&gt;Starlette 1.0 is out&lt;/a&gt;! This is a really big deal. I think Starlette may be the Python framework with the most usage compared to its relatively low brand recognition because Starlette is the foundation of &lt;a href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt;, which has attracted a huge amount of buzz that seems to have overshadowed Starlette itself.&lt;/p&gt;
&lt;p&gt;Kim Christie started working on Starlette in 2018 and it quickly became my favorite out of the new breed of Python ASGI frameworks. The only reason I didn't use it as the basis for my own &lt;a href="https://datasette.io/"&gt;Datasette&lt;/a&gt; project was that it didn't yet promise stability, and I was determined to provide a stable API for Datasette's own plugins... albeit I still haven't been brave enough to ship my own 1.0 release (after 26 alphas and counting)!&lt;/p&gt;
&lt;p&gt;Then in September 2025 Marcelo Trylesinski &lt;a href="https://github.com/Kludex/starlette/discussions/2997"&gt;announced that Starlette and Uvicorn were transferring to their GitHub account&lt;/a&gt;, in recognition of their many years of contributions and to make it easier for them to receive sponsorship against those projects.&lt;/p&gt;
&lt;p&gt;The 1.0 version has a few breaking changes compared to the 0.x series, described in &lt;a href="https://starlette.dev/release-notes/#100rc1-february-23-2026"&gt;the release notes for 1.0.0rc1&lt;/a&gt; that came out in February.&lt;/p&gt;
&lt;p&gt;The most notable of these is a change to how code runs on startup and shutdown. Previously that was handled by &lt;code&gt;on_startup&lt;/code&gt; and &lt;code&gt;on_shutdown&lt;/code&gt; parameters, but the new system uses a neat &lt;a href="https://starlette.dev/lifespan/"&gt;lifespan&lt;/a&gt; mechanism instead based around an &lt;a href="https://docs.python.org/3/library/contextlib.html#contextlib.asynccontextmanager"&gt;async context manager&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;span class="pl-en"&gt;@&lt;span class="pl-s1"&gt;contextlib&lt;/span&gt;.&lt;span class="pl-c1"&gt;asynccontextmanager&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;def&lt;/span&gt; &lt;span class="pl-en"&gt;lifespan&lt;/span&gt;(&lt;span class="pl-s1"&gt;app&lt;/span&gt;):
    &lt;span class="pl-k"&gt;async&lt;/span&gt; &lt;span class="pl-k"&gt;with&lt;/span&gt; &lt;span class="pl-en"&gt;some_async_resource&lt;/span&gt;():
        &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s"&gt;"Run at startup!"&lt;/span&gt;)
        &lt;span class="pl-k"&gt;yield&lt;/span&gt;
        &lt;span class="pl-en"&gt;print&lt;/span&gt;(&lt;span class="pl-s"&gt;"Run on shutdown!"&lt;/span&gt;)

&lt;span class="pl-s1"&gt;app&lt;/span&gt; &lt;span class="pl-c1"&gt;=&lt;/span&gt; &lt;span class="pl-en"&gt;Starlette&lt;/span&gt;(
    &lt;span class="pl-s1"&gt;routes&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;routes&lt;/span&gt;,
    &lt;span class="pl-s1"&gt;lifespan&lt;/span&gt;&lt;span class="pl-c1"&gt;=&lt;/span&gt;&lt;span class="pl-s1"&gt;lifespan&lt;/span&gt;
)&lt;/pre&gt;
&lt;p&gt;If you haven't tried Starlette before it feels to me like an asyncio-native cross between Flask and Django, unsurprising since creator Kim Christie is also responsible for Django REST Framework. Crucially, this means you can write most apps as a single Python file, Flask style.&lt;/p&gt;
&lt;p&gt;This makes it &lt;em&gt;really&lt;/em&gt; easy for LLMs to spit out a working Starlette app from a single prompt.&lt;/p&gt;
&lt;p&gt;There's just one problem there: if 1.0 breaks compatibility with the Starlette code that the models have been trained on, how can we have them generate code that works with 1.0?&lt;/p&gt;
&lt;p&gt;I decided to see if I could get this working &lt;a href="https://simonwillison.net/2025/Oct/16/claude-skills/"&gt;with a Skill&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id="building-a-skill-with-claude"&gt;Building a Skill with Claude&lt;/h4&gt;
&lt;p&gt;Regular Claude Chat on &lt;a href="https://claude.ai/"&gt;claude.ai&lt;/a&gt; has skills, and one of those default skills is the &lt;a href="https://github.com/anthropics/skills/blob/main/skills/skill-creator/SKILL.md"&gt;skill-creator skill&lt;/a&gt;. This means Claude knows how to build its own skills.&lt;/p&gt;
&lt;p&gt;So I started &lt;a href="https://claude.ai/share/b537c340-aea7-49d6-a14d-3134aa1bd957"&gt;a chat session&lt;/a&gt; and told it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Clone Starlette from GitHub - it just had its 1.0 release. Build a skill markdown document for this release which includes code examples of every feature.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I didn't even tell it where to find the repo, Starlette is widely enough known that I expected it could find it on its own.&lt;/p&gt;
&lt;p&gt;It ran &lt;code&gt;git clone https://github.com/encode/starlette.git&lt;/code&gt; which is actually the old repository name, but GitHub handles redirects automatically so this worked just fine.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://github.com/simonw/research/blob/main/starlette-1-skill/SKILL.md"&gt;resulting skill document&lt;/a&gt; looked very thorough to me... and then I noticed a new button at the top I hadn't seen before labelled "Copy to your skills". So I clicked it:&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/skill-button.jpg" alt="Screenshot of the Claude.ai interface showing a conversation titled &amp;quot;Starlette 1.0 skill document with code examples.&amp;quot; The left panel shows a chat where the user prompted: &amp;quot;Clone Starlette from GitHub - it just had its 1.0 release. Build a skill markdown document for this release which includes code examples of every feature.&amp;quot; Claude's responses include collapsed sections labeled &amp;quot;Strategized cloning repository and documenting comprehensive feature examples,&amp;quot; &amp;quot;Examined version details and surveyed source documentation comprehensively,&amp;quot; and &amp;quot;Synthesized Starlette 1.0 knowledge to construct comprehensive skill documentation,&amp;quot; with intermediate messages like &amp;quot;I'll clone Starlette from GitHub and build a comprehensive skill document. Let me start by reading the skill-creator guide and then cloning the repo,&amp;quot; &amp;quot;Now let me read through all the documentation files to capture every feature:&amp;quot; and &amp;quot;Now I have a thorough understanding of the entire codebase. Let me build the comprehensive skill document.&amp;quot; The right panel shows a skill preview pane with buttons &amp;quot;Copy to your skills&amp;quot; and &amp;quot;Copy&amp;quot; at the top, and a Description section reading: &amp;quot;Build async web applications and APIs with Starlette 1.0, the lightweight ASGI framework for Python. Use this skill whenever a user wants to create an async Python web app, REST API, WebSocket server, or ASGI application using Starlette. Triggers include mentions of 'Starlette', 'ASGI', async Python web frameworks, or requests to build lightweight async APIs, WebSocket services, streaming responses, or middleware pipelines. Also use when the user is working with FastAPI internals (which is built on Starlette), needs ASGI middleware patterns, or wants a minimal async web server&amp;quot; (text truncated)." style="max-width: 100%;" /&gt;&lt;/p&gt;
&lt;p&gt;And now my regular Claude chat has access to that skill!&lt;/p&gt;
&lt;h4 id="a-task-management-demo-app"&gt;A task management demo app&lt;/h4&gt;
&lt;p&gt;I started &lt;a href="https://claude.ai/share/b5285fbc-5849-4939-b473-dcb66f73503b"&gt;a new conversation&lt;/a&gt; and prompted:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Build a task management app with Starlette, it should have projects and tasks and comments and labels&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And Claude did exactly that, producing a simple GitHub Issues clone using Starlette 1.0, a SQLite database (via &lt;a href="https://github.com/omnilib/aiosqlite"&gt;aiosqlite&lt;/a&gt;) and a Jinja2 template.&lt;/p&gt;
&lt;p&gt;Claude even tested the app manually like this:&lt;/p&gt;
&lt;div class="highlight highlight-source-shell"&gt;&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; /home/claude/taskflow &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; timeout 5 python -c &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;import asyncio&lt;/span&gt;
&lt;span class="pl-s"&gt;from database import init_db&lt;/span&gt;
&lt;span class="pl-s"&gt;asyncio.run(init_db())&lt;/span&gt;
&lt;span class="pl-s"&gt;print('DB initialized successfully')&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="pl-k"&gt;2&amp;gt;&amp;amp;1&lt;/span&gt;

pip install httpx --break-system-packages -q \
  &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="pl-c1"&gt;cd&lt;/span&gt; /home/claude/taskflow &lt;span class="pl-k"&gt;&amp;amp;&amp;amp;&lt;/span&gt; \
  python -c &lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;from starlette.testclient import TestClient&lt;/span&gt;
&lt;span class="pl-s"&gt;from main import app&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;client = TestClient(app)&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;r = client.get('/api/stats')&lt;/span&gt;
&lt;span class="pl-s"&gt;print('Stats:', r.json())&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;r = client.get('/api/projects')&lt;/span&gt;
&lt;span class="pl-s"&gt;print('Projects:', len(r.json()), 'found')&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;r = client.get('/api/tasks')&lt;/span&gt;
&lt;span class="pl-s"&gt;print('Tasks:', len(r.json()), 'found')&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;r = client.get('/api/labels')&lt;/span&gt;
&lt;span class="pl-s"&gt;print('Labels:', len(r.json()), 'found')&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;r = client.get('/api/tasks/1')&lt;/span&gt;
&lt;span class="pl-s"&gt;t = r.json()&lt;/span&gt;
&lt;span class="pl-s"&gt;print(f'Task 1: &lt;span class="pl-cce"&gt;\"&lt;/span&gt;{t[&lt;span class="pl-cce"&gt;\"&lt;/span&gt;title&lt;span class="pl-cce"&gt;\"&lt;/span&gt;]}&lt;span class="pl-cce"&gt;\"&lt;/span&gt; - {len(t[&lt;span class="pl-cce"&gt;\"&lt;/span&gt;comments&lt;span class="pl-cce"&gt;\"&lt;/span&gt;])} comments, {len(t[&lt;span class="pl-cce"&gt;\"&lt;/span&gt;labels&lt;span class="pl-cce"&gt;\"&lt;/span&gt;])} labels')&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;r = client.post('/api/tasks', json={'title':'Test task','project_id':1,'priority':'high','label_ids':[1,2]})&lt;/span&gt;
&lt;span class="pl-s"&gt;print('Created task:', r.status_code, r.json()['title'])&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;r = client.post('/api/comments', json={'task_id':1,'content':'Test comment'})&lt;/span&gt;
&lt;span class="pl-s"&gt;print('Created comment:', r.status_code)&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;r = client.get('/')&lt;/span&gt;
&lt;span class="pl-s"&gt;print('Homepage:', r.status_code, '- length:', len(r.text))&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;/span&gt;
&lt;span class="pl-s"&gt;print('\nAll tests passed!')&lt;/span&gt;
&lt;span class="pl-s"&gt;&lt;span class="pl-pds"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For all of the buzz about Claude Code, it's easy to overlook that Claude itself counts as a coding agent now, fully able to both write and then test the code that it is writing.&lt;/p&gt;
&lt;p&gt;Here's what the resulting app looked like. The code is &lt;a href="https://github.com/simonw/research/blob/main/starlette-1-skill/taskflow"&gt;here in my research repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://static.simonwillison.net/static/2026/taskflow.jpg" alt="Screenshot of a dark-themed Kanban board app called &amp;quot;TaskFlow&amp;quot; showing the &amp;quot;Website Redesign&amp;quot; project. The left sidebar has sections &amp;quot;OVERVIEW&amp;quot; with &amp;quot;Dashboard&amp;quot;, &amp;quot;All Tasks&amp;quot;, and &amp;quot;Labels&amp;quot;, and &amp;quot;PROJECTS&amp;quot; with &amp;quot;Website Redesign&amp;quot; (1) and &amp;quot;API Platform&amp;quot; (0). The main area has three columns: &amp;quot;TO DO&amp;quot; (0) showing &amp;quot;No tasks&amp;quot;, &amp;quot;IN PROGRESS&amp;quot; (1) with a card titled &amp;quot;Blog about Starlette 1.0&amp;quot; tagged &amp;quot;MEDIUM&amp;quot; and &amp;quot;Documentation&amp;quot;, and &amp;quot;DONE&amp;quot; (0) showing &amp;quot;No tasks&amp;quot;. Top-right buttons read &amp;quot;+ New Task&amp;quot; and &amp;quot;Delete&amp;quot;." style="max-width: 100%;" /&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/ai"&gt;ai&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/asgi"&gt;asgi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/kim-christie"&gt;kim-christie&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/coding-agents"&gt;coding-agents&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/skills"&gt;skills&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/agentic-engineering"&gt;agentic-engineering&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/starlette"&gt;starlette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="open-source"/><category term="python"/><category term="ai"/><category term="asgi"/><category term="kim-christie"/><category term="generative-ai"/><category term="llms"/><category term="ai-assisted-programming"/><category term="claude"/><category term="coding-agents"/><category term="skills"/><category term="agentic-engineering"/><category term="starlette"/></entry><entry><title>Re-assessing the automatic charset decoding policy in HTTPX</title><link href="https://simonwillison.net/2021/Aug/13/charset/#atom-tag" rel="alternate"/><published>2021-08-13T22:07:54+00:00</published><updated>2021-08-13T22:07:54+00:00</updated><id>https://simonwillison.net/2021/Aug/13/charset/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.encode.io/reports/august-2021#weeknotes-friday-13th-august-2021"&gt;Re-assessing the automatic charset decoding policy in HTTPX&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
Tom Christie ran an analysis of the top 1,000 most accessed websites (according to an older extract from Google’s Ad Planner service) and found that a full 5% of them both omitted a charset parameter and failed to decode as UTF-8. As a result, HTTPX will be depending on the charset-normalizer Python library to handle those cases.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/unicode"&gt;unicode&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/kim-christie"&gt;kim-christie&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/httpx"&gt;httpx&lt;/a&gt;&lt;/p&gt;



</summary><category term="unicode"/><category term="kim-christie"/><category term="httpx"/></entry><entry><title>Async Support - HTTPX</title><link href="https://simonwillison.net/2020/Jan/10/httpx/#atom-tag" rel="alternate"/><published>2020-01-10T04:49:59+00:00</published><updated>2020-01-10T04:49:59+00:00</updated><id>https://simonwillison.net/2020/Jan/10/httpx/#atom-tag</id><summary type="html">
    
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.python-httpx.org/async/"&gt;Async Support - HTTPX&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
HTTPX is the new async-friendly HTTP library for Python spearheaded by Tom Christie. It works in both async and non-async mode with an API very similar to requests. The async support is particularly interesting - it's a really clean API, and now that Jupyter supports top-level await you can run &lt;code&gt;(await httpx.AsyncClient().get(url)).text&lt;/code&gt; directly in a cell and get back the response. Most excitingly the library lets you pass an ASGI app directly to the client and then perform requests against it - ideal for unit tests.

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


    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/async"&gt;async&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/http"&gt;http&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/asgi"&gt;asgi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/kim-christie"&gt;kim-christie&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/httpx"&gt;httpx&lt;/a&gt;&lt;/p&gt;



</summary><category term="async"/><category term="http"/><category term="python"/><category term="asgi"/><category term="kim-christie"/><category term="httpx"/></entry><entry><title>Porting Datasette to ASGI, and Turtles all the way down</title><link href="https://simonwillison.net/2019/Jun/23/datasette-asgi/#atom-tag" rel="alternate"/><published>2019-06-23T21:39:00+00:00</published><updated>2019-06-23T21:39:00+00:00</updated><id>https://simonwillison.net/2019/Jun/23/datasette-asgi/#atom-tag</id><summary type="html">
    &lt;p&gt;This evening I finally closed a &lt;a href="https://simonwillison.net/tags/datasette/"&gt;Datasette&lt;/a&gt; issue that I opened more than 13 months ago: &lt;a href="https://github.com/simonw/datasette/issues/272"&gt;#272: Port Datasette to ASGI&lt;/a&gt;. A few notes on why this is such an important step for the project.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://asgi.readthedocs.io/"&gt;ASGI&lt;/a&gt; is the Asynchronous Server Gateway Interface standard. It’s been evolving steadily over the past few years under the guidance of Andrew Godwin. It’s intended as an asynchronous replacement for the venerable &lt;a href="https://wsgi.readthedocs.io/"&gt;WSGI&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;&lt;a id="Turtles_all_the_way_down_6"&gt;&lt;/a&gt;Turtles all the way down&lt;/h3&gt;
&lt;p&gt;Ten years ago at EuroDjangoCon 2009 in Prague I gave a talk entitled &lt;a href="https://www.slideshare.net/simon/django-heresies"&gt;Django Heresies&lt;/a&gt;. After discussing some of the design decisions in Django that I didn’t think had aged well, I spent the last part of the talk talking about &lt;em&gt;Turtles all the way down&lt;/em&gt;. I &lt;a href="https://simonwillison.net/2009/May/19/djng/?#turtles-all-the-way-down"&gt;wrote that idea up here&lt;/a&gt; on my blog (see also &lt;a href="https://www.slideshare.net/simon/django-heresies/65-The_Django_Contract_A_view"&gt;these slides&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The key idea was that Django would be more interesting if the core Django contract - a function that takes a request and returns a response - was extended to more places in the framework. The top level site, the reusable applications, middleware and URL routing could all share that same contract. Everything could be composed from the same raw building blocks.&lt;/p&gt;
&lt;p&gt;I’m excited about ASGI because it absolutely fits the &lt;em&gt;turtles all the way down&lt;/em&gt; model.&lt;/p&gt;
&lt;p&gt;The ASGI contract is an asynchronous function that takes three arguments:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;async def application(scope, receive, send):
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;scope&lt;/code&gt; is a serializable dictionary providing the context for the current connection. &lt;code&gt;receive&lt;/code&gt; is an awaitable which can be used to recieve incoming messages. &lt;code&gt;send&lt;/code&gt; is an awaitable that can be used to send replies.&lt;/p&gt;
&lt;p&gt;It’s a pretty low-level set of primitives (and less obvious than a simple request/response) - and that’s because ASGI is about more than just the standard HTTP request/response cycle. This contract works for HTTP, WebSockets and potentially any other protocol that needs to asynchronously send and receive data.&lt;/p&gt;
&lt;p&gt;It’s an extremely elegant piece of protocol design, informed by Andrew’s experience with Django Channels, SOA protocols (we are co-workers at Eventbrite where we’ve both been heavily involved in Eventbrite’s &lt;a href="https://github.com/eventbrite/pysoa"&gt;SOA mechanism&lt;/a&gt;) and Andrew’s extensive conversations with other maintainers in the Python web community.&lt;/p&gt;
&lt;p&gt;The ASGI protocol really is turtles all the way down - it’s a simple, well defined contract which can be composed together to implement all kinds of interesting web architectural patterns.&lt;/p&gt;
&lt;p&gt;My &lt;a href="https://github.com/simonw/asgi-cors/"&gt;asgi-cors library&lt;/a&gt; was my first attempt at building an ASGI turtle. &lt;a href="https://github.com/simonw/asgi-cors/blob/master/asgi_cors.py"&gt;The implementation&lt;/a&gt; is a simple Python decorator which, when applied to another ASGI callable, adds HTTP CORS headers based on the parameters you pass to the decorator. The library has zero installation dependencies (it has test dependencies on pytest and friends) and can be used on any HTTP ASGI project.&lt;/p&gt;
&lt;p&gt;Building &lt;code&gt;asgi-cors&lt;/code&gt; completely sold me on ASGI as the turtle pattern I had been desiring for over a decade!&lt;/p&gt;
&lt;h3&gt;&lt;a id="Datasette_plugins_and_ASGI_31"&gt;&lt;/a&gt;Datasette plugins and ASGI&lt;/h3&gt;
&lt;p&gt;Which brings me to Datasette.&lt;/p&gt;
&lt;p&gt;One of the most promising components of Datasette is its plugin mechanism. Based on &lt;a href="https://pluggy.readthedocs.io/en/latest/"&gt;pluggy&lt;/a&gt; (extracted from pytest), &lt;a href="https://datasette.readthedocs.io/en/stable/plugins.html"&gt;Datasette Plugins&lt;/a&gt; allow new features to be added to Datasette without needing to change the underlying code. This means new features can be built, packaged and shipped entirely independently of the core project. A list of currently available plugins &lt;a href="https://datasette.readthedocs.io/en/latest/ecosystem.html#datasette-plugins"&gt;can be found here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;WordPress is very solid blogging engine. Add in the plugin ecosystem around it and it can be used to build literally any CMS you can possibly imagine.&lt;/p&gt;
&lt;p&gt;My dream for Datasette is to apply the same model: I want a strong core for publishing and exploring data that’s enhanced by plugins to solve a huge array of data analysis, visualization and API-backed problems.&lt;/p&gt;
&lt;p&gt;Datasette has &lt;a href="https://datasette.readthedocs.io/en/latest/plugins.html#plugin-hooks"&gt;a range of plugin hooks already&lt;/a&gt;, but I’ve so far held back on implementing the most useful class of hooks: hooks that allow developers to add entirely new URL routes exposing completely custom functionality.&lt;/p&gt;
&lt;p&gt;The reason I held back is that I wanted to be confident that the contract I was offering was something I would continue to support moving forward. A plugin system isn’t much good if the core implementation keeps on changing in backwards-incompatible ways.&lt;/p&gt;
&lt;p&gt;ASGI is the exact contract I’ve been waiting for. It’s not quite ready yet, but you can follow &lt;a href="https://github.com/simonw/datasette/issues/520"&gt;#520: prepare_asgi plugin hook&lt;/a&gt; (thoughts and suggestions welcome!) to be the first to hear about this hook when it lands. I’m planning to use it to make my asgi-cors library available as a plugin, after which I’m excited to start exploring the idea of bringing authentication plugins to Datasette (and to the wider ASGI world in general).&lt;/p&gt;
&lt;p&gt;I’m hoping that many Datasette ASGI plugins will exist in a form that allows them to be used by other ASGI applications as well.&lt;/p&gt;
&lt;p&gt;I also plan to use ASGI to make components of Datasette itself available to other ASGI applications. If you just want a single instance of Datasette’s &lt;a href="https://datasette.readthedocs.io/en/stable/pages.html#table"&gt;table view&lt;/a&gt; to be embedded somewhere in your URL configuration you should be able to do that by routing traffic directly to the ASGI-compatible view class.&lt;/p&gt;
&lt;p&gt;I’m really excited about exploring the intersection of ASGI turtles-all-the-way-down and pluggy’s powerful mechanism for gluing components together. Both WSGI and Django’s reusable apps have attempted to create a reusable ecosystem in the past, to limited levels of success. Let’s see if ASGI can finally make the turtle dream come true.&lt;/p&gt;

&lt;h3&gt;&lt;a id="Further_reading_53"&gt;&lt;/a&gt;Further reading&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://www.encode.io/articles/hello-asgi/"&gt;Hello ASGI&lt;/a&gt; by Tom Christie is the best introduction to ASGI I’ve seen. Tom is the author of the &lt;a href="https://www.uvicorn.org/"&gt;Uvicorn&lt;/a&gt; ASGI server (used by Datasette as-of this evening) and &lt;a href="https://www.starlette.io/"&gt;Starlette&lt;/a&gt;, a delightfully well-designd ASGI web framework. I’ve learned an enormous amount about ASGI by reading Tom’s code. Tom also gave &lt;a href="https://www.youtube.com/watch?v=u8GSFEg5lnU"&gt;a talk about ASGI&lt;/a&gt; at DjangoCon Europe a few months ago.&lt;/p&gt;
&lt;p&gt;If you haven’t read &lt;a href="https://www.aeracode.org/2018/06/04/django-async-roadmap/"&gt;A Django Async Roadmap&lt;/a&gt; by Andrew Godwin last year you should absolutely catch up. More than just talking about ASGI, Andrew sketches out a detailed and actionable plan for bringing asyncio to Django core. Andrew landeded &lt;a href="https://github.com/django/django/pull/11209"&gt;the first Django core ASGI code&lt;/a&gt; based on the plan just a few days ago.&lt;/p&gt;
&lt;p&gt;If you're interested in the details of Datasette's ASGI implementation, I posted &lt;a href="https://github.com/simonw/datasette/issues/272"&gt;detailed commentary on issue #272&lt;/a&gt; over the past thirteen months as I researched and finalized my approach. I added further commentary to &lt;a href="https://github.com/simonw/datasette/pull/518"&gt;the associated pull request&lt;/a&gt;, which gathers together the 34 commits it took to ship the feature (squashed into a single commit to master).&lt;/p&gt;
    
        &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/andrew-godwin"&gt;andrew-godwin&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/projects"&gt;projects&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/datasette"&gt;datasette&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/asgi"&gt;asgi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/kim-christie"&gt;kim-christie&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/pytest"&gt;pytest&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/starlette"&gt;starlette&lt;/a&gt;&lt;/p&gt;
    

</summary><category term="andrew-godwin"/><category term="projects"/><category term="datasette"/><category term="asgi"/><category term="kim-christie"/><category term="pytest"/><category term="starlette"/></entry><entry><title>Quoting Tom Christie</title><link href="https://simonwillison.net/2018/Oct/8/tom-christie/#atom-tag" rel="alternate"/><published>2018-10-08T14:43:16+00:00</published><updated>2018-10-08T14:43:16+00:00</updated><id>https://simonwillison.net/2018/Oct/8/tom-christie/#atom-tag</id><summary type="html">
    &lt;blockquote cite="https://www.encode.io/articles/hello-asgi/"&gt;&lt;p&gt;The ASGI specification provides an opportunity for Python to hit a productivity/performance sweet-spot for a wide range of use-cases, from writing high-volume proxy servers through to bringing large-scale web applications to market at speed.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p class="cite"&gt;&amp;mdash; &lt;a href="https://www.encode.io/articles/hello-asgi/"&gt;Tom Christie&lt;/a&gt;&lt;/p&gt;

    &lt;p&gt;Tags: &lt;a href="https://simonwillison.net/tags/async"&gt;async&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/python"&gt;python&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/asgi"&gt;asgi&lt;/a&gt;, &lt;a href="https://simonwillison.net/tags/kim-christie"&gt;kim-christie&lt;/a&gt;&lt;/p&gt;



</summary><category term="async"/><category term="python"/><category term="asgi"/><category term="kim-christie"/></entry></feed>