<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
  <id>https://daniel.feldroy.com/</id>
  <title>Til posts by Daniel Roy Greenfeld</title>
  <updated>2026-06-09T10:43:01.547617+00:00</updated>
  <author>
    <name>Daniel Roy Greenfeld</name>
    <email>daniel@feldroy.com</email>
    <uri>https://daniel.feldroy.com</uri>
  </author>
  <link href="https://daniel.feldroy.com" rel="alternate"/>
  <generator uri="https://lkiesow.github.io/python-feedgen" version="1.0.0">python-feedgen</generator>
  <logo>https://f004.backblazeb2.com/file/daniel-feldroy-com/public/images/profile.jpg</logo>
  <rights>All rights reserved 2026, Daniel Roy Greenfeld</rights>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-05-bitwise-xor</id>
    <title>TIL: ^ bitwise XOR</title>
    <updated>2025-05-07T03:30:43.170061+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;The bitwise XOR operator &lt;code&gt;^&lt;/code&gt;, also known as &lt;code&gt;exclusive or&lt;/code&gt;, can be used to compare boolean objects to see if one and only one is &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Let's see it in action, first comparing three &lt;code&gt;False&lt;/code&gt; booleans, which will return &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;```python {.marimo}
False ^ False ^ False&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;let&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;s demonstrate three different combinations of a single `True` and any number of `False` booleans, which will return `True` in each case.&lt;/span&gt;

&lt;span class="s1"&gt;```python {.marimo}&lt;/span&gt;
&lt;span class="s1"&gt;print(True ^ False ^ False)&lt;/span&gt;
&lt;span class="s1"&gt;print(False ^ True ^ False)&lt;/span&gt;
&lt;span class="s1"&gt;print(False ^ False ^ True)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;However, if we have two or more values that are &lt;code&gt;True&lt;/code&gt;, the result will be &lt;code&gt;False&lt;/code&gt; because more than one value are &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;```python {.marimo}
print(True ^ True ^ False)
print(True ^ False ^ True)
print(False ^ True ^ True)&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;## What about non-boolean types?&lt;/span&gt;

&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n n-Quoted"&gt;`^`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;works&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;If&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;non&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;you&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ll get a `TypeError`. For example, if you try to use it on integers or strings to check for truthiness, you&amp;#39;&lt;/span&gt;&lt;span class="n"&gt;ll&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n n-Quoted"&gt;`&lt;/span&gt;&lt;span class="n n-Quoted n-Quoted-Escape"&gt;``&lt;/span&gt;&lt;span class="n n-Quoted"&gt;python&lt;/span&gt;
&lt;span class="n n-Quoted"&gt;&amp;#39;&amp;#39; ^ &amp;#39;&amp;#39; ^ &amp;#39;one&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="nx"&gt;TypeError&lt;/span&gt;&lt;span class="w"&gt;                   &lt;/span&gt;&lt;span class="nx"&gt;Traceback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;most&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;recent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;last&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;In&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;one&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;

&lt;span class="nx"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unsupported&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;operand&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;&lt;span class="nx"&gt;str&lt;/span&gt;&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To make this comparison works, you can convert the non-boolean types to boolean first. For example, you can use the &lt;code&gt;bool()&lt;/code&gt; function to convert an integer or string to a boolean before using the &lt;code&gt;^&lt;/code&gt; operator.&lt;/p&gt;
&lt;p&gt;```python {.marimo}
bool('') ^ bool('') ^ bool('one')&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="cp"&gt;## Updates&lt;/span&gt;

&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2025&lt;/span&gt;&lt;span class="mo"&gt;-05-07&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Updated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;XOR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;returns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;only&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;one&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;True&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Removed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;segment&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;too&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;far&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;away&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;design&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="n"&gt;XOR&lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Credit&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;goes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Curt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Merrill&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//bsky.app/profile/cmerrill.com) and [Rens Dimmendaal](https://rensdimmendaal.com/).&lt;/span&gt;

