davepeck.orgDave Peck's Master Feedhttps://davepeck.org/en-usIntroducing tdom: HTML templating with t‑stringshttps://davepeck.org/2025/09/22/introducing-tdom-html-templating-with-python-t-strings/https://davepeck.org/2025/09/22/introducing-tdom-html-templating-with-python-t-strings/<p>Python 3.14's <a href="/2025/04/11/pythons-new-t-strings/">new t‑strings</a> add flexibility and power to the language's arsenal of string processing tools. They make it easy to distinguish between static and dynamic content—essential for <a href="https://owasp.org/www-community/attacks/xss/">safe</a> web templating.</p> <p>That's why <a href="https://t-strings.help/">we're</a> excited to introduce <a href="https://github.com/t-strings/tdom"><code>tdom</code></a>, a brand-new toolkit that leverages t‑strings and brings Python web templates into the modern era. It's easy to use: write HTML in a t‑string and pass it to <code>tdom.html()</code>:</p> <pre><code>&gt;&gt;&gt; from tdom import html &gt;&gt;&gt; &gt;&gt;&gt; name = "Alice" &gt;&gt;&gt; node = html(t'&lt;div&gt;Hi, {name}!&lt;/div&gt;') &gt;&gt;&gt; str(node) '&lt;div&gt;Hi, Alice!&lt;/div&gt;' </code></pre> <p>Under the hood, <code>html()</code> parses your HTML and returns a tree of nodes. These can be manipulated, inspected, or rendered with <code>str()</code>.</p> <p>As you'd expect, <code>html()</code> safely escapes special characters found in substitutions:</p> <pre><code>&gt;&gt;&gt; from tdom import html &gt;&gt;&gt; &gt;&gt;&gt; name = '&lt;script&gt;alert("pwned")&lt;/script&gt;' &gt;&gt;&gt; node = html(t'&lt;div&gt;Hi, {name}!&lt;/div&gt;') &gt;&gt;&gt; str(node) '&lt;div&gt;Hi, &amp;lt;script&amp;gt;alert("pwned")&amp;lt;/script&amp;gt;!&lt;/div&gt;' </code></pre> <p>In addition to keeping things safe, <code>tdom</code> provides a wide variety of convenience features that make it easier to build complex HTML. If you've ever used <a href="https://lit.dev/docs/v1/lit-html/introduction/"><code>lit-html</code></a>, <a href="https://github.com/developit/htm"><code>htm</code></a>, <a href="https://react.dev/learn#writing-markup-with-jsx">JSX</a>, or similar JavaScript tools, you'll feel right at home.</p> <p>To give a taste of what's possible, here are a few highlights:</p> <ul> <li> <p><strong>Dynamic attributes</strong>: Provide individual attributes <em>or</em> many at once with a dict. Boolean attributes are handled the way you'd expect.</p> <pre><code>&gt;&gt;&gt; from tdom import html &gt;&gt;&gt; &gt;&gt;&gt; attrs = {"id": "yum", "data-act": "submit"} &gt;&gt;&gt; disabled = True &gt;&gt;&gt; t = t'&lt;button {attrs} disabled={disabled}&gt;Yum!&lt;/button&gt;' &gt;&gt;&gt; node = html(t) &gt;&gt;&gt; str(node) '&lt;button id="yum" data-act="submit" disabled&gt;Yum!&lt;/button&gt;' </code></pre> </li> <li> <p><strong>Special handling for special attributes</strong>: Deftly deal with <code>data-*</code>, <code>aria-*</code>, <code>style</code> and <code>class</code> attributes. Inspired by <a href="https://github.com/JedWatson/classnames"><code>classnames</code></a>, CSS classes can be provided as strings, lists, or dicts.</p> <pre><code>&gt;&gt;&gt; from tdom import html &gt;&gt;&gt; &gt;&gt;&gt; classes = ["btn", {"primary": False}, {"active": True}] &gt;&gt;&gt; node = html(t'&lt;button class={classes}&gt;Click me&lt;/button&gt;') &gt;&gt;&gt; str(node) '&lt;button class="btn active"&gt;Click me&lt;/button&gt;' </code></pre> </li> <li> <p><strong>Intuitive content</strong>: Pass strings, nodes, iterables, and much more as child content; <code>tdom</code> will flatten and render it all correctly.</p> <pre><code>&gt;&gt;&gt; from tdom import html &gt;&gt;&gt; &gt;&gt;&gt; cheeses = ["Cheddar", "Gouda", "Brie"] &gt;&gt;&gt; items = (t'&lt;li&gt;{cheese}&lt;/li&gt;' for cheese in cheeses) &gt;&gt;&gt; node = html(t'&lt;ul&gt;{items}&lt;/ul&gt;') &gt;&gt;&gt; str(node) '&lt;ul&gt;&lt;li&gt;Cheddar&lt;/li&gt;&lt;li&gt;Gouda&lt;/li&gt;&lt;li&gt;Brie&lt;/li&gt;&lt;/ul&gt;' </code></pre> </li> <li> <p><strong>Components</strong>: Define reusable functions and classes and invoke them directly in your templates using a JSX-like syntax.</p> <pre><code>&gt;&gt;&gt; from tdom import html, Node &gt;&gt;&gt; &gt;&gt;&gt; def Card( ... children: Node, ... title: str, ... subtitle: str | None = None, ... **attrs, ... ) -&gt; Node: ... return html(t''' ... &lt;div class="card" {attrs}&gt; ... &lt;h2&gt;{title}&lt;/h2&gt; ... {subtitle and t'&lt;h3&gt;{subtitle}&lt;/h3&gt;'} ... &lt;div class="content"&gt; ... {children} ... &lt;/div&gt; ... &lt;/div&gt;''') &gt;&gt;&gt; &gt;&gt;&gt; result = html(t''' ... &lt;{Card} title="Cheese Emporium" id="cheeses"&gt; ... &lt;p&gt;Sorry, we're fresh out!&lt;/p&gt; ... &lt;/{Card}&gt;''') &gt;&gt;&gt; &gt;&gt;&gt; str(result) &lt;div class="card" id="cheeses"&gt; &lt;h2&gt;Cheese Emporium&lt;/h2&gt; &lt;div class="content"&gt; &lt;p&gt;Sorry, we're fresh out!&lt;/p&gt; &lt;/div&gt; &lt;/div&gt; </code></pre> </li> <li> <p><strong>Much more</strong>. If it feels intuitive, it's probably supported... or should be! See the <a href="https://github.com/t-strings/tdom">README</a> for a rundown of current features.</p> </li> </ul> <p>Please give <code>tdom</code> a try and send us your feedback! If you have <a href="https://docs.astral.sh/uv/">Astral's <code>uv</code></a> installed, you can use <code>tdom</code> today with:</p> <pre><code>uv run --with tdom --python 3.14 python </code></pre> <p>The <a href="https://github.com/t-strings/tdom"><code>tdom</code> project</a> is still very young; it's labeled <a href="https://pypi.org/project/tdom/">pre-alpha on PyPI</a> and there's still plenty of <a href="https://github.com/t-strings/tdom/issues">work for us to do</a>. If you run into bugs, please <a href="https://github.com/t-strings/tdom/issues">open an issue</a>; if there's an intuitive feature you'd like to see that we don't yet support, please <a href="https://github.com/t-strings/tdom/discussions">start a discussion</a> and we'll find a way to make it happen.</p> <p><code>tdom</code> is part of a broader ecosystem of tools designed to promote modern HTML templating in Python. Early <a href="https://github.com/t-strings/tdom/discussions">efforts are underway</a> to add syntax highlighting, formatting, linting, and autocompletion support to popular editors like VS Code and PyCharm. Stay tuned for updates!</p> Mon, 22 Sep 2025 20:45:00 GMTPhoto: Pika Posehttps://davepeck.org/2025/09/10/pika-pose/https://davepeck.org/2025/09/10/pika-pose/<a href="https://davepeck.org/2025/09/10/pika-pose.jpg"> <img src="https://davepeck.org/2025/09/10/pika-pose.jpg" /> </a><p>A camera-friendly pika perched on the rocks along the Chain Lakes Loop near Mount Baker.</p>Thu, 11 Sep 2025 00:21:00 GMThttps://davepeck.org/2025/08/28/python-the-documentary-world-premiere/https://davepeck.org/2025/08/28/python-the-documentary-world-premiere/<p>The premiere of <a href="https://www.youtube.com/watch?v=GfH4QL4VqJ0">Python: The Documentary</a> is just a couple hours away. It streams for free on YouTube at 10AM Pacific.</p> <p>Over the past year, I've had the pleasure of getting to know and <a href="https://t-strings.help">collaborate with</a> a few of the Pythonistas involved. I'm excited to learn more about the history and growth of Python's welcoming and impactful open source community.</p> Thu, 28 Aug 2025 15:21:00 GMThttps://davepeck.org/2025/08/27/building-profitable-startups-with-ai/https://davepeck.org/2025/08/27/building-profitable-startups-with-ai/<p><em>"I built an entire startup with AI and now it's profitable."</em></p> <p>Posts like this seem increasingly common. Curiously, they rarely include details: a link to the startup, a clear description of the customer and problem, or a breakdown of how AI was actually used.</p> <p>Please allow me to admit my gentle skepticism.</p> <p>I use LLMs to write code. Like anyone who has done so, I've learned a lot about when they're useful and when they're... hopeless. Claims to the contrary aside, they can't do it all. They can't even do it most.</p> <p>I've also helped build (and tragicomically failed to build) a few businesses. Like anyone who has done so, I know tech is rarely the long pole in the tent. Sales, marketing, building a team, crafting a culture, instilling values, listening to customers and responding to their needs, making forward progress in a sea of uncertainty, getting anyone to care at all? <em>Hard</em>. And, last I checked, not things AI can singlehandedly solve.</p> <p>All this said: it <em>is</em> an exciting time to be a solopreneur. Used judiciously, I believe that AI <em>can</em> help builders move faster. I look forward to a future full of delightful, <em>nuanced</em> stories about how small teams leveraged AI to craft something people love. Just be sure to include a link.</p> Wed, 27 Aug 2025 15:33:00 GMTUnsheltered Seattlehttps://davepeck.org/2025/08/22/unsheltered-seattle/https://davepeck.org/2025/08/22/unsheltered-seattle/<p>I built <a href="https://unsheltered.davepeck.org/?markerSize=relative&amp;kind=encampment&amp;kind=vehicle">a little website</a> to help wrap my head around the state of unsheltered homelessness in Seattle.</p> <p>The site uses <a href="https://data.seattle.gov/City-Administration/Customer-Service-Requests/5ngg-rpne/about_data">Seattle's public customer service request dataset</a>, which aggregates data from the <a href="https://www.seattle.gov/customer-service-bureau/find-it-fix-it-mobile-app">Find It, Fix It App</a> and <a href="https://seattle-cwiprod.motorolasolutions.com/cwi/select">city web portal</a> to show recent (and, if you like, historical) reports of encampments, abandoned vehicles, and related issues:</p> <p><a href="https://unsheltered.davepeck.org/?markerSize=relative&amp;kind=encampment&amp;kind=vehicle" target="_blank"><img src="/2025/08/22/unsheltered-seattle-visualization-screenshot.png" /></a></p> <p>Through the <a href="https://github.com/davepeck/unsheltered/actions">magic of GitHub actions</a>, the website's data is updated daily. I've learned that the location of encampments can change rapidly, sometimes as a natural consequence of people moving around, and sometimes due to aggressive sweeps by the city. The site shows timelines for major encampments so you can get a sense of how they've evolved.</p> <p>The visualization also introduces the idea of "<a href="https://unsheltered.davepeck.org/safe-zones/">safe zones</a>": areas in the city that fall within roughly a block of public parks, schools, libraries, and child care centers. These closely match the zones that <a href="https://www.portland.gov/mayor/keith-wilson/news/2025/1/27/mayor-wilson-presents-blueprint-end-unsheltered-homelessness">Portland has established</a> as part of Mayor Wilson's homelessness strategy. (Seattle has not established such zones and I'm <em>not</em> an advocate, but I think it's worth understanding how they work.) For instance, you can use <a href="https://unsheltered.davepeck.org/?markerSize=relative&amp;kind=encampment&amp;kind=vehicle">Unsheltered Seattle</a> to filter down to <a href="https://unsheltered.davepeck.org/?markerSize=relative&amp;safeZones=school&amp;threshold=5&amp;kind=vehicle">highly reported abandoned vehicles parked near schools</a>.</p> <p>It's worth noting that report counts, while directionally useful, don't precisely indicate the number of tents or unsheltered individuals. Seattleites have to be motivated to submit reports. Different neighborhoods have different levels of engagement with the Find It, Fix It app, and some encampments are more visible or in more pedestrian-heavy areas than others. Just <em>one</em> tent in a prominent location along the <a href="https://www.seattle.gov/parks/allparks/burke-gilman-trail">Burke-Gilman trail</a> is likely to gather more reports than <em>twenty</em> tents in a secluded slope at the back of the <a href="https://www.seattle.gov/parks/allparks/sw-queen-anne-greenbelt">Queen Anne Greenbelt</a>. The data also shows <a href="https://unsheltered.davepeck.org/overall/">clear seasonal</a> and short-term weather related patterns.</p> <p>The site is open source, so if you're curious about how it works, you can check out the <a href="https://github.com/davepeck/unsheltered">GitHub repository</a>. It's a complete mess at the moment since I extracted it from a larger previous project. Hopefully I'll have time to clean it up a bit in the future.</p> Fri, 22 Aug 2025 18:34:00 GMThttps://davepeck.org/2025/07/13/psychotic-bugs/https://davepeck.org/2025/07/13/psychotic-bugs/<p>There are bugs and then there are <em>bugs</em>.</p> <p>The <a href="https://oxide-and-friends.transistor.fm/episodes/adventures-in-data-corruption">latest <em>Oxide and Friends</em> episode</a> tells the story of an epic data corruption bug that nearly derailed <a href="https://oxide.computer">Oxide</a>'s launch.</p> <p>Says <a href="https://bsky.app/profile/bcantrill.bsky.social">CTO Bryan Cantrill</a>:</p> <blockquote> <p>An adage of mine is that bugs can be psychotic or nonreproducible, but not both.</p> <p>And when I say psychotic I don't mean just, like, difficult. I mean [...] ripping at the fabric of reality.</p> <p>The fabric of reality that is created by the computer, by the operating system, it creates these abstractions that we view as kind of bedrock abstractions. And when those start to break, that's a psychotic bug. You know, when you have a thread that is executing on two CPUs at the same time? It's like: it can't be.</p> </blockquote> <p>Such a <a href="https://oxide-and-friends.transistor.fm/episodes/adventures-in-data-corruption">good listen</a>.</p> Sun, 13 Jul 2025 21:26:00 GMThttps://davepeck.org/2025/07/07/cat-restaurant-game-vibe-coded-with-vscode-and-github-copilot/https://davepeck.org/2025/07/07/cat-restaurant-game-vibe-coded-with-vscode-and-github-copilot/<p>My daughter and I vibe coded a little <a href="https://restaurant.cats-and-dogs.club/">Cat Restaurant Game</a> using <a href="https://code.visualstudio.com/">VSCode</a>, <a href="https://github.com/features/copilot">GitHub Copilot</a> in its new-ish <a href="https://code.visualstudio.com/docs/copilot/chat/chat-agent-mode">agent mode</a>, and <a href="https://www.anthropic.com/claude/sonnet">Claude Sonnet 4</a>. The graphics were generated by <a href="https://chat.openai.com/">ChatGPT</a>; my daughter made the sounds and music using <a href="https://www.apple.com/mac/garageband/">GarageBand</a>.</p> <p>You play a little girl that runs a sushi restaurant and has to <a href="https://restaurant.cats-and-dogs.club/">feed the hungry cat customers</a> before they get impatient:</p> <p><img src="/2025/07/07/cat-restaurant-vibe-coded-game-screenshot.png" /></p> <p>The whole project took about three hours over two sessions. We never touched the code. Instead, the exercise was to take a vague game idea, make it concrete, break it into sub-parts that formed a coherent whole, and then describe each part in sufficient detail to get Copilot to generate the outcome we wanted. In other words, a great learning experience!</p> <p><em>Because</em> we never touched the code, it's predictably wonky. For instance, it uses <a href="https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame"><code>requestAnimationFrame()</code></a> but fails to make use of the time delta in callbacks; I assume the game will run at different speeds on different machines. Another example: the <a href="https://github.com/davepeck/cats-and-dogs-restaurant/blob/8c08543d6aee25e73ae5d474e0889387379613e0/src/main.ts#L30"><code>GameState</code> interface</a> is a mishmash, confusing game state with input state among other things. But hey, <a href="https://restaurant.cats-and-dogs.club/">the thing works</a>!</p> Tue, 08 Jul 2025 02:53:00 GMThttps://davepeck.org/2025/06/11/mullet-schema/https://davepeck.org/2025/06/11/mullet-schema/<p>One of my favorite database design patterns is what I call the "mullet schema": business up front, party in the back, where <em>every</em> table in the database ends with a JSON column.</p> <p>I even mentioned it on <a href="https://talkpython.fm/episodes/show/505/t-strings-in-python">a recent episode</a> of the <a href="https://talkpython.fm/">Talk Python podcast</a>:</p> <p>This morning, I read <a href="https://www.linkedin.com/in/crawshaw/">David Crawshaw</a>'s excellent post, <em><a href="https://crawshaw.io/blog/programming-with-agents">How I Program With Agents</a></em>. In a sidebar, he completely blew my mullet-addled mind:</p> <blockquote> <p>I learned an odd way of using SQL at Tailscale (from Brad and Maisem): make every table a JSON object. In particular, have only one “real” column and the rest generated from the JSON. So the typical table looks like:</p> </blockquote> <pre><code>CREATE TABLE IF NOT EXISTS Cookie ( Cookie TEXT NOT NULL AS (Data-&gt;&gt;'cookie') STORED UNIQUE, -- PK UserID INTEGER NOT NULL AS (Data-&gt;&gt;'user_id') STORED REFERENCES User (UserID), Created INTEGER NOT NULL AS (unixepoch(Data-&gt;&gt;'created')) STORED, LastUsed INTEGER AS (unixepoch(Data-&gt;&gt;'last_used')) CHECK (LastUsed&gt;0), Data JSONB NOT NULL ); </code></pre> <p><em>Now</em> it's a party!</p> <p>David mentions some of the tradeoffs and hints at a potential future blog post about the approach. In the meantime, I may need to rethink my mullet nomenclature!</p> Wed, 11 Jun 2025 14:58:00 GMThttps://davepeck.org/2025/05/27/a-whirlwind-tour-of-t-strings/https://davepeck.org/2025/05/27/a-whirlwind-tour-of-t-strings/<p>I gave <a href="https://www.youtube.com/watch?v=lXngPPRaqGg&amp;t=2291s">a lightning talk</a> at PyCon introducing t-strings to a hall full of Pythonistas. At just under five minutes, the intro <em>had</em> to be a "whirlwind". I hope I managed to convey the basics in a fun way.</p> <p>PS: right after I spoke, <a href="https://www.sheenaoc.com">Sheena O'Connell</a> gave a talk about <a href="https://www.youtube.com/watch?v=lXngPPRaqGg&amp;t=2625s">Python in Africa</a> that I found inspiring. It's worth a <a href="https://www.youtube.com/watch?v=lXngPPRaqGg&amp;t=2625s">watch</a>.</p> Tue, 27 May 2025 15:26:00 GMThttps://davepeck.org/2025/05/21/pycon-wrapup/https://davepeck.org/2025/05/21/pycon-wrapup/<p>I just ran across* <a href="https://wsvincent.com/about/">Will Vincent</a>'s delightful <a href="https://wsvincent.com/pyconus-recap/">PyCon 2025 Recap</a>, the first of many recaps I hope to read from folks I met at the conference.</p> <p>* I'm old, so I "ran across" it in my RSS reader.</p> Wed, 21 May 2025 21:30:00 GMThttps://davepeck.org/2025/05/19/t-strings-help-website/https://davepeck.org/2025/05/19/t-strings-help-website/<p><a href="https://us.pycon.org/2025/">PyCon US</a> has been a whirlwind of activity. One fun project: we stood up a simple but hopefully useful new <a href="https://t-strings.help/">t-strings help website</a>.</p> Mon, 19 May 2025 12:11:00 GMThttps://davepeck.org/2025/05/14/talk-python-podcast-episode-505-t-strings-in-python/https://davepeck.org/2025/05/14/talk-python-podcast-episode-505-t-strings-in-python/<p>Just in time for <a href="https://us.pycon.org/2025/">PyCon US</a>, I had the pleasure of being a guest on the <a href="https://talkpython.fm/">Talk Python</a> podcast with <a href="https://mkennedy.codes/about/">Michael Kennedy</a>. Along with <a href="https://github.com/pauleveritt">Paul</a> and <a href="https://github.com/jimbaker">Jim</a>, two of my <a href="https://peps.python.org/pep-0750/">PEP 750</a> co-conspirators, we talked all about the new <a href="/2025/04/11/pythons-new-t-strings/">t-strings</a> feature that will ship later this year with Python 3.14.</p> <p>You can <a href="https://talkpython.fm/episodes/show/505/t-strings-in-python">listen to the episode here</a>.</p> Wed, 14 May 2025 15:53:00 GMThttps://davepeck.org/2025/05/06/vibe-coding-videos/https://davepeck.org/2025/05/06/vibe-coding-videos/<p>LLMs are powerful levers for writing code. With time and plenty of experimentation, I've found tools and strategies that work well for me.</p> <p>LLMs are <em>also</em> wildly flexible. To learn how others use them, I've started watching screencasts by expert engineers. Three I recommend:</p> <ul> <li><a href="https://www.youtube.com/@kevinleneway2290">Kevin Leneway</a> is one of the most talented software developers I've had the pleasure of <a href="https://psl.com/">working with</a>. He recently published a series on <a href="https://www.youtube.com/watch?v=gvpxq1hqzXY">building a Cursor-like tool</a> and has accumulated a number of <a href="https://www.youtube.com/watch?v=5Lu7k2SShNw">interesting techniques</a> for getting the most out of LLMs.</li> <li><a href="https://www.youtube.com/@MaryRoseCook">Mary Rose Cook</a> is an expert in game development and is currently building a <a href="https://www.youtube.com/watch?v=WcpfyZ1yQRA">2D tactical shooter</a> with AI. Her approach strikes an interesting balance between being particular about architecture and requirements and letting LLMs do their thing.</li> <li><a href="https://www.youtube.com/@PamelaFox">Pamela Fox</a> is a Cloud Advocate at Microsoft and is currently using LLMs to assist with <a href="https://www.youtube.com/watch?v=ZD8_1krNW38&amp;t=893s">complex backend tasks in Python</a>.</li> </ul> <p>All three have <em>very</em> different styles; I've learned quite a bit from each. Check out their channels and see what vibes with you.</p> Tue, 06 May 2025 15:34:00 GMTPhoto: Covel Creek Fallshttps://davepeck.org/2025/05/05/covel-creek-falls/https://davepeck.org/2025/05/05/covel-creek-falls/<a href="https://davepeck.org/2025/05/05/covel-creek-falls.jpg"> <img src="https://davepeck.org/2025/05/05/covel-creek-falls.jpg" /> </a><p>The view from behind Covel Creek Falls, during a weekend campout in the Gifford Pinchot National Forest.</p>Mon, 05 May 2025 18:36:00 GMThttps://davepeck.org/2025/04/14/pep-787-safer-subprocess-usage-using-t-strings/https://davepeck.org/2025/04/14/pep-787-safer-subprocess-usage-using-t-strings/<p>PEP 787, <a href="https://peps.python.org/pep-0787/"><em>Safer subprocess usage using t-strings</em></a>, is a newly proposed <a href="https://peps.python.org/pep-0001/">PEP</a> that improves the safety of <a href="https://docs.python.org/3/library/subprocess.html">subprocess</a> invocations in Python by building directly on the <a href="/2025/04/11/pythons-new-t-strings/">template strings</a> features of <a href="https://peps.python.org/pep-0750/">PEP 750</a>. I couldn't be more excited to see it! The proposal is <a href="https://discuss.python.org/t/pep-787-safer-subprocess-usage-using-t-strings/">open for feedback</a>.</p> <p>(PS: This new proposal was written by the <a href="https://x.com/nhumrich">two</a> <a href="https://mastodon.social/@ancoghlan">authors</a> of <a href="https://peps.python.org/pep-0501/">PEP 501</a>, which fed directly into the development of <a href="https://peps.python.org/pep-0750/">PEP 750</a>. Nick Humrich offers a good perspective on the history <a href="https://news.ycombinator.com/item?id=43648120">in his recent HN posts</a>.)</p> Mon, 14 Apr 2025 16:10:00 GMTPython's new t-stringshttps://davepeck.org/2025/04/11/pythons-new-t-strings/https://davepeck.org/2025/04/11/pythons-new-t-strings/<p>Template strings, also known as t-strings, have been <a href="https://peps.python.org/pep-0750/">officially accepted</a> as a feature in Python 3.14, which will ship in October 2025. 🎉</p> <p>I'm excited about t-strings because they make string processing safer and more flexible. In this post, I'll explain what t-strings are, why they were added to Python, and how you can use them.</p> <h4>What are t-strings?</h4> <p>Template strings are like f-strings with superpowers. They share the same familiar syntax:</p> <pre><code>food = "cheese" string = f"Tasty {food}!" template = t"Tasty {food}!" </code></pre> <p>But they result in different types:</p> <pre><code>food = "cheese" type(f"Tasty {food}!") # &lt;class 'str'&gt; type(t"Tasty {food}!") # &lt;class 'string.templatelib.Template'&gt; </code></pre> <p>F-strings are "just" strings. But t-strings give you a new type, <code>Template</code>, that lets you access the <strong>parts</strong> of your string:</p> <pre><code>food = "cheese" template = t"Tasty {food}!" list(template) # ['Tasty ', Interpolation(value='cheese'), '!'] </code></pre> <p>The <code>Interpolation</code> type is <em>also</em> new in Python 3.14. It's a fancy way of saying "this part of your string was created by substitution."</p> <p>By giving developers access to the parts of strings, t-strings make it possible to write code that processes strings in powerful and safe ways.</p> <h4>Why t-strings?</h4> <p>Since they were introduced in Python 3.6, <a href="https://docs.python.org/3/tutorial/inputoutput.html#formatted-string-literals">f-strings</a> have become <em>very</em> popular. Developers use them for <em>everything</em>... even when they shouldn't.</p> <p>The most common (mis)uses of f-strings lead to security vulnerabilities like <a href="https://owasp.org/www-community/attacks/SQL_Injection">SQL injection</a> and <a href="https://owasp.org/www-community/attacks/xss/">cross-site scripting</a> (XSS).</p> <p>Here's the famous "<a href="https://xkcd.com/327/">Little Bobby Tables</a>" vulnerability using f-strings:</p> <pre><code>from my_database_library import execute_sql def get_student(name: str) -&gt; dict: query = f"SELECT * FROM students WHERE name = '{name}'" return execute_sql(query) get_student("Robert'); DROP TABLE students;--") # ☠️ ☠️ ☠️ </code></pre> <p>That <code>execute_sql()</code> function takes a <code>str</code> as input. It has no way to know whether that <code>Robert');</code> nonsense was intended or not. (It wasn't.)</p> <p>Template strings solve this problem because they keep track of which parts of a string are static and which parts are dynamic. This makes it possible to write libraries that safely process user input. An updated version of the database library could look like this:</p> <pre><code>from string.templatelib import Template from my_database_library import execute_sql_t def get_student(name: str) -&gt; dict: query = t"SELECT * FROM students WHERE name = '{name}'" return execute_sql_t(query) get_student("Robert'); DROP TABLE students;--") # 🎉 🦄 👍 </code></pre> <p>No more SQL injection! The <code>execute_sql_t()</code> function can see that <code>{name}</code> is a substitution and can safely escape its value.</p> <p>Importantly, <code>Template</code> instances are <em>not</em> strings. They cannot be used in places that expect a <code>str</code>. They cannot be concatenated with plain strings. They offer no specialized <code>__str__()</code> implementation; <code>str(template)</code> is not useful. Instead, if you want to convert a <code>Template</code> to a <code>str</code>, you must explicitly process it first.</p> <h4>How do I work with t-strings?</h4> <p>The easiest way to get access to the parts of a <code>Template</code> is to iterate over it:</p> <pre><code>food = "cheese" template = t"Tasty {food}!" for part in template: if isinstance(part, str): print("String part:", part) else: print("Interpolation part:", part.value) # String part: Tasty # Interpolation part: cheese # String part: ! </code></pre> <p>You can also access the parts directly via the <code>.strings</code> and <code>.values</code> properties:</p> <pre><code>food = "cheese" template = t"Tasty {food}!" assert template.strings == ("Tasty ", "!") assert template.values == (food,) </code></pre> <p>There is always one more (possibly empty) string than value. That is, <code>t"".strings == ("",)</code> and <code>t"{food}".strings == ("", "")</code>.</p> <p>Developers writing complex processing code can also access the gory details of each interpolation:</p> <pre><code>food = "cheese" template = t"Tasty {food!s:&gt;8}!" interpolation = template.interpolations[0] assert interpolation.value == "cheese" assert interpolation.expression == "food" assert interpolation.conversion == "s" assert interpolation.format_spec == "&gt;8" </code></pre> <p>If you've gone deep with f-strings in the past, you may recognize that <code>conversion</code> and <code>format_spec</code> correspond to the <code>!s</code> and <code>:&gt;8</code> parts of the <a href="https://docs.python.org/3/reference/lexical_analysis.html#formatted-string-literals">f‑string syntax</a>.</p> <p>The <code>expression</code> property is a string containing the <em>exact</em> expression that was used inside the curly braces. This means you can use <code>ast.parse()</code> to analyze it further if you need to.</p> <p>Most of the time, you'll probably create <code>Template</code> instances using the <code>t"..."</code> literal syntax. But sometimes, you may want to create them programmatically:</p> <pre><code>from string.templatelib import Template, Interpolation template = Template("Tasty ", Interpolation("cheese"), "!") </code></pre> <p>Strings and interpolations can be provided to the <code>Template</code> constructor in any order.</p> <h4>Processing t-strings</h4> <p>Let's say we wanted to write code to convert all substituted words into pig latin. All it takes is a simple function:</p> <pre><code>def pig_latin(template: Template) -&gt; str: """Convert a Template to pig latin.""" result = [] for item in template: if isinstance(item, str): result.append(item) else: word = item.value if word and word[0] in "aeiou": result.append(word + "yay") else: result.append(word[1:] + word[0] + "ay") return "".join(result) food = "cheese" template = t"Tasty {name}!" assert pig_latin(template) == "Tasty heesecay!" </code></pre> <p>This is a goofy example; if you'd like to see some <em>less</em> silly examples, check out the <a href="https://github.com/davepeck/pep750-examples/">PEP 750 examples repository</a>.</p> <h4>Real-world uses for t-strings</h4> <p>We can imagine a library that provides an <code>html()</code> function that takes a <code>Template</code> and returns a safely escaped string:</p> <pre><code>user_supplied_input = "&lt;script&gt;alert('pwn')&lt;/script&gt;" safe = html(t"&lt;p&gt;{user_supplied_input}&lt;/p&gt;") assert safe == "&lt;p&gt;&amp;lt;script&amp;gt;alert('pwn')&amp;lt;/script&amp;gt;&lt;/p&gt;" </code></pre> <p>Of course, t-strings are useful for more than just safety; they also allow for more flexible string processing. For example, that <code>html()</code> function could return a new type, <code>Element</code>. It could also accept all sorts of useful substitutions in the HTML itself:</p> <pre><code>attributes = {"src": "roquefort.jpg", "alt": "Yum"} element = html(t"&lt;img {attributes} /&gt;") # You can imagine Element having a nice __str__ method: assert str(element) == "&lt;img src='roquefort.jpg' alt='Yum' /&gt;" </code></pre> <p>If you've worked with JavaScript, these examples may feel familiar. That's because t-strings are the Pythonic parallel to JavaScript's <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates">tagged templates</a>. HTML templating libraries like <a href="https://www.npmjs.com/package/lit-html"><code>lit-html</code></a> and <a href="https://www.npmjs.com/package/htm"><code>htm</code></a> are quite popular in the JavaScript ecosystem; t-strings make it possible to build similar libraries in Python.</p> <h4>What's next once t-strings ship?</h4> <p>T-strings are a powerful new feature that will make Python string processing safer and more flexible. I hope to see them used in all sorts of libraries and frameworks, especially those that deal with user input.</p> <p>In addition, I hope that the tooling ecosystem will adapt to support t-strings. For instance, I'd love to see <code>black</code> and <code>ruff</code> format t-string <em>contents</em>, and <code>vscode</code> <em>color</em> those contents, if they're a common type like HTML or SQL.</p> <p>It's been fun to get to know and work with <a href="https://github.com/jimbaker">Jim</a>, <a href="https://github.com/pauleveritt">Paul</a>, <a href="https://github.com/koxudaxi">Koudai</a>, <a href="https://github.com/lysnikolaou">Lysandros</a>, and <a href="https://en.wikipedia.org/wiki/Guido_van_Rossum">Guido</a> on this project and to interact with <a href="https://discuss.python.org/t/pep750-template-strings-new-updates/71594">many more members of the Python community</a> online without whose input PEP 750 simply wouldn't have come together. I can't wait to see what developers build with t-strings once they ship!</p> Fri, 11 Apr 2025 15:31:00 GMThttps://davepeck.org/2025/02/08/we-are-destroying-software/https://davepeck.org/2025/02/08/we-are-destroying-software/<p><a href="https://antirez.com/">Antirez</a> is back with a short list of <a href="https://antirez.com/news/145">how we're destroying software</a>. A few excerpts:</p> <blockquote> <p>We are destroying software with complex build systems.</p> <p>We are destroying software pushing for rewrites of things that work.</p> <p>We are destroying software trying to produce code as fast as possible, not as well designed as possible.</p> <p>...</p> </blockquote> <p>I dunno. It seems like it was ever thus. We've always layered on terrible leaky abstractions and built unfriendly brittle tools. We've always needlessly rewritten systems that work just fine. We've always rushed to get it out the door now, not out the door right.</p> <p>But I do want to call this one out:</p> <blockquote> <p>We are destroying software mistaking it for a purely engineering discipline.</p> </blockquote> <p>So much this. People and politics and life are so much a part of software. The tools we build reflect our values and beliefs. They enable others to promote and entrench their values and beliefs, whether for good or for ill. Let's work to make it for good.</p> <p><a href="https://antirez.com/news/145">Link</a></p> Sat, 08 Feb 2025 19:10:00 GMThttps://davepeck.org/2025/01/21/welp/https://davepeck.org/2025/01/21/welp/<p>Welp. He's back.</p> <p>Yesterday, like all incoming presidents, Trump signed a barrage of executive orders. <em>Unlike</em> other incoming presidents, many of his actions were bizarre, unlawful, or outright unconstitutional. <a href="https://www.lawfaremedia.org/">Lawfare</a> remains my go-to source for both <a href="https://www.lawfaremedia.org/article/trump's-tiktok-executive-order-and-the-limits-of-executive-non-enforcement">cautious analysis</a> and <a href="https://www.lawfaremedia.org/article/documents">original documents</a>.</p> <p>From where I sit, the one truly terrifying action Trump took yesterday was <a href="https://www.lawfaremedia.org/article/trump-pardons-or-commutes-terms-of-all-jan.-6-rioters">pardoning or commuting the terms of every January 6th insurrectionist</a>. What better way to buy the loyalty of his most violent supporters? What better way to ensure future violence should such violence serve his needs?</p> Tue, 21 Jan 2025 17:10:00 GMTSmall tech in 2025https://davepeck.org/2025/01/15/small-tech-in-2025/https://davepeck.org/2025/01/15/small-tech-in-2025/<p>Big tech can fend for itself. For me, small tech — home to all manner of creative computer weirdos — is where the fun is at. Here are some trends I'm keeping an eye on:</p> <h4>The open social web</h4> <p>The open social web hit its stride in 2024. Idealists and software nerds got organized: the <a href="https://socialwebfoundation.org/team/">Social Web Foundation</a> emerged to steward the <a href="https://www.w3.org/TR/activitypub/">ActivityPub</a> ecosystem, <a href="https://freeourfeeds.com">Free Our Feeds</a> started holding <a href="https://atproto.com">ATProto</a>'s feet to the decentralized <a href="https://dustycloud.org/blog/how-decentralized-is-bluesky/">fire</a>, and <a href="https://www.anew.social">A New Social</a> began building <a href="https://fed.brid.gy">bridges</a> between open networks. Independent researchers brought focus to the <a href="https://erinkissane.com/fediverse-governance-drop">challenges of decentralized governance</a> while nonprofits developed new resources for <a href="https://about.iftas.org">federated moderation</a>. And, in early 2025, <a href="https://joinmastodon.org/about">Mastodon</a> took big steps toward <a href="https://blog.joinmastodon.org/2025/01/the-people-should-own-the-town-square/">community ownership</a>.</p> <p>Creative tinkerers have leveraged the social web's openness to build interesting indie businesses. <a href="https://surf.social">Surf.social</a> ships an innovative UI for exploring open social content. <a href="https://murmel.social">Murmel</a> sends top stories from your feed directly to your inbox. <a href="https://masto.host">Mastohost</a> makes it easy to self-host Mastodon. And <a href="https://micro.blog">Micro.blog</a> continues to thoughtfully explore new directions.</p> <h4>Novel use of large language models</h4> <p>While big tech spins its wheels on <a href="https://www.salesforce.com/agentforce/agentic-systems/">"agentic" AI hype</a>, small tech is discovering creative and force-multiplying new uses for LLMs.</p> <p>Researchers like <a href="https://wattenberger.com">Amelia Wattenberger</a>, <a href="https://thesephist.com">Linus Lee</a>, <a href="https://tonybeltramelli.com">Tony Beltramelli</a>, <a href="https://maggieappleton.com">Maggie Appleton</a>, and <a href="https://www.swyx.io">Shawn Wang</a> are all exploring LLM-powered user experiences built on tight feedback loops. My favorite of these interface prototypes <em>avoid</em> chat-like interactions entirely, instead hiding LLMs gently in the background. Gestures lead to LLM invocations, which lead to the further <a href="https://wattenberger.com/thoughts/our-interfaces-have-lost-their-senses">evolution of the interface</a>.</p> <p>Meanwhile, services like <a href="https://citymeetings.nyc">CityMeetings.nyc</a> show the potential for LLMs to transmute piles of unstructured content into actionable insight. CityMeetings breaks down every New York City Council meeting into easily readable and linkable digests. It has become an essential tool for local journalists. Impressively, CityMeetings is a <a href="https://vikramoberoi.com/about/">one-person project</a>!</p> <p>Small tech is (of course!) full of indie devs, who naturally experiment with LLMs for their own software development. AI code assistants like <a href="https://github.com/features/copilot">GitHub Copilot</a> are now omnipresent, and editor workflow innovations from <a href="https://cursor.com">Cursor</a> and <a href="https://windsurfai.org">Windsurf AI</a>, along with chat UX features like <a href="https://support.anthropic.com/en/articles/9487310-what-are-artifacts-and-how-do-i-use-them">Anthropic's Artifacts</a>, suggest where tools might go next. Small tech startups like <a href="https://www.val.town">Val Town</a> have learned from these patterns and introduced <a href="https://www.val.town/townie/signup">their own bespoke AI tools</a> for developers.</p> <h4>Experiments in local-first</h4> <p>The small tech community has a long history of building tools that prioritize user agency and privacy. That's why, when <a href="https://martin.kleppmann.com">Martin Kleppmann</a> and <a href="https://adamwiggins.com">Adam Wiggins</a> introduced the idea of "<a href="https://www.inkandswitch.com/local-first.html">local-first software</a>" architecture, the community paid attention. Local-first applications store data locally, work offline, and sync with other devices when possible — aligning nicely with the ethos of small tech and the open social web.</p> <p>In his fun-to-read <a href="https://macwright.com/2025/01/11/predictions">predictions for 2025</a>, <a href="https://macwright.com/about/">Tom MacWright</a> anticipates that "local-first will have a breakthrough moment". The technical challenges — building sync engines, resolving conflicts, and managing schema migrations — remain substantial, so I don't expect to see a true market breakthrough. That said, new frameworks like <a href="https://zero.rocicorp.dev">Zero</a>, <a href="https://electric-sql.com">Electric</a>, and <a href="https://jazz.tools">Jazz</a> show that indie devs will keep pushing the envelope.</p> <h4>Sustainable and small</h4> <p>I'm always drawn to indie software shops that eschew "traditional" VC financing in favor of a focus on sustainable growth and personal independence. I'm delighted by the sheer amount of <a href="https://www.val.town">technical depth</a>, <a href="https://www.hiddendoor.co">art</a>, <a href="https://home.omg.lol">whimsy</a>, and <a href="https://obsidian.md">nerdy joy</a> I'm seeing from today's crop of small tech startups. Creative experimentation is alive and well. While big tech keeps embiggening, small tech is quietly shaping a more humane and thoughtful future.</p> Wed, 15 Jan 2025 18:43:00 GMThttps://davepeck.org/2025/01/07/severance-theme-song-defiant-piano-jazz/https://davepeck.org/2025/01/07/severance-theme-song-defiant-piano-jazz/<p><i>This is an audio post. You can listen to the audio here: <a href="https://davepeck.org/audio/2025/01/07/davepeck-dot-org_severance-theme-song-defiant-piano-jazz-jam-v2.mp3">Severance theme song: Defiant Piano Jazz</a></i></p><p>It's 2025 and I'm apparently unreasonably excited about the <a href="https://www.youtube.com/watch?v=VwP6M9zS_pQ">upcoming second season of Severance</a>. It's the TV show with the most <em><a href="https://www.imdb.com/title/tt0120601/">Being John Malkovich</a></em> energy since, well, <em>Malkovich</em>.</p> <p>The <a href="https://www.youtube.com/watch?v=NmS3m0OG-Ug">opening theme song</a> was stuck in my head all vacation; I couldn't resist noodling with it on the keys. So here's my defiant piano jazz jam:</p> Tue, 07 Jan 2025 18:31:00 GMT