&lt;span class="err"&gt;```&lt;/span&gt;&lt;span class="n"&gt;python&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{.&lt;/span&gt;&lt;span class="n"&gt;marimo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;hide_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;true&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;mo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;python {.marimo}
import marimo as mo&lt;/code&gt;&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-05-bitwise-xor"/>
    <summary>How to mark a comparison of booleans as True or False using bitwise XOR.</summary>
    <category term="TIL"/>
    <category term="python"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-05-07T03:30:43.170061+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-05-exception-add_note</id>
    <title>TIL: Exception.add_note</title>
    <updated>2025-05-13T14:10:31.105025+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;Python 3.11 introduced a new method called &lt;code&gt;add_note&lt;/code&gt; for exceptions, which allows you to add extra information to exceptions in an easy, intuitive way.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;ZeroDivisionError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_note&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;This is a note about the error&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_note&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;This is another note&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_note&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;All notes must be strings&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The output will look something like this:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;=========================================================
ZeroDivisionError       Traceback (most recent call last)
Cell In[1], line 2
      1 try:
====&amp;gt; 2     1/0
      3 except ZeroDivisionError as e:
      4     e.add_note(&amp;quot;This is a note about the error&amp;quot;)

ZeroDivisionError: division by zero
This is a note about the error
This is another note
All notes must be strings
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;See those last three lines? Those are the notes we added to the exception! &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;add_note&lt;/code&gt; allows for adding extra info without overriding the normal error, printing after the rest of the exception. The notes must be strings, and are stored in a list at the &lt;code&gt;__notes__&lt;/code&gt; attribute. &lt;/p&gt;
&lt;p&gt;Finally, technically speaking, &lt;code&gt;add_note&lt;/code&gt; is a method of the &lt;code&gt;BaseException&lt;/code&gt; class, which is the base class for Python exceptions. This means that we can use it with any and all Python exceptions.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-05-exception-add_note"/>
    <summary>Adding extra info to exceptions the easy way.</summary>
    <category term="TIL"/>
    <category term="python"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-05-13T14:10:31.105025+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-06-html-404-errors-for-fastapi</id>
    <title>TIL: HTML 404 errors for FastAPI</title>
    <updated>2025-06-13T04:23:23.230032+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;I had a TIL about custom responses in FastAPI but there's enough nuance that it deserves a full blog post. In the meantime, here's a TIL about custom HTTP responses. &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;fastapi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;fastapi.responses&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HTMLResponse&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;custom_404_exception_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;HTMLResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;lt;p&amp;gt;404 Not Found at &amp;quot;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s1"&gt;&amp;quot;&amp;lt;/p&amp;gt;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Add more HTTP exceptions as needed&lt;/span&gt;
&lt;span class="n"&gt;HTTP_EXCEPTIONS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;custom_404_exception_handler&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exception_handlers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;HTTP_EXCEPTIONS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;read_root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Hello&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;World&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Try it out by running the app and going to a non-existent path, like &lt;code&gt;/not-found&lt;/code&gt;. You should see a simple HTML page with a 404 message.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-06-html-404-errors-for-fastapi"/>
    <summary>For those times when FastAPI is serving web pages and users go to the wrong place.</summary>
    <category term="python"/>
    <category term="fastapi"/>
    <category term="TIL"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-06-13T04:23:23.230032+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-06-removing-exif-geodata-from-media</id>
    <title>TIL: Removing exif geodata from media</title>
    <updated>2025-06-23T20:56:49.362878+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;To do this you'll need &lt;a href="https://exiftool.org/"&gt;exiftool&lt;/a&gt;. On Mac it can be installed with &lt;code&gt;brew install exiftool&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;How to remove the geodata:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;exiftool&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;-gps*=&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;beach-video.mp4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-06-removing-exif-geodata-from-media"/>
    <summary>How to remove revealing data from your images and videos</summary>
    <category term="TIL"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-06-23T20:56:49.362878+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-07-single-source-version-package-builds-with-uv</id>
    <title>TIL: Single source version package builds with uv</title>
    <updated>2025-07-23T01:59:20.353392+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;ol&gt;
&lt;li&gt;Remove &lt;code&gt;version&lt;/code&gt; in &lt;code&gt;pyproject.toml&lt;/code&gt; and replace with &lt;code&gt;dynamic = ["version"]&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;[tool.setuptools.dynamic]&lt;/code&gt; and specify the location of the version using this dialogue: &lt;code&gt;version = { attr = "mypackage.__version__" }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;In your project's root &lt;code&gt;__init__.py&lt;/code&gt;, add a &lt;code&gt;__version__&lt;/code&gt; attribute.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# pyproject.toml&lt;/span&gt;
&lt;span class="k"&gt;[project]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mypackage&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;dynamic&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;version&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;# version = &amp;quot;0.1.0&amp;quot; # Don&amp;#39;t set version here&lt;/span&gt;

&lt;span class="k"&gt;[tool.setuptools.dynamic]&lt;/span&gt;
&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;attr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;mypackage.__version__&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# Set version here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Don't forget to specify the version in Python:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# mypackage/__init__.py&lt;/span&gt;
&lt;span class="n"&gt;__version__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;0.1.0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Thanks to Audrey Roy Greenfeld for pairing with me on getting this to work.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-07-single-source-version-package-builds-with-uv"/>
    <summary>Tired of updating the version in multiple places before publishing a package update? Leery of using inspect.metadata to fetch the package? Here's how to have a single source of version using UV's build subcommand.</summary>
    <category term="TIL"/>
    <category term="python"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-07-23T01:59:20.353392+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-07-how-to-type-args-and-kwargs</id>
    <title>TIL: How to type args and kwargs</title>
    <updated>2025-07-26T09:15:07.896468+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;An oddity of my work for a while has been that I haven't used &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt; with type annotations. Recently, however I've been working on code that leans on those things a lot. And I've been ignoring setting types there because this fails wretchedly with type checking libraries:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# This fails type checks. :(&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;typing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;That's because the types you set in the args are the value of the container. So to make my code pass the type checkers I need to do:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# This passes type checks!&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;typing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Thanks to &lt;a href="https://willmcgugan.github.io/"&gt;Will McGugan&lt;/a&gt; for explaining this to me. I'm a fan of his work, which includes the inestimable &lt;a href="https://github.com/Textualize/rich"&gt;Rich&lt;/a&gt; and &lt;a href="https://textual.textualize.io/"&gt;Textualize&lt;/a&gt; libraries!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Update&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2025-07-28: Luke Plant points out that Adam Johnson has a good article on this topic: https://adamj.eu/tech/2021/05/11/python-type-hints-args-and-kwargs/&lt;/li&gt;
&lt;/ul&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-07-how-to-type-args-and-kwargs"/>
    <summary>A reduction in boilerplate confused me, the answer is that the type to define is the value in the containers.</summary>
    <category term="howto"/>
    <category term="python"/>
    <category term="TIL"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-07-26T09:15:07.896468+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-08-single-source-version-package-builds-with-uv-redux</id>
    <title>TIL: Single source version package builds with uv (redux)</title>
    <updated>2025-08-22T02:20:08.473528+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;Not that long ago I wrote about &lt;a href="https://daniel.feldroy.com/posts/til-2025-07-single-source-version-package-builds-with-uv"&gt;how to use UV to build packages with a single source of truth for the version number&lt;/a&gt;. Since then, my friend &lt;a href="https://adamj.eu/"&gt;Adam Johnson&lt;/a&gt; has pointed out that I could be doing it better. Here's how &lt;a href="https://adamj.eu/tech/2025/07/30/python-check-package-version-importlib-metadata-version/"&gt;he demonstrated&lt;/a&gt; I should be doing it instead.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# pyproject.toml&lt;/span&gt;
&lt;span class="k"&gt;[project]&lt;/span&gt;
&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;air&amp;quot;&lt;/span&gt;
&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;0.25.0&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# This is the source of truth for the version number&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The way to check programmatically the version number is to rely not on someone setting &lt;code&gt;__version__&lt;/code&gt; in the code, but rather to use the following technique:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;importlib.metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;
&lt;span class="n"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;air&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# This will print &amp;quot;0.25.0&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Thanks for the tip, Adam! This is a much cleaner and tool friendly way to ensure that the version number is consistent across your package without having to manually update it in multiple places.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-08-single-source-version-package-builds-with-uv-redux"/>
    <summary>Tired of updating the version in multiple places before publishing a package update? Here's how Adam Johnson told me I should be doing it.</summary>
    <category term="TIL"/>
    <category term="python"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-08-22T02:20:08.473528+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-08-using-sqlmodel-asynchronously-with-fastapi-and-air-with-postgresql</id>
    <title>TIL: Using SQLModel Asynchronously with FastAPI (and Air) with PostgreSQL</title>
    <updated>2025-08-29T05:54:58.034855+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;First, let's set up our environment. We'll need to install the necessary packages:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uv&lt;span class="w"&gt; &lt;/span&gt;venv
uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;FastAPI[standard]&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SQLModel&lt;span class="w"&gt; &lt;/span&gt;asyncpg&lt;span class="w"&gt; &lt;/span&gt;psycopg2-binary&lt;span class="w"&gt; &lt;/span&gt;greenlet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A quick quick guide to these dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;FastAPI&lt;/strong&gt;: The web framework we'll be using. We install with the [standard] extras to make running in the shell easy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLModel&lt;/strong&gt;: The ORM for interacting with our SQL database&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;psycopg2-binary&lt;/strong&gt;: A PostgreSQL adapter for Python, which will be used by SQLModel for synchronous operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;asyncpg&lt;/strong&gt;: An asynchronous PostgreSQL client library, which will be used by SQLModel for async operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;greenlet&lt;/strong&gt;: A dependency for SQLAlchemy, which powers SQLModel, to support asynchronous operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yes, we need BOTH synchronous and asynchronous PostgreSQL drivers. SQLModel (and SQLAlchemy) use the synchronous driver for certain operations, even when we are mostly working in an async context.&lt;/p&gt;
&lt;h2 id="fastapi"&gt;FastAPI&lt;/h2&gt;
&lt;p&gt;Here we'll set up a simple FastAPI application that uses SQLModel asynchronously (and synchronously).  As far as I can tell, to create tables we need to use a synchronous engine, which explains the need for both drivers and engines. Specifically we'll create (synchronously) a &lt;code&gt;Story&lt;/code&gt; model and two asynchronous endpoints: one for retrieving stories and another for creating a new story.&lt;/p&gt;
&lt;p&gt;When creating my example database, I used 'drg' as my username and called the database 'my-database'. Adjust the connection strings as needed for your setup.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="sb"&gt;```&lt;/span&gt;python
&lt;span class="c1"&gt;# main.py&lt;/span&gt;
from&lt;span class="w"&gt; &lt;/span&gt;fastapi&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;Depends,&lt;span class="w"&gt; &lt;/span&gt;FastAPI
from&lt;span class="w"&gt; &lt;/span&gt;sqlalchemy.ext.asyncio&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;create_async_engine,&lt;span class="w"&gt; &lt;/span&gt;async_sessionmaker
from&lt;span class="w"&gt; &lt;/span&gt;sqlmodel.ext.asyncio.session&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;AsyncSession
from&lt;span class="w"&gt; &lt;/span&gt;sqlmodel&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;SQLModel,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;Field,&lt;span class="w"&gt; &lt;/span&gt;create_engine
from&lt;span class="w"&gt; &lt;/span&gt;typing&lt;span class="w"&gt; &lt;/span&gt;import&lt;span class="w"&gt; &lt;/span&gt;AsyncGenerator


&lt;span class="c1"&gt;# Step 0: sync engine for non-web db actions like creating tables&lt;/span&gt;
&lt;span class="nv"&gt;sync_engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;create_engine&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;postgresql://username@localhost/my-database&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 1: Create async engine and session&lt;/span&gt;
&lt;span class="nv"&gt;async_engine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;create_async_engine&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;postgresql+asyncpg://username@localhost/my-database&amp;quot;&lt;/span&gt;,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Async connection string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True,&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Optional: Set to False in production&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;future&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True,
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Set up async session&lt;/span&gt;
&lt;span class="nv"&gt;async_session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;async_sessionmaker&lt;span class="o"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;async_engine,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AsyncSession,
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;expire_on_commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False,
&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 3: Create a dependency to yield an async session&lt;/span&gt;
async&lt;span class="w"&gt; &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;_get_async_session&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt;&lt;span class="w"&gt; &lt;/span&gt;AsyncGenerator&lt;span class="o"&gt;[&lt;/span&gt;AsyncSession,&lt;span class="w"&gt; &lt;/span&gt;None&lt;span class="o"&gt;]&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;async&lt;span class="w"&gt; &lt;/span&gt;with&lt;span class="w"&gt; &lt;/span&gt;async_session&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;as&lt;span class="w"&gt; &lt;/span&gt;session:
&lt;span class="w"&gt;        &lt;/span&gt;yield&lt;span class="w"&gt; &lt;/span&gt;session

&lt;span class="c1"&gt;# Wrap _get_async_session with Depends for FastAPI to keep type checking happy&lt;/span&gt;
&lt;span class="nv"&gt;get_async_session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Depends&lt;span class="o"&gt;(&lt;/span&gt;_get_async_session&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Define a SQL model table&lt;/span&gt;
class&lt;span class="w"&gt; &lt;/span&gt;Story&lt;span class="o"&gt;(&lt;/span&gt;SQLModel,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;id:&lt;span class="w"&gt; &lt;/span&gt;int&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;None&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Field&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;None,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;True&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;slug:&lt;span class="w"&gt; &lt;/span&gt;str

&lt;span class="c1"&gt;# Create tables (synchronously, outside of request handling)&lt;/span&gt;
SQLModel.metadata.create_all&lt;span class="o"&gt;(&lt;/span&gt;sync_engine&lt;span class="o"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 4: Create FastAPI app and endpoints&lt;/span&gt;
&lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;FastAPI&lt;span class="o"&gt;()&lt;/span&gt;


@app.get&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
async&lt;span class="w"&gt; &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;index&lt;span class="o"&gt;(&lt;/span&gt;session:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AsyncSession&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;get_async_session&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;stmt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;select&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;Story&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;stories&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;await&lt;span class="w"&gt; &lt;/span&gt;session.exec&lt;span class="o"&gt;(&lt;/span&gt;stmt&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;stories&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;x.slug&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;x&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;stories&lt;span class="o"&gt;]}&lt;/span&gt;


@app.post&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/{slug}&amp;quot;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
async&lt;span class="w"&gt; &lt;/span&gt;def&lt;span class="w"&gt; &lt;/span&gt;create_story&lt;span class="o"&gt;(&lt;/span&gt;slug:&lt;span class="w"&gt; &lt;/span&gt;str,&lt;span class="w"&gt; &lt;/span&gt;session:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;AsyncSession&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;get_async_session&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;story&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;Story&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;slug&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;session.add&lt;span class="o"&gt;(&lt;/span&gt;story&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Don&amp;#39;t commit standard db actions&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;await&lt;span class="w"&gt; &lt;/span&gt;session.commit&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="c1"&gt;# Always await commits&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;slug&amp;quot;&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;story.slug&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Start it up with:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fastapi&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="w"&gt; &lt;/span&gt;main.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Go to the API docs and try it out: &lt;a href="http://localhost:8000/docs"&gt;http://localhost:8000/docs&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="air"&gt;Air&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://airdocs.fastapicloud.dev/"&gt;Air&lt;/a&gt; is a FastAPI-based framework for building HTML-focused web applications. It builds on top of FastAPI and adds features like easier templating, improved form handling, and more. You can combine it with FastAPI routes and SQLModel to create the web pages for your API application.&lt;/p&gt;
&lt;p&gt;In any case, except for single import change, as well as defining the app and endpoints, the code is identical to the FastAPI example above. The main difference is that instead of returning JSON responses, we return HTML using Air's components and layouts. Air can return Jinja-powered responses, web but here we'll use Air's built-in components for simplicity.&lt;/p&gt;
&lt;p&gt;First let's set up our environment. We'll need to install the necessary packages:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uv&lt;span class="w"&gt; &lt;/span&gt;venv
uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Air[standard]&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;SQLModel&lt;span class="w"&gt; &lt;/span&gt;asyncpg&lt;span class="w"&gt; &lt;/span&gt;psycopg2-binary&lt;span class="w"&gt; &lt;/span&gt;greenlet
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Air&lt;/strong&gt;: The FastAPI-powered web framework we'll be using. We install with the [standard] extras to make running in the shell easy&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLModel&lt;/strong&gt;: The ORM for interacting with our SQL database&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;psycopg2-binary&lt;/strong&gt;: A PostgreSQL adapter for Python, which will be used by SQLModel for synchronous operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;asyncpg&lt;/strong&gt;: An asynchronous PostgreSQL client library, which will be used by SQLModel for async operations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;greenlet&lt;/strong&gt;: A dependency for SQLAlchemy, which powers SQLModel, to support asynchronous operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now for the code. When creating my example database, I used 'drg' as my username and called the database 'my-database'. Adjust the connection strings as needed for your setup.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# main.py&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;air&lt;/span&gt;  &lt;span class="c1"&gt;# air is built on FastAPI&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;fastapi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlalchemy.ext.asyncio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;async_sessionmaker&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlmodel.ext.asyncio.session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sqlmodel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SQLModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;typing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncGenerator&lt;/span&gt;


&lt;span class="c1"&gt;# Step 0: sync engine for non-web db action&lt;/span&gt;
&lt;span class="n"&gt;sync_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;postgresql://username@localhost/my-database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 1: Create async engine and session&lt;/span&gt;
&lt;span class="n"&gt;async_engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;create_async_engine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;postgresql+asyncpg://username@localhost/my-database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Async connection string&lt;/span&gt;
    &lt;span class="n"&gt;echo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Optional: Set to False in production&lt;/span&gt;
    &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Step 2: Set up async session&lt;/span&gt;
&lt;span class="n"&gt;async_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;async_sessionmaker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;async_engine&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;class_&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AsyncSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;expire_on_commit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Step 3: Create a dependency to yield an async session&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;_get_async_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;AsyncGenerator&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;AsyncSession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;async_session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;


&lt;span class="n"&gt;get_async_session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Depends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_get_async_session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;Story&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SQLModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Field&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;


&lt;span class="n"&gt;SQLModel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;metadata&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sync_engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# After this comment, the project diverges from the FastAPI example&lt;/span&gt;

&lt;span class="c1"&gt;# Step 4: Create FastAPI app and endpoints&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Air&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_async_session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;stmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Story&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;stories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stmt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layouts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mvpcss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Article&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ol&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Li&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stories&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="nd"&gt;@app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&lt;/span&gt;&lt;span class="si"&gt;{slug}&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;create_story&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;get_async_session&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s2"&gt;&amp;quot;Note: This is a GET endpoint for simplicity; in a real app, use POST for creating resources.&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;story&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Story&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;story&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Don&amp;#39;t commit standard db actions&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# Always await commits&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layouts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mvpcss&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;H1&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;story&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;slug&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;P&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Story created! &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;air&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Go back&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Start it up with:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;fastapi&lt;span class="w"&gt; &lt;/span&gt;dev&lt;span class="w"&gt; &lt;/span&gt;main.py
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Try it out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="http://localhost:8000/first-article"&gt;http://localhost:8000/first-article&lt;/a&gt; - Just clicking this will create a new article with the slug "first-article". In a real app, you'd want to use a POST request for creating resources, but this is just for demonstration.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://localhost:8000/second-article"&gt;http://localhost:8000/second-article&lt;/a&gt; - Create a second article.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://localhost:8000"&gt;http://localhost:8000&lt;/a&gt; - View the list of articles.&lt;/li&gt;
&lt;/ul&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-08-using-sqlmodel-asynchronously-with-fastapi-and-air-with-postgresql"/>
    <summary>SQLModel is a really useful library for working with SQL databases in Python, built on top of SQLAlchemy and Pydantic. However, AFAIK there's no documentation supporting asynchronous operations for PostgreSQL, which can be a limitation when building high-performance web applications with FastAPI and Air.</summary>
    <category term="air"/>
    <category term="fastapi"/>
    <category term="python"/>
    <category term="TIL"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-08-29T05:54:58.034855+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-09-setting-environment-variables-for-pytest</id>
    <title>TIL: Setting environment variables for pytest</title>
    <updated>2025-09-02T02:29:33.084710+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;When writing tests in pytest, often there's a need to set environment variables for your tests. Instead of modifying &lt;code&gt;os.environ&lt;/code&gt; directly, which can lead to side effects and make tests harder to manage, here's how to do it with the &lt;a href="https://pypi.org/project/pytest-env/"&gt;pytest-env&lt;/a&gt; package.&lt;/p&gt;
&lt;p&gt;First, install the package. &lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pip&lt;span class="w"&gt; &lt;/span&gt;install&lt;span class="w"&gt; &lt;/span&gt;pytest-env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# classic but works great&lt;/span&gt;
uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;pytest-env&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# if you&amp;#39;re one of us cool kids using uv&lt;/span&gt;
uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;pytest-env&lt;span class="w"&gt; &lt;/span&gt;--group&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;# if you use a specific test group of dependencies&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;A lot of people use &lt;code&gt;pytest.ini&lt;/code&gt; to configure pytest, but I prefer using &lt;code&gt;pyproject.toml&lt;/code&gt; for a more modern approach. Here's how you can set environment variables in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;[tool.pytest_env]&lt;/span&gt;
&lt;span class="c1"&gt;# Test value for the SUPER_SECRET_KEY environment variable&lt;/span&gt;
&lt;span class="n"&gt;SUPER_SECRET_KEY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;ABC123&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then run your tests just like you would normally do, and now the environment variable will be picked up by whatever test or code is looking for it. That's correct, you don't need to do any further configuration.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;pytest&lt;span class="w"&gt; &lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Since a lot of API SDK libraries use environment variables for configuration, this is a great way to ensure your tests run in a controlled environment. Either with mock replies or against a test instance of the service.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-09-setting-environment-variables-for-pytest"/>
    <summary>An easier way of doing it then modifying os.environ</summary>
    <category term="python"/>
    <category term="TIL"/>
    <category term="testing"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-09-02T02:29:33.084710+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-09-merging-two-git-projects</id>
    <title>TIL: Merging two git projects</title>
    <updated>2025-09-13T08:49:50.670264+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;Of course this task can be done with copy/paste. The challenge there is the loss of git history. All the history of struggles and tribulations are gone. More important is the attribution - unless any and all contributors are made co-authors. But then the volume of attribution isn't accurate, some one who made one tiny change gets as much credit as the leading contributor.&lt;/p&gt;
&lt;p&gt;A better way is to merge the repos together while preserving the git history. Let's imagine we want to merge repo #2 into repo #1:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://github.com/feldroy/air"&gt;AIR&lt;/a&gt;&lt;/strong&gt; (A new Python web framework). This is in a repo on my laptop called &lt;code&gt;air&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AirDocs&lt;/strong&gt; (Documentation for AIR, getting merged into air). This is in a repo on github located at &lt;a href="https://github.com/feldroy/airdocs"&gt;github.com/feldroy/airdocs&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here's how I did it:&lt;/p&gt;
&lt;h2 id="step-1-add-an-airdocs-git-remote-to-air"&gt;Step 1: Add an airdocs git remote to air&lt;/h2&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;air/
git&lt;span class="w"&gt; &lt;/span&gt;remote&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;-f&lt;span class="w"&gt; &lt;/span&gt;airdocs&lt;span class="w"&gt; &lt;/span&gt;git@github.com:feldroy/airdocs.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="step-2-merge-airdocs-into-air"&gt;Step 2: Merge AirDocs into AIR&lt;/h2&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git&lt;span class="w"&gt; &lt;/span&gt;merge&lt;span class="w"&gt; &lt;/span&gt;airdocs/main&lt;span class="w"&gt; &lt;/span&gt;--allow-unrelated-histories
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;At this point code conflicts may arise, which can be resolved. In our case, we made sure that the repos didn't have overlapping files. &lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-09-merging-two-git-projects"/>
    <summary>Attribution matters to me, I want contributors to always get full credit for their effort. This is how you preserve the git history of a project you are bringing into another project.</summary>
    <category term="TIL"/>
    <category term="git"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-09-13T08:49:50.670264+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-09-env-files-with-uv-run</id>
    <title>TIL: Loading .env files with uv run</title>
    <updated>2025-09-28T22:44:18.296914+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;We don't need python-dotenv, use &lt;code&gt;uv run&lt;/code&gt; with &lt;code&gt;--env-file&lt;/code&gt;, and your env vars from &lt;code&gt;.env&lt;/code&gt; get loaded. &lt;/p&gt;
&lt;p&gt;For example, if we've got a FastAPI or Air project we can run it locally with env vars like:&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--env-file&lt;span class="w"&gt; &lt;/span&gt;.env&lt;span class="w"&gt; &lt;/span&gt;fastapi&lt;span class="w"&gt; &lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can specific different env files for different environments, like &lt;code&gt;.env.dev&lt;/code&gt;, &lt;code&gt;.env.prod&lt;/code&gt;, etc.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uv&lt;span class="w"&gt; &lt;/span&gt;run&lt;span class="w"&gt; &lt;/span&gt;--env-file&lt;span class="w"&gt; &lt;/span&gt;.env.dev&lt;span class="w"&gt; &lt;/span&gt;fastapi&lt;span class="w"&gt; &lt;/span&gt;dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;All credit goes to &lt;a href="https://audrey.feldroy.com"&gt;Audrey Roy Greenfeld&lt;/a&gt; for &lt;a href="https://x.com/audreyfeldroy/status/1964565105599009078"&gt;pointing this out&lt;/a&gt;.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-09-env-files-with-uv-run"/>
    <summary>Replacing python-dotenv with uv</summary>
    <category term="TIL"/>
    <category term="python"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-09-28T22:44:18.296914+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2025-11-default-code-block-languages-for-mkdocs</id>
    <title>TIL: Default code block languages for mkdocs</title>
    <updated>2025-11-22T12:08:34.618632+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;When using &lt;a href="https://squidfunk.github.io/mkdocs-material/"&gt;Mkdocs with Material&lt;/a&gt;, you can set default languages for code blocks in your &lt;code&gt;mkdocs.yml&lt;/code&gt; configuration file. This is particularly useful for inline code examples that may not have explicit language tags.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nt"&gt;markdown_extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p p-Indicator"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pymdownx.highlight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;default_lang&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l l-Scalar l-Scalar-Plain"&gt;python&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;You can see what this looks like in practice with Air's API reference for forms here: &lt;a href="https://feldroy.github.io/air/api/forms/"&gt;feldroy.github.io/air/api/forms/&lt;/a&gt;. With this configuration, any code block without a specified language defaults to Python syntax highlighting, making documentation clearer and more consistent.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2025-11-default-code-block-languages-for-mkdocs"/>
    <summary>Really useful for making inline code examples have code highlighting.</summary>
    <category term="TIL"/>
    <category term="python"/>
    <category term="markdown"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2025-11-22T12:08:34.618632+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2026-02-using-pygmentsrenderer-with-mistletoe-as-a-partial</id>
    <title>TIL: Using PygmentsRenderer with mistletoe as a partial</title>
    <updated>2026-02-22T10:09:16.103693+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;For the past 18 months or so on this site, I've been using &lt;a href="https://marked.js.org/"&gt;marked.js&lt;/a&gt; for the web and &lt;a href="https://pypi.org/project/Markdown/"&gt;python-markdown&lt;/a&gt; for the atom feed. I decided not long ago to switch to using &lt;a href="https://github.com/miyuchina/mistletoe"&gt;mistletoe&lt;/a&gt; so there's one consistent source of truth for markdown rendering.&lt;/p&gt;
&lt;p&gt;I also thought that instead of defining a reusable function that both the web and atom feed could use, I would just use Python's &lt;code&gt;functools.partial&lt;/code&gt; to create a new function that has the &lt;code&gt;renderer&lt;/code&gt; argument set to &lt;code&gt;PygmentsRenderer&lt;/code&gt;. Normally I prefer fully defined functions over using &lt;code&gt;partial&lt;/code&gt;, but in this case, since it is for my personal site it felt like a good use of &lt;a href="https://daniel.feldroy.com/posts/python-partials-are-fun"&gt;partial&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="add-the-dependencies"&gt;Add the dependencies&lt;/h2&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;uv&lt;span class="w"&gt; &lt;/span&gt;add&lt;span class="w"&gt; &lt;/span&gt;mistletoe&lt;span class="w"&gt; &lt;/span&gt;pygments
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="create-the-partial-function"&gt;Create the partial function&lt;/h2&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;functools&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mistletoe&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;mistletoe.contrib.pygments_renderer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PygmentsRenderer&lt;/span&gt;

&lt;span class="n"&gt;markdown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;partial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mistletoe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;renderer&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PygmentsRenderer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="use-the-new-function"&gt;Use the new function&lt;/h2&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;markdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;markdown_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h2 id="see-it-in-action"&gt;See it in action&lt;/h2&gt;
&lt;p&gt;Here's the commits where I implemented this change, at least on the web side: &lt;a href="https://github.com/pydanny/daniel.feldroy.com/commit/7cbeec1ff7a5835e0eed882b3dba483554012677"&gt;Using pygments for highlighting of code&lt;/a&gt;. Before I change the atom feed generation to use this, I'll make sure that it renders nicely on planetpython and other feed aggregators. I'll post that in a seperate TIL when I do figure out how to do that.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2026-02-using-pygmentsrenderer-with-mistletoe-as-a-partial"/>
    <summary>Another part of the process of switching from marked.js and python-markdown to just using mistletoe.</summary>
    <category term="TIL"/>
    <category term="python"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2026-02-22T10:09:16.103693+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2026-03-checkovs-gun</id>
    <title>TIL: Checkov's Gun</title>
    <updated>2026-03-09T03:12:43.043418+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Chekhov%27s_gun"&gt;Chekhov's gun&lt;/a&gt; is a storytelling rule that says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you show something important early in a story, it should matter later.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've used this technique for years but didn't know it had a name until today. It's similar to foreshadowing, and objectively just a synonym of that word. My personal interpretation is that it has to do with specific concrete objects or behaviors rather.&lt;/p&gt;
&lt;p&gt;One of my favorite uses of Checkov's Gun is in early cinema as well as some Hong Kong action films where the camera will show a background object early in a dramatic scene. That background object will then be used in some way later in the action. Buster Keaton, Jackie Chan, and Sammo Hung are the grandmasters of this technique.&lt;/p&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2026-03-checkovs-gun"/>
    <summary>A powerful technique used in storytelling.</summary>
    <category term="TIL"/>
    <category term="book"/>
    <category term="writing"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2026-03-09T03:12:43.043418+00:00</published>
  </entry>
  <entry>
    <id>https://daniel.feldroy.com/posts/til-2026-04-improving-mucss-readability-on-mobile</id>
    <title>TIL: Improving µCSS readability on mobile</title>
    <updated>2026-04-06T02:45:46.176600+00:00</updated>
    <author>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </author>
    <content type="html">&lt;p&gt;With &lt;a href="https://mucss.org"&gt;µCSS&lt;/a&gt; on mobile, the typography can be hard to read, and media queries appear to be the right approach to fixing it. Adding this bit of CSS below makes text more easy to read on mobile devices. I'm now using this on both this site and my &lt;a href="https://grimdaniel.com/"&gt;author site&lt;/a&gt;.&lt;/p&gt;
&lt;div class="codehilite"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nt"&gt;max-width&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;768px&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;h1&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;h2&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;h3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;line-height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.25&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;font-size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="kt"&gt;em&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;overflow-x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;auto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content>
    <link href="https://daniel.feldroy.com/posts/til-2026-04-improving-mucss-readability-on-mobile"/>
    <summary>How to get an otherwise responsive framework to look good on mobile devices.</summary>
    <category term="TIL"/>
    <contributor>
      <name>Daniel Roy Greenfeld</name>
      <email>daniel@feldroy.com</email>
    </contributor>
    <published>2026-04-06T02:45:46.176600+00:00</published>
  </entry>
</feed>
