<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Blog on Hugo van Kemenade</title><link>https://hugovk.dev/blog/</link><description>Recent content in Blog on Hugo van Kemenade</description><generator>Hugo</generator><language>en</language><copyright>&lt;a href="https://creativecommons.org/licenses/by-sa/4.0/"&gt;{{&lt; icon "cc" &gt;}}&amp;hairsp;{{&lt; icon "cc-by" &gt;}}&amp;hairsp;{{&lt; icon "cc-sa" &gt;}}&lt;/a&gt;&amp;nbsp;Hugo van Kemenade&lt;br&gt;&lt;p class="text-xs text-neutral-500 dark:text-neutral-400"&gt;Powered by &lt;a class="hover:underline hover:decoration-primary-400 hover:text-primary-500" href="https://gohugo.io/" target="_blank" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; &amp;amp; &lt;a class="hover:underline hover:decoration-primary-400 hover:text-primary-500" href="https://github.com/jpanther/congo" target="_blank" rel="noopener noreferrer"&gt;Congo&lt;/a&gt; &amp;amp; &lt;a class="hover:underline hover:decoration-primary-400 hover:text-primary-500" href="https://hugovk.dev" target="_blank" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt;&lt;/p&gt;</copyright><lastBuildDate>Wed, 18 Feb 2026 13:35:25 +0000</lastBuildDate><atom:link href="https://hugovk.dev/blog/index.xml" rel="self" type="application/rss+xml"/><item><title>A CLI to fight GitHub spam</title><link>https://hugovk.dev/blog/2026/gh-triage/</link><pubDate>Wed, 18 Feb 2026 13:35:25 +0000</pubDate><guid>https://hugovk.dev/blog/2026/gh-triage/</guid><description>&lt;h2 id="gh-triage-spam" class="relative group"&gt;&lt;code&gt;gh triage spam&lt;/code&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#gh-triage-spam" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://github.com/python/cpython/issues?q=sort%3Aupdated-desc%20state%3Aclosed%20label%3Ainvalid"&gt;We&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/issues/144900"&gt;get&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/issues/144899"&gt;a&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/pull/144806"&gt;lot&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/issues/144671"&gt;of&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/issues/144654"&gt;spam&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/issues/144468"&gt;in&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/pull/144352"&gt;the&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/issues/144344"&gt;CPython&lt;/a&gt;
&lt;a href="https://github.com/python/cpython/issues/144296"&gt;project&lt;/a&gt;&lt;a href="https://github.com/python/cpython/issues/144275"&gt;.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A lot of it isn&amp;rsquo;t even &lt;a href="https://en.wiktionary.org/wiki/AI_slop#English"&gt;slop&lt;/a&gt;, but
mostly worthless &amp;ldquo;nothing&amp;rdquo; issues and PRs that barely fill in the issue template, or add
a line of nonsense to some arbitrary file.&lt;/p&gt;
&lt;p&gt;They&amp;rsquo;re often from new accounts with usernames like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;za9066559-wq&lt;/li&gt;
&lt;li&gt;quanghuynh10111-png&lt;/li&gt;
&lt;li&gt;riffocristobal579-cmd&lt;/li&gt;
&lt;li&gt;sajjad5giot&lt;/li&gt;
&lt;li&gt;satyamchoudhary1430-boop&lt;/li&gt;
&lt;li&gt;SilaMey&lt;/li&gt;
&lt;li&gt;standaell1234-maker&lt;/li&gt;
&lt;li&gt;eedamhmd2005-ui&lt;/li&gt;
&lt;li&gt;ksdmyanmar-lighter&lt;/li&gt;
&lt;li&gt;experments-studios&lt;/li&gt;
&lt;li&gt;madurangarathanayaka5-art&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A new issue from a username following the pattern &lt;code&gt;nameNNNN-short_suffix&lt;/code&gt; is a dead
giveaway. I think they&amp;rsquo;re trying to farm &amp;ldquo;realistic&amp;rdquo; accounts: open a PR, open an issue,
comment on something, make a fake review.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s easy but tedious to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;close the PR/issue as not planned&lt;/li&gt;
&lt;li&gt;retitle to &amp;ldquo;spam&amp;rdquo;&lt;/li&gt;
&lt;li&gt;apply the &amp;ldquo;invalid&amp;rdquo; label&lt;/li&gt;
&lt;li&gt;remove other labels&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I use the GitHub CLI &lt;code&gt;gh&lt;/code&gt; a lot (for example, &lt;code&gt;gh co NNN&lt;/code&gt; to check out a PR locally),
and it&amp;rsquo;s straightforward to write your own Python-based extensions, so I wrote
&lt;a href="https://github.com/hugovk/gh-triage"&gt;&lt;code&gt;gh triage&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Install:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; gh extension install hugovk/gh-triage
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Cloning into &amp;#39;/Users/hugo/.local/share/gh/extensions/gh-triage&amp;#39;...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;remote: Enumerating objects: 15, done.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;remote: Counting objects: 100% (15/15), done.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;remote: Compressing objects: 100% (13/13), done.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;remote: Total 15 (delta 5), reused 12 (delta 2), pack-reused 0 (from 0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Receiving objects: 100% (15/15), 5.09 KiB | 5.09 MiB/s, done.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Resolving deltas: 100% (5/5), done.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✓ Installed extension hugovk/gh-triage
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then run like &lt;code&gt;gh triage spam &amp;lt;issue-or-pr-number-or-url&amp;gt;&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; gh triage spam https://github.com/python/cpython/issues/144900
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✅ Removed labels: type-bug
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✅ Added labels: invalid
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✅ Changed title: spam
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;✅ Closed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This can be used for any repo that you have permissions for: it applies the &amp;ldquo;invalid&amp;rdquo; or
&amp;ldquo;spam&amp;rdquo; labels, but only if they exist in the repo.&lt;/p&gt;
&lt;p&gt;Next step: perhaps it could print out the URL to make it easy to report the account to
GitHub (usually for &amp;ldquo;Spam or inauthentic Activity&amp;rdquo;).&lt;/p&gt;
&lt;h2 id="gh-triage-unassign" class="relative group"&gt;&lt;code&gt;gh triage unassign&lt;/code&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#gh-triage-unassign" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Not spam, but another triage helper.&lt;/p&gt;
&lt;p&gt;A less common occurrence is a rebase or merge from &lt;code&gt;main&lt;/code&gt; or change of PR base branch
that ends up bringing in lots of code changes. This often assigns the PR to dozens of
people via
&lt;a href="https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners"&gt;&lt;code&gt;CODEOWNERS&lt;/code&gt;&lt;/a&gt;,
for example:
&lt;a href="https://github.com/python/cpython/pull/142564#event-21644515910"&gt;python/cpython#142564&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Everyone&amp;rsquo;s already been pinged and subscribed to the PR, so it&amp;rsquo;s too late to help that,
but we can automate unassigning them all so at least the PR is not in their &amp;ldquo;assigned
to&amp;rdquo; list.&lt;/p&gt;
&lt;p&gt;Run &lt;code&gt;gh triage unassign &amp;lt;issue-or-pr-number-or-url&amp;gt;&lt;/code&gt; to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;remove all assignees (issues and PRs)&lt;/li&gt;
&lt;li&gt;remove all requested reviewers (PRs only)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gh triage unassign &lt;span class="m"&gt;142564&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hugovk/gh-triage"&gt;&lt;code&gt;gh triage&lt;/code&gt; homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Adam Johnson&amp;rsquo;s
&lt;a href="https://adamj.eu/tech/2025/11/24/github-top-gh-cli-commands/"&gt;top &lt;code&gt;gh&lt;/code&gt; commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&lt;a href="https://archive.org/details/ottoofsilverhand00pylerich/page/55/mode/1up"&gt;&lt;i&gt;Otto of the Silver Hand&lt;/i&gt; written and illustrated by William Pyle&lt;/a&gt;,
originally published 1888, from the
&lt;a href="https://archive.org/details/university_of_california_libraries"&gt;University of California Libraries&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Speeding up Pillow's open and save</title><link>https://hugovk.dev/blog/2026/faster-pillow/</link><pubDate>Wed, 28 Jan 2026 10:29:04 +0000</pubDate><guid>https://hugovk.dev/blog/2026/faster-pillow/</guid><description>&lt;h2 id="tachyon" class="relative group"&gt;Tachyon &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#tachyon" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I tried out
&lt;a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-sampling-profiler"&gt;Tachyon&lt;/a&gt;,
the new &amp;ldquo;high-frequency statistical sampling profiler&amp;rdquo; coming in
&lt;a href="https://www.python.org/downloads/latest/python3.15/"&gt;Python 3.15&lt;/a&gt;, to see if we can
speed up the Pillow imaging library. I started with a simple script to open an image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Tests/images/hopper.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then ran:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.15 -m profiling.sampling run --flamegraph /tmp/1.py png
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Captured 35 samples in 0.04 seconds
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Sample rate: 1,000.00 samples/sec
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Error rate: 25.71
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Flamegraph data: 1 root functions, total samples: 26, 169 unique strings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Flamegraph saved to: flamegraph_97927.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which generates this flame graph:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 
 
 
 
 
 
 
 
 
 
 
 
 &lt;picture class="mx-auto my-0 rounded-md" &gt;
 &lt;img
 src="https://hugovk.dev/blog/2026/faster-pillow/flamegraph-png.svg"
 width="1158"
 height="620"
 class="mx-auto my-0 rounded-md"
 alt="Flame graph for opening a PNG with Pillow"
 loading="lazy" decoding="async"
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The whole thing took 40 milliseconds, with half in &lt;code&gt;Image.py&lt;/code&gt;&amp;rsquo;s &lt;code&gt;open()&lt;/code&gt;. If you visit
the &lt;a href="flamegraph_97927.html"&gt;interactive HTML page&lt;/a&gt; we can see &lt;code&gt;open()&lt;/code&gt; calls
&lt;code&gt;preinit()&lt;/code&gt;, which in turn imports &lt;code&gt;GifImagePlugin&lt;/code&gt;, &lt;code&gt;BmpImagePlugin&lt;/code&gt;, &lt;code&gt;PngImagePlugin&lt;/code&gt;
and &lt;code&gt;JpegImagePlugin&lt;/code&gt; (hover over the &lt;code&gt;&amp;lt;module&amp;gt;&lt;/code&gt; boxes to see them).&lt;/p&gt;
&lt;p&gt;Do we really need to import all those plugins when we&amp;rsquo;re only interested in PNG?&lt;/p&gt;
&lt;p&gt;Okay, let&amp;rsquo;s try another kind of image:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.15 -m profiling.sampling run --flamegraph /tmp/1.py webp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Captured 59 samples in 0.06 seconds
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Sample rate: 1,000.00 samples/sec
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Error rate: 22.03
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Flamegraph data: 1 root functions, total samples: 46, 256 unique strings
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Flamegraph saved to: flamegraph_98028.html
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;





&lt;figure&gt;
 
 








 
 
 
 
 
 
 
 
 
 
 
 
 
 &lt;picture class="mx-auto my-0 rounded-md" &gt;
 &lt;img
 src="https://hugovk.dev/blog/2026/faster-pillow/flamegraph-webp.svg"
 width="1158"
 height="620"
 class="mx-auto my-0 rounded-md"
 alt="Flame graph for opening a WebP with Pillow"
 loading="lazy" decoding="async"
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Hmm, 60 milliseconds with 80% in &lt;code&gt;open()&lt;/code&gt; and most of that in &lt;code&gt;init()&lt;/code&gt;. The
&lt;a href="flamegraph_98028.html"&gt;HTML page&lt;/a&gt; shows it imports &lt;code&gt;AvifImagePlugin&lt;/code&gt;, &lt;code&gt;PdfImagePlugin&lt;/code&gt;,
&lt;code&gt;WebpImagePlugin&lt;/code&gt;, &lt;code&gt;DcxImagePlugin&lt;/code&gt;, &lt;code&gt;DdsImagePlugin&lt;/code&gt; and &lt;code&gt;PalmImagePlugin&lt;/code&gt;. We also
have &lt;code&gt;preinit&lt;/code&gt; importing &lt;code&gt;GifImagePlugin&lt;/code&gt;, &lt;code&gt;BmpImagePlugin&lt;/code&gt; and &lt;code&gt;PngImagePlugin&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Again, why import &lt;em&gt;even more&lt;/em&gt; plugins when we only care about WebP?&lt;/p&gt;
&lt;h2 id="loading-all-the-plugins" class="relative group"&gt;Loading all the plugins? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#loading-all-the-plugins" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;That&amp;rsquo;s enough profiling, let&amp;rsquo;s look at the code.&lt;/p&gt;
&lt;p&gt;When
&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/Image.py#L3525"&gt;&lt;code&gt;open()&lt;/code&gt;&lt;/a&gt;ing
or
&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/Image.py#L2536"&gt;&lt;code&gt;save()&lt;/code&gt;&lt;/a&gt;ing
an image, if Pillow isn&amp;rsquo;t yet initialised, we call a
&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/Image.py#L327-L369"&gt;&lt;code&gt;preinit()&lt;/code&gt;&lt;/a&gt;
function. This loads five drivers for five formats by importing their plugins: BMP, GIF,
JPEG, PPM and PNG.&lt;/p&gt;
&lt;p&gt;During import, each plugin
&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/GifImagePlugin.py#L1204-L1211"&gt;registers&lt;/a&gt;
its file extensions, MIME types and some methods used for opening and saving.&lt;/p&gt;
&lt;p&gt;Then we check each of these plugins in turn to see if one will accept the image. Most of
Pillow&amp;rsquo;s plugins detect an image by opening the file and checking if the first few bytes
match a magic prefix. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/GifImagePlugin.py#L73-L74"&gt;GIF&lt;/a&gt;
starts with &lt;code&gt;b&amp;quot;GIF87a&amp;quot;&lt;/code&gt; or &lt;code&gt;b&amp;quot;GIF89a&amp;quot;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/PngImagePlugin.py#L66"&gt;PNG&lt;/a&gt;
starts with &lt;code&gt;b&amp;quot;\211PNG\r\n\032\n&amp;quot;&lt;/code&gt;
(&lt;a href="https://www.w3.org/TR/PNG-Rationale.html#R.PNG-file-signature"&gt;reference&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/JpegImagePlugin.py#L334-L336"&gt;JPEG&lt;/a&gt;
starts with &lt;code&gt;b&amp;quot;\xff\xd8\xff&amp;quot;&lt;/code&gt;, where &lt;code&gt;\xff\xd8&lt;/code&gt; means &amp;ldquo;Start of Image&amp;rdquo;, and &lt;code&gt;\xff&lt;/code&gt; is
the start of the next marker
(&lt;a href="https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure"&gt;reference&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If none of these five match, we call
&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/Image.py#L372-L396"&gt;&lt;code&gt;init()&lt;/code&gt;&lt;/a&gt;,
which imports the remaining 42 plugins. We then check each of these for a match.&lt;/p&gt;
&lt;p&gt;This has been the case since at least
&lt;a href="https://github.com/hugovk/pil-svn.effbot.org/blame/master/PIL/Image.py#L290"&gt;PIL 1.1.1&lt;/a&gt;
released in 2000 (this is the oldest version I have to check). There were 33 builtin
plugins then and 47 now.&lt;/p&gt;
&lt;h2 id="lazy-loading" class="relative group"&gt;Lazy loading &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#lazy-loading" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;This is all a bit wasteful if we only need one or two image formats during a program&amp;rsquo;s
lifetime, especially for things like CLIs. Longer running programs may need a few more,
but unlikely all 47.&lt;/p&gt;
&lt;p&gt;A benefit of the plugin system is third parties can
&lt;a href="https://pillow.readthedocs.io/en/stable/handbook/third-party-plugins.html"&gt;create their own plugins&lt;/a&gt;,
but we can be more efficient with our builtins.&lt;/p&gt;
&lt;p&gt;I opened a &lt;a href="https://github.com/python-pillow/Pillow/pull/9398"&gt;PR&lt;/a&gt; to add a mapping of
file extensions to plugins. Before calling &lt;code&gt;preinit()&lt;/code&gt; or &lt;code&gt;init()&lt;/code&gt;, we can instead do a
cheap lookup, which may save us importing, registering, and checking all those plugins.&lt;/p&gt;
&lt;p&gt;Of course, we may have an image without an extension, or with the &amp;ldquo;wrong&amp;rdquo; extension, but
that&amp;rsquo;s fine; I expect it&amp;rsquo;s rare and anyway we&amp;rsquo;ll fall back to the original &lt;code&gt;preinit()&lt;/code&gt;
-&amp;gt; &lt;code&gt;init()&lt;/code&gt; flow.&lt;/p&gt;
&lt;p&gt;After merging the PR, here&amp;rsquo;s a new flame graph for opening PNG
(&lt;a href="flamegraph_27477.html"&gt;HTML page&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 
 
 
 
 
 
 
 
 
 
 
 
 &lt;picture class="mx-auto my-0 rounded-md" &gt;
 &lt;img
 src="https://hugovk.dev/blog/2026/faster-pillow/flamegraph-png2.svg"
 width="1158"
 height="580"
 class="mx-auto my-0 rounded-md"
 alt="Much less compressed flame graph showing less work"
 loading="lazy" decoding="async"
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;And for WebP (&lt;a href="flamegraph_26821.html"&gt;HTML page&lt;/a&gt;):&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 
 
 
 
 
 
 
 
 
 
 
 
 &lt;picture class="mx-auto my-0 rounded-md" &gt;
 &lt;img
 src="https://hugovk.dev/blog/2026/faster-pillow/flamegraph-webp2.svg"
 width="1158"
 height="420"
 class="mx-auto my-0 rounded-md"
 alt="Much less compressed for WebP"
 loading="lazy" decoding="async"
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The flame graphs are scaled to the same width, but there&amp;rsquo;s far fewer boxes meaning
there&amp;rsquo;s much less work now. We&amp;rsquo;re down from 40 and 60 milliseconds to 20 and 20
milliseconds.&lt;/p&gt;
&lt;p&gt;The PR has a bunch of benchmarks which show opening a PNG (that previously loaded five
plugins) is now 2.6 times faster. Opening a WebP (that previously loaded all 47
plugins), is now 14 times faster. Similarly, Saving PNG is improved by 2.2 times and
WebP by 7.9 times. Success! This will be in Pillow 12.2.0.&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Henry Schreiner on
&lt;a href="https://iscinumpy.dev/post/packaging-faster/"&gt;making packaging faster&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adam Johnson&amp;rsquo;s &lt;a href="https://adamj.eu/tech/2026/01/14/python-introducing-tprof/"&gt;tprof&lt;/a&gt; is
another new tool which is useful for things like this.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Localising xkcd</title><link>https://hugovk.dev/blog/2026/localising-xkcd/</link><pubDate>Sat, 03 Jan 2026 14:06:51 +0000</pubDate><guid>https://hugovk.dev/blog/2026/localising-xkcd/</guid><description>&lt;p&gt;I gave a lightning talk at a bunch of conferences in 2025 about some of the exciting new
things coming in &lt;a href="https://hugovk.dev/blog/2025/and-now/"&gt;Python 3.14&lt;/a&gt;, including
&lt;a href="https://t-strings.help/"&gt;template strings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One thing we can use t-strings for is to prevent SQL injection. The user gives you an
untrusted t-string, and you can sanitise it, before using it in a safer way.&lt;/p&gt;
&lt;p&gt;I illustrated this with &lt;a href="https://xkcd.com/327/"&gt;xkcd 327&lt;/a&gt;, titled &amp;ldquo;Exploits of a Mom&amp;rdquo;,
but commonly known as &amp;ldquo;Little Bobby Tables&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;I localised most of the slides for the PyCon I was at, including this comic. Here they
are!&lt;/p&gt;
&lt;h2 id="pycon-italia" class="relative group"&gt;PyCon Italia &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pycon-italia" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;em&gt;May, Bologna&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_995e2f2e16770508.webp 330w,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_6a9e2dccbb28c515.webp 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_28f444017a20f626.webp 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_e5d7409854dfeb32.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1480"
 height="455"
 class="mx-auto my-0 rounded-md"
 alt="Did you really name your son Roberto&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Piccolo Berto Tables, we call him."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_f37ee9781ddd6c04.png" srcset="https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_1fe19a9843221c5f.png 330w,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_f37ee9781ddd6c04.png 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_983cee40d8a7df8a.png 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_636e648764ba49c0.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="pycon-greece" class="relative group"&gt;PyCon Greece &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pycon-greece" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;em&gt;August, Athens&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_b998c56a680f1740.webp 330w,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_dd21f49cc0d5fcc3.webp 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_a6fa45b89775c954.webp 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_f7a86970f8e419a5.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1480"
 height="455"
 class="mx-auto my-0 rounded-md"
 alt="Did you really name your son Κωνσταντίνος&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Μικρός Ντίνος Tables, we call him."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_d3cd5d4db080600e.png" srcset="https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_864b681980aacf4b.png 330w,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_d3cd5d4db080600e.png 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_e5f987a1869d3d8.png 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_df9734df0a3b8cf2.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="pycon-estonia" class="relative group"&gt;PyCon Estonia &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pycon-estonia" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;em&gt;October, Tallinn&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_66f79b383f76eb90.webp 330w,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_e2f5c29c9147d2c6.webp 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_9ec1ae1dbd122955.webp 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_1f02e918756eeb05.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1480"
 height="456"
 class="mx-auto my-0 rounded-md"
 alt="Did you really name your son Raivo&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Pisikene Tables, we call him."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_c96a282ef803f3a2.png" srcset="https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_18f7b90a95d0855f.png 330w,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_c96a282ef803f3a2.png 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_6d311c9260c8fca5.png 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_c0d9cd8c668d3add.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="pycon-finland" class="relative group"&gt;PyCon Finland &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pycon-finland" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;em&gt;October, Jyväskylä&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_318aa6666404e224.webp 330w,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_b8bbc612292d722f.webp 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_c514fb93cbf48753.webp 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_f846147908bd5e4f.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1480"
 height="456"
 class="mx-auto my-0 rounded-md"
 alt="Did you really name your son Juhani&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Pikku Jussi, we call him."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_daf02ba348f2e3a7.png" srcset="https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_88df9802a4cddf44.png 330w,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_daf02ba348f2e3a7.png 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_d85823f003af379b.png 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_e0daf49d92b129d2.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="pycon-sweden" class="relative group"&gt;PyCon Sweden &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pycon-sweden" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;em&gt;October, Stockholm&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_ed3f2f550a46718.webp 330w,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_cfab5fa00c7f24db.webp 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_c69d817d779ef38e.webp 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_3a53e97ac889d9cb.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1480"
 height="456"
 class="mx-auto my-0 rounded-md"
 alt="Did you really name your son Oskar&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Lille Ogge Bord, we call him."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_d1bc0862c613bdf0.png" srcset="https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_dd6bb05b88dd2994.png 330w,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_d1bc0862c613bdf0.png 660w
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_b169ef5e792c325d.png 1024w
 
 
 ,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_1940d36e84c40c54.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="thanks" class="relative group"&gt;Thanks &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thanks" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Thanks to Randall Munroe for licensing the comic under the
&lt;a href="https://creativecommons.org/licenses/by-nc/2.5/"&gt;Creative Commons Attribution-NonCommercial 2.5 License&lt;/a&gt;.
These adaptations are therefore licensed the same way.&lt;/p&gt;
&lt;p&gt;Finally, here&amp;rsquo;s links for 2026, I recommend them all:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://2026.pycon.it/en"&gt;PyCon Italia, 27-30 May&lt;/a&gt;: the
&lt;a href="https://2026.pycon.it/en/call-for-proposals"&gt;CFP&lt;/a&gt; is open until 6th January&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pycon.ee/"&gt;PyCon Estonia, 8-9 October&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2026.pycon.gr/"&gt;PyCon Greece, 12-13 October&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pycon.se/"&gt;PyCon Sweden, TBA&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://fi.pycon.org/"&gt;PyCon Finland, TBA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Replacing python-dateutil to remove six</title><link>https://hugovk.dev/blog/2025/minus-six/</link><pubDate>Mon, 29 Dec 2025 16:53:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/minus-six/</guid><description>&lt;p&gt;The dateutil library is a popular and powerful Python library for dealing with dates and
times.&lt;/p&gt;
&lt;p&gt;However, it still supports Python 2.7 by
&lt;a href="https://sethmlarson.dev/winning-a-bet-about-six-the-python-2-compatibility-shim"&gt;depending on the six compatibility shim&lt;/a&gt;,
and I&amp;rsquo;d prefer not to install for Python 3.10 and higher.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how I replaced three uses of its
&lt;a href="https://dateutil.readthedocs.io/en/stable/relativedelta.html"&gt;&lt;code&gt;relativedelta&lt;/code&gt;&lt;/a&gt; in a
couple of CLIs that didn&amp;rsquo;t really need to use it.&lt;/p&gt;
&lt;h2 id="one" class="relative group"&gt;One &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#one" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://github.com/hugovk/norwegianblue/pull/267"&gt;norwegianblue&lt;/a&gt; was using it to
calculate six months from now:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;dt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dateutil.relativedelta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;relativedelta&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.datetime(2025, 12, 29, 15, 59, 44, 518240, tzinfo=datetime.timezone.utc)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;six_months_from_now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;relativedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="o"&gt;=+&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.datetime(2026, 6, 29, 15, 59, 44, 518240, tzinfo=datetime.timezone.utc)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But we don&amp;rsquo;t need to be so precise here, and 180 days is good enough, using the standard
library&amp;rsquo;s
&lt;a href="https://docs.python.org/3/library/datetime.html#datetime.timedelta"&gt;&lt;code&gt;datetime.timedelta&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;dt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.datetime(2025, 12, 29, 15, 59, 44, 518240, tzinfo=datetime.timezone.utc)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;six_months_from_now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.datetime(2026, 6, 27, 15, 59, 44, 518240, tzinfo=datetime.timezone.utc)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="two" class="relative group"&gt;Two &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#two" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://github.com/hugovk/pypistats/pull/519"&gt;pypistats&lt;/a&gt; was using it get the last day
of a month:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;dt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.date(2025, 12, 1)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;relativedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;relativedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.date(2025, 12, 31)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Instead, we can use the stdlib&amp;rsquo;s
&lt;a href="https://docs.python.org/3/library/calendar.html#calendar.monthrange"&gt;&lt;code&gt;calendar.monthrange&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;calendar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;dt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;last_day&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calendar&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;monthrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 31&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_day&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.date(2025, 12, 31)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="three" class="relative group"&gt;Three &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#three" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Finally, to get last month as a yyyy-mm string:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;dt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;dateutil.relativedelta&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;relativedelta&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.date(2025, 12, 29)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;relativedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;months&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.date(2025, 11, 29)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#39;2025-11&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;datetime&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;dt&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;today&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# datetime.date(2025, 12, 29)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;today&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# 2025, 11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;year&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;02d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# &amp;#39;2025-11&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Goodbye six, and we also get slightly quicker install, import and run times.&lt;/p&gt;
&lt;h2 id="bonus" class="relative group"&gt;Bonus &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#bonus" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I recommend
&lt;a href="https://adamj.eu/tech/2019/09/12/how-i-import-pythons-datetime-module/"&gt;Adam Johnson&amp;rsquo;s tip&lt;/a&gt;
to &lt;code&gt;import datetime as dt&lt;/code&gt; to avoid the ambiguity of which &lt;code&gt;datetime&lt;/code&gt; is the module and
which is the class.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&lt;a href="https://archive.org/details/lfaaustria0001/LFA_Austria_0001_016.jpg"&gt;Ver Sacrum calendar&lt;/a&gt;
by &lt;a href="https://www.museumoo.com/Libri/Ver-Sacrum-Kalender"&gt;Alfred Roller&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;</description></item><item><title>And now for something completely different</title><link>https://hugovk.dev/blog/2025/and-now/</link><pubDate>Tue, 23 Dec 2025 13:03:33 +0000</pubDate><guid>https://hugovk.dev/blog/2025/and-now/</guid><description>&lt;p&gt;Starting in 2019, Python &lt;a href="https://www.python.org/downloads/release/python-380/"&gt;3.8&lt;/a&gt; and
&lt;a href="https://www.python.org/downloads/release/python-390/"&gt;3.9&lt;/a&gt; release manager Łukasz Langa
added a new section to the release notes called &amp;ldquo;And now for something completely
different&amp;rdquo; with a sketch transcript from Monty Python.&lt;/p&gt;
&lt;p&gt;For Python
&lt;a href="https://discuss.python.org/t/python-3-10-0-is-now-available/10955?u=hugovk#p-39262-and-now-for-something-completely-different-4"&gt;3.10&lt;/a&gt;
and
&lt;a href="https://discuss.python.org/t/python-3-11-0-final-is-now-available/20291?u=hugovk#p-71754-and-now-for-something-completely-different-6"&gt;3.11&lt;/a&gt;,
the next release manager Pablo Galindo Salgado continued the section but included
astrophysics facts.&lt;/p&gt;
&lt;p&gt;For Python
&lt;a href="https://discuss.python.org/t/python-3-12-0-final-is-here/35186?u=hugovk#p-118859-and-now-for-something-completely-different-7"&gt;3.12&lt;/a&gt;,
the next RM Thomas Wouters shared poems (and took a break for 3.13).&lt;/p&gt;
&lt;p&gt;And for Python 3.14, I&amp;rsquo;m doing all things &lt;em&gt;π&lt;/em&gt;, pie and [mag]pie.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a collection of my different things for the first year (and a bit) of Python
3.14.&lt;/p&gt;
&lt;h2 id="alpha-1" class="relative group"&gt;alpha 1 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#alpha-1" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-1/68039?u=hugovk"&gt;2024-10-15&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;π&lt;/em&gt; (or pi) is a mathematical constant, approximately 3.14, for the ratio of a circle’s
circumference to its diameter. It is an irrational number, which means it cannot be
written as a simple fraction of two integers. When written as a decimal, its digits go
on forever without ever repeating a pattern.&lt;/p&gt;
&lt;p&gt;Here’s 76 digits of &lt;em&gt;π&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;3.141592653589793238462643383279502884197169399375105820974944592307816406286&lt;/p&gt;
&lt;p&gt;Piphilology is the creation of mnemonics to help remember digits of &lt;em&gt;π&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In a pi-poem, or “piem”, the number of letters in a word equal the corresponding digit.
This covers 9 digits, 3.14159265:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;How I wish I could recollect pi easily today!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One of the most well-known covers 15 digits, 3.14159265358979:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;How I want a drink, alcoholic of course, after the heavy chapters involving quantum
mechanics!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here’s a 35-word piem in the shape of a circle, 3.1415926535897932384626433832795728:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;It’s a fact&lt;/em&gt; &lt;em&gt;A ratio immutable&lt;/em&gt; &lt;em&gt;Of circle round and width,&lt;/em&gt; &lt;em&gt;Produces geometry’s
deepest conundrum.&lt;/em&gt; &lt;em&gt;For as the numerals stay random,&lt;/em&gt; &lt;em&gt;No repeat lets out its
presence,&lt;/em&gt; &lt;em&gt;Yet it forever stretches forth.&lt;/em&gt; &lt;em&gt;Nothing to eternity.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Guinness World Record for memorising the most digits is held by Rajveer Meena, who
recited 70,000 digits blindfold in 2015. The unofficial record is held by Akira
Haraguchi who recited 100,000 digits in 2006.&lt;/p&gt;
&lt;h2 id="alpha-2" class="relative group"&gt;alpha 2 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#alpha-2" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-2/71711?u=hugovk"&gt;2024-11-19&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ludolph van Ceulen (1540-1610) was a fencing and mathematics teacher in Leiden,
Netherlands, and spent around 25 years calculating &lt;em&gt;π&lt;/em&gt; (or pi), using essentially the
same methods Archimedes employed some seventeen hundred years earlier.&lt;/p&gt;
&lt;p&gt;Archimedes estimated &lt;em&gt;π&lt;/em&gt; by calculating the circumferences of polygons that fit just
inside and outside of a circle, reasoning the circumference of the circle lies between
these two values. Archimedes went up to polygons with 96 sides, for a value between
3.1408 and 3.1428, which is accurate to two decimal places.&lt;/p&gt;
&lt;p&gt;Van Ceulen used a polygon with half a billion sides. He published a 20-decimal value in
his 1596 book
&lt;a href="https://old.maa.org/press/periodicals/convergence/mathematical-treasure-van-ceulen-s-vanden-circkel"&gt;&lt;em&gt;Vanden Circkel&lt;/em&gt;&lt;/a&gt;
(&lt;a href="https://www.lindahall.org/about/news/scientist-of-the-day/ludolph-van-ceulen/"&gt;“On the Circle”&lt;/a&gt;),
and later expanded it to 35 decimals:&lt;/p&gt;
&lt;p&gt;3.14159265358979323846264338327950288&lt;/p&gt;
&lt;p&gt;Van Ceulen’s 20 digits is more than enough precision for any conceivable practical
purpose. For example, even if a printed circle was perfect down to the atomic scale, the
thermal vibrations of the molecules of ink would make most of those digits physically
meaningless. NASA Jet Propulsion Laboratory’s highest accuracy calculations, for
interplanetary navigation, uses 15 decimals: 3.141592653589793.&lt;/p&gt;
&lt;p&gt;At Van Ceulen’s request, his upper and lower bounds for &lt;em&gt;π&lt;/em&gt; were engraved on his
&lt;a href="https://www.atlasobscura.com/places/ludolph-van-ceulen-memorial-leiden"&gt;tombstone in Leiden&lt;/a&gt;.
The tombstone was eventually lost but restored in 2000. In the Netherlands and Germany,
π is sometimes referred to as the “Ludolphine number”, after Van Ceulen.&lt;/p&gt;
&lt;h2 id="alpha-3" class="relative group"&gt;alpha 3 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#alpha-3" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-3/74542?u=hugovk"&gt;2024-12-17&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A mince pie is a small, round covered tart filled with “mincemeat”, usually eaten during
the Christmas season – the UK consumes some 800 million each Christmas. Mincemeat is a
mixture of things like apple, dried fruits, candied peel and spices, and originally
would have contained meat chopped small, but rarely nowadays. They are often served warm
with brandy butter.&lt;/p&gt;
&lt;p&gt;According to the Oxford English Dictionary, the earliest mention of Christmas mince pies
is by Thomas Dekker, writing in the aftermath of the
&lt;a href="https://en.wikipedia.org/wiki/1603_London_plague"&gt;1603 London plague&lt;/a&gt;, in
&lt;a href="https://archive.org/details/bim_early-english-books-1475-1640_newes-from-graues-end-s_gravesend_1604/page/n10/mode/1up?q=mince&amp;#43;pyes"&gt;&lt;em&gt;Newes from Graues-end: Sent to Nobody&lt;/em&gt;&lt;/a&gt;
(1604):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ten thousand in London swore to feast their neighbors with nothing but plum-porredge,
and mince-pyes all Christmas.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Here’s a meaty recipe from
&lt;a href="https://www.google.com/books/edition/Rare_and_Excellent_Receipts/PZfbqUrHykwC?hl=en&amp;amp;gbpv=1&amp;amp;dq=%22mince&amp;#43;pies%22&amp;amp;pg=PA9&amp;amp;printsec=frontcover"&gt;&lt;em&gt;Rare and Excellent Receipts, Experienc’d and Taught by Mrs Mary Tillinghast and now Printed for the Use of her Scholars Only&lt;/em&gt;&lt;/a&gt;
(1678):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;XV. &lt;em&gt;How to make Mince-pies.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;To every pound of Meat, take two pound of beef Suet, a pound of Corrants, and a
quarter of an Ounce of Cinnamon, one Nutmeg, a little beaten Mace, some beaten Colves,
a little Sack &amp;amp; Rose-water, two large Pippins, some Orange and Lemon peel cut very
thin, and shred very small, a few beaten Carraway-seeds, if you love them the Juyce of
half a Lemon squez’d into this quantity of meat; for Sugar, sweeten it to your relish;
then mix all these together and fill your Pie. The best meat for Pies is
Neats-Tongues, or a leg of Veal; you may make them of a leg of Mutton if you please;
the meat must be parboyl’d if you do not spend it presently; but if it be for present
use, you may do it raw, and the Pies will be the better.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="alpha-4" class="relative group"&gt;alpha 4 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#alpha-4" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-4/77112?u=hugovk"&gt;2025-01-14&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In Python, you can use Greek letters as constants. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pi&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;π&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;circumference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;π&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;radius&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;circumference&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;6378.137&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# 40075.016685578485&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="alpha-5" class="relative group"&gt;alpha 5 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#alpha-5" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-5/80364?u=hugovk"&gt;2025-02-11&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;2025-01-29 marked the start of a new lunar year, the Year of the Snake :snake: (and the
Year of Python?).&lt;/p&gt;
&lt;p&gt;For centuries, &lt;em&gt;π&lt;/em&gt; was often approximated as 3 in China. Some time between the years 1
and 5 CE, astronomer, librarian, mathematician and politician Liu Xin (劉歆) calculated
&lt;em&gt;π&lt;/em&gt; as 3.154.&lt;/p&gt;
&lt;p&gt;Around 130 CE, mathematician, astronomer, and geographer Zhang Heng (張衡, 78–139)
compared the celestial circle with the diameter of the earth as 736:232 to get 3.1724.
He also came up with a formula for the ratio between a cube and inscribed sphere as 8:5,
implying the ratio of a square’s area to an inscribed circle is √8:√5. From this, he
calculated &lt;em&gt;π&lt;/em&gt; as √10 (~3.162).&lt;/p&gt;
&lt;p&gt;Third century mathematician Liu Hui (刘徽) came up with an algorithm for calculating &lt;em&gt;π&lt;/em&gt;
iteratively: calculate the area of a polygon inscribed in a circle, then as the number
of sides of the polygon is increased, the area becomes closer to that of the circle,
from which you can approximate &lt;em&gt;π&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This algorithm is similar to the method used by Archimedes in the 3rd century BCE and
Ludolph van Ceulen in the 16th century CE (see
&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-2/71711?u=hugovk"&gt;3.14.0a2 release notes&lt;/a&gt;),
but Archimedes only went up to a 96-sided polygon (96-gon). Liu Hui went up to a 192-gon
to approximate &lt;em&gt;π&lt;/em&gt; as 157/50 (3.14) and later a 3072-gon for 3.14159.&lt;/p&gt;
&lt;p&gt;Liu Hu wrote a commentary on the book The Nine Chapters on the Mathematical Art which
included his &lt;em&gt;π&lt;/em&gt; approximations.&lt;/p&gt;
&lt;p&gt;In the fifth century, astronomer, inventor, mathematician, politician, and writer Zu
Chongzhi (祖沖之, 429–500) used Liu Hui’s algorithm to inscribe a 12,288-gon to compute
&lt;em&gt;π&lt;/em&gt; between 3.1415926 and 3.1415927, correct to seven decimal places. This was more
accurate than Hellenistic calculations and wouldn’t be improved upon for 900 years.&lt;/p&gt;
&lt;p&gt;Happy Year of the Snake!&lt;/p&gt;
&lt;h2 id="alpha-6" class="relative group"&gt;alpha 6 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#alpha-6" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-6/84513?u=hugovk"&gt;2025-03-14&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;March 14 is celebrated as &lt;a href="https://www.exploratorium.edu/pi"&gt;pi day&lt;/a&gt;, because 3.14 is an
approximation of &lt;em&gt;π&lt;/em&gt;. The day is observed by eating pies (savoury and/or sweet) and
celebrating &lt;em&gt;π&lt;/em&gt;. The first pi day was organised by physicist and tinkerer Larry Shaw of
the San Francisco &lt;a href="https://annex.exploratorium.edu/learning_studio/pi/"&gt;Exploratorium&lt;/a&gt;
in 1988. It is also the &lt;a href="https://www.idm314.org/"&gt;International Day of Mathematics&lt;/a&gt; and
Albert Einstein’s birthday. Let’s all eat some pie, recite some &lt;em&gt;π&lt;/em&gt;, install and test
some py, and wish a happy birthday to Albert, Loren and all the other pi day children!&lt;/p&gt;
&lt;h2 id="alpha-7" class="relative group"&gt;alpha 7 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#alpha-7" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0a7-3-13-3-3-12-10-3-11-12-3-10-17-and-3-9-22-are-now-available/87580?u=hugovk"&gt;2025-04-08&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;On Saturday, 5th April, 3.141592653589793 months of the year had elapsed.&lt;/p&gt;
&lt;h2 id="beta-1" class="relative group"&gt;beta 1 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#beta-1" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-beta-1-is-here/91117?u=hugovk"&gt;2025-05-07&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The mathematical constant pi is represented by the Greek letter &lt;em&gt;π&lt;/em&gt; and represents the
ratio of a circle’s circumference to its diameter. The first person to use &lt;em&gt;π&lt;/em&gt; as a
symbol for this ratio was Welsh self-taught mathematician William Jones in 1706. He was
a farmer’s son born in Llanfihangel Tre’r Beirdd on Angelsy (Ynys Môn) in 1675 and only
received a basic education at a local charity school. However, the owner of his parents’
farm noticed his mathematical ability and arranged for him to move to London to work in
a bank.&lt;/p&gt;
&lt;p&gt;By age 20, he served at sea in the Royal Navy, teaching sailors mathematics and helping
with the ship’s navigation. On return to London seven years later, he became a maths
teacher in coffee houses and a private tutor. In 1706, Jones published &lt;em&gt;Synopsis
Palmariorum Matheseos&lt;/em&gt; which used the symbol &lt;em&gt;π&lt;/em&gt; for the ratio of a circle’s
circumference to diameter (hunt for it on pages
&lt;a href="https://archive.org/details/SynopsisPalmariorumMatheseosOrANewIntroductionToTheMathematics/page/n261/mode/1up?view=theater"&gt;243&lt;/a&gt;
and
&lt;a href="https://archive.org/details/SynopsisPalmariorumMatheseosOrANewIntroductionToTheMathematics/page/n283/mode/1up?view=theater"&gt;263&lt;/a&gt;
or
&lt;a href="https://commons.wikimedia.org/wiki/File:Synopsis_Palmariorum_Matheseos_pi.jpg"&gt;here&lt;/a&gt;).
Jones was also the first person to realise &lt;em&gt;π&lt;/em&gt; is an irrational number, meaning it can
be written as decimal number that goes on forever, but cannot be written as a fraction
of two integers.&lt;/p&gt;
&lt;p&gt;But why &lt;em&gt;π&lt;/em&gt;? It’s thought Jones used the Greek letter &lt;em&gt;π&lt;/em&gt; because it’s the first letter
in &lt;em&gt;perimetron&lt;/em&gt; or perimeter. Jones was the first to use &lt;em&gt;π&lt;/em&gt; as our familiar ratio but
wasn’t the first to use it in as part of the ratio. William Oughtred, in his 1631
&lt;em&gt;Clavis Mathematicae&lt;/em&gt; (&lt;em&gt;The Key of Mathematics&lt;/em&gt;), used &lt;em&gt;π/δ&lt;/em&gt; to represent what we now
call pi. His &lt;em&gt;π&lt;/em&gt; was the circumference, not the ratio of circumference to diameter.
James Gregory, in his 1668 &lt;em&gt;Geometriae Pars Universalis&lt;/em&gt; (&lt;em&gt;The Universal Part of
Geometry&lt;/em&gt;) used &lt;em&gt;π/ρ&lt;/em&gt; instead, where &lt;em&gt;ρ&lt;/em&gt; is the radius, making the ratio 6.28… or
&lt;a href="https://www.tauday.com/"&gt;&lt;em&gt;τ&lt;/em&gt;&lt;/a&gt;. After Jones, Leonhard Euler had used &lt;em&gt;π&lt;/em&gt; for 6.28…, and
also &lt;em&gt;p&lt;/em&gt; for 3.14…, before settling on and popularising &lt;em&gt;π&lt;/em&gt; for the famous ratio.&lt;/p&gt;
&lt;h2 id="beta-2" class="relative group"&gt;beta 2 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#beta-2" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-beta-2-is-here/93396?u=hugovk"&gt;2025-05-26&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;In 1897, the State of Indiana almost passed a bill defining &lt;em&gt;π&lt;/em&gt; as 3.2.&lt;/p&gt;
&lt;p&gt;Of course, it’s not that simple.&lt;/p&gt;
&lt;p&gt;Edwin J. Goodwin, M.D., claimed to have come up with a solution to an ancient
geometrical problem called squaring the circle, first proposed in Greek mathematics. It
involves trying to draw a circle and a square with the same area, using only a compass
and a straight edge. It turns out to be impossible because &lt;em&gt;π&lt;/em&gt; is transcendental (and
this had been proved just 13 years earlier by Ferdinand von Lindemann), but Goodwin
fudged things so the value of &lt;em&gt;π&lt;/em&gt; was 3.2 (his writings have included at least nine
different values of &lt;em&gt;π&lt;/em&gt;: including 4, 3.236, 3.232, 3.2325… and even 9.2376…).&lt;/p&gt;
&lt;p&gt;Goodwin had copyrighted his proof and offered it to the State of Indiana to use in their
educational textbooks without paying royalties, provided they endorsed it. And so
Indiana Bill No. 246 was introduced to the House on 18th January 1897. It was not
understood and initially referred to the House Committee on Canals, also called the
Committee on Swamp Lands. They then referred it to the Committee on Education, who duly
recommended on 2nd February that “said bill do pass”. It passed its second reading on
the 5th and the education chair moved that they suspend the constitutional rule that
required bills to be read on three separate days. This passed 72-0, and the bill itself
passed 67-0.&lt;/p&gt;
&lt;p&gt;The bill was referred to the Senate on 10th February, had its first reading on the 11th,
and was referred to the Committee on Temperance, whose chair on the 12th recommended
“that said bill do pass”.&lt;/p&gt;
&lt;p&gt;A mathematics professor,
&lt;a href="https://www.biodiversitylibrary.org/page/14641808#page/455/mode/1up"&gt;Clarence Abiathar Waldo&lt;/a&gt;,
happened to be in the State Capitol on the day the House passed the bill and walked in
during the debate to hear an ex-teacher argue:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The case is perfectly simple. If we pass this bill which establishes a new and correct
value for pi , the author offers to our state without cost the use of his discovery
and its free publication in our school text books, while everyone else must pay him a
royalty.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Waldo ensured the senators were “properly coached”; and on the 12th, during the second
reading, after an unsuccessful attempt to amend the bill it was postponed indefinitely.
But not before the senators had some fun.&lt;/p&gt;
&lt;p&gt;The Indiana News reported on the 13th:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;…the bill was brought up and made fun of. The Senators made bad puns about it,
ridiculed it and laughed over it. The fun lasted half an hour. Senator Hubbell said
that it was not meet for the Senate, which was costing the State $250 a day, to waste
its time in such frivolity. He said that in reading the leading newspapers of Chicago
and the East, he found that the Indiana State Legislature had laid itself open to
ridicule by the action already taken on the bill. He thought consideration of such a
proposition was not dignified or worthy of the Senate. He moved the indefinite
postponement of the bill, and the motion carried.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="beta-3" class="relative group"&gt;beta 3 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#beta-3" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-beta-3-is-here/95843?u=hugovk"&gt;2025-06-17&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you’re heading out to sea, remember the
&lt;a href="https://xkcd.com/3023/"&gt;Maritime Approximation&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;π&lt;/em&gt; mph = &lt;em&gt;e&lt;/em&gt; knots&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="beta-4" class="relative group"&gt;beta 4 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#beta-4" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-beta-4-is-here/98092?u=hugovk"&gt;2025-07-08&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;All this talk of &lt;em&gt;π&lt;/em&gt; and yet some say &lt;em&gt;π&lt;/em&gt; is wrong. &lt;a href="https://www.tauday.com/"&gt;Tau Day&lt;/a&gt;
(June 28th, 6/28 in the US) celebrates &lt;em&gt;τ&lt;/em&gt; as the “true circle constant”, as the ratio
of a circle’s circumference to its radius, &lt;em&gt;C/r&lt;/em&gt; = 6.283185… The
&lt;a href="https://www.tauday.com/tau-manifesto"&gt;Tau Manifesto&lt;/a&gt; declares &lt;em&gt;π&lt;/em&gt; “a confusing and
unnatural choice for the circle constant”, in part because “2&lt;em&gt;π&lt;/em&gt; occurs with astonishing
frequency throughout mathematics”.&lt;/p&gt;
&lt;p&gt;If you wish to embrace &lt;em&gt;τ&lt;/em&gt; the good news is &lt;a href="https://peps.python.org/pep-0628/"&gt;PEP 628&lt;/a&gt;
added &lt;a href="https://docs.python.org/3/library/math.html#math.tau"&gt;&lt;code&gt;math.tau&lt;/code&gt;&lt;/a&gt; to Python 3.6
in 2016:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;When working with radians, it is trivial to convert any given fraction of a circle to
a value in radians in terms of &lt;code&gt;tau&lt;/code&gt;. A quarter circle is &lt;code&gt;tau/4&lt;/code&gt;, a half circle is
&lt;code&gt;tau/2&lt;/code&gt;, seven 25ths is &lt;code&gt;7*tau/25&lt;/code&gt;, etc. In contrast with the equivalent expressions
in terms of &lt;code&gt;pi&lt;/code&gt; (&lt;code&gt;pi/2&lt;/code&gt;, &lt;code&gt;pi&lt;/code&gt;, &lt;code&gt;14*pi/25&lt;/code&gt;), the unnecessary and needlessly confusing
multiplication by two is gone.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="release-candidate-1" class="relative group"&gt;release candidate 1 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#release-candidate-1" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-release-candidate-1-is-go/99754?u=hugovk"&gt;2025-07-22&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Today, 22nd July, is Pi Approximation Day, because 22/7 is a common approximation of &lt;em&gt;π&lt;/em&gt;
and closer to &lt;em&gt;π&lt;/em&gt; than 3.14.&lt;/p&gt;
&lt;p&gt;22/7 is a Diophantine approximation, named after Diophantus of Alexandria (3rd century
CE), which is a way of estimating a real number as a ratio of two integers. 22/7 has
been known since antiquity; Archimedes (3rd century BCE) wrote the first known proof
that 22/7 overestimates &lt;em&gt;π&lt;/em&gt; by comparing 96-sided polygons to the circle it
circumscribes.&lt;/p&gt;
&lt;p&gt;Another approximation is 355/113. In Chinese mathematics, 22/7 and 355/113 are
respectively known as Yuelü (约率; yuēlǜ; “approximate ratio”) and Milü (密率; mìlǜ;
“close ratio”).&lt;/p&gt;
&lt;p&gt;Happy &lt;a href="https://piapproximationday.com/"&gt;Pi Approximation Day&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="release-candidate-2" class="relative group"&gt;release candidate 2 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#release-candidate-2" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0rc2-and-3-13-7-are-go/102403?u=hugovk"&gt;2025-08-14&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The magpie, &lt;em&gt;Pica pica&lt;/em&gt; in Latin, is a black and white bird in the crow family, known
for its chattering call.&lt;/p&gt;
&lt;p&gt;The first-known use in English is from a
&lt;a href="https://archive.org/details/transactionscong5191cong/page/360/mode/2up?q=magpy"&gt;1589 poem&lt;/a&gt;,
where magpie is spelled “magpy” and cuckoo is “cookow”:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Th[e]y fly to wood like breeding hauke, And leave old neighbours loue, They pearch
themselves in syluane lodge, And soare in th’ aire aboue. There : magpy teacheth them to
chat, And cookow soone doth hit them pat.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The name comes from Mag, short for Margery or Margaret (compare robin redbreast, jenny
wren, and its corvid relative jackdaw); and pie, a magpie or other bird with black and
white (or pied) plumage. The sea-pie (1552) is the oystercatcher, the grey pie (1678)
and murdering pie (1688) is the great grey shrike. Others birds include the yellow and
black pie, red-billed pie, wandering tree-pie, and river pie. The rain-pie, wood-pie and
French pie are woodpeckers.&lt;/p&gt;
&lt;p&gt;Pie on its own dates to before 1225, and comes from the Latin name for the bird, &lt;em&gt;pica&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="release-candidate-3" class="relative group"&gt;release candidate 3 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#release-candidate-3" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0rc3-is-go/103815?u=hugovk"&gt;2025-09-18&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;According to Pablo Galindo Salgado at
&lt;a href="https://www.youtube.com/live/uTu4H3cynOI?t=29965s"&gt;PyCon Greece&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There are things that are supercool indeed, like for instance, this is one of the
results that I&amp;rsquo;m more proud about. This equation over here, which you don&amp;rsquo;t need to
understand, you don&amp;rsquo;t need to be scared about, but this equation here tells what is
the maximum time that it takes for a ray of light to fall into a black hole. And as
you can see the math is quite complicated but the answer is quite simple: it&amp;rsquo;s 2π
times the mass of the black hole. So if you normalise by the mass of the black hole,
the answer is 2π. And because there is nothing specific about your election of things
in this formula, this formula is universal. It means it doesn&amp;rsquo;t depend on anything
other than nature itself. Which means that you can use this as a definition of π. This
is a valid alternative definition of the number π. It&amp;rsquo;s literally half the maximum
time it takes to fall into a black hole, which is kind of crazy. So next time someone
asks you what π means you can just drop this thing and impress them quite a lot. Maybe
Hugo could use this information to put it into the release notes of πthon [yes, I
can, thank you!].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="3140-final" class="relative group"&gt;3.14.0 (final) &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#3140-final" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-0-final-is-here/104210?u=hugovk"&gt;2025-10-07&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Edgar Allen Poe died on 7th October 1849.&lt;/p&gt;
&lt;p&gt;As we all recall from
&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-1/68039?u=hugovk#p-199032-and-now-for-something-completely-different-3"&gt;3.14.0a1&lt;/a&gt;,
piphilology is the creation of mnemonics to help memorise the digits of &lt;em&gt;π&lt;/em&gt;, and the
number of letters in each word in a pi-poem (or “piem”) successively correspond to the
digits of &lt;em&gt;π&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In 1995, Mike Keith, an American mathematician and author of constrained writing, retold
Poe’s &lt;em&gt;The Raven&lt;/em&gt; as a 740-word piem. Here’s the first two stanzas of
&lt;a href="http://www.cadaeic.net/naraven.htm"&gt;&lt;em&gt;Near A Raven&lt;/em&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Poe, E. Near a Raven&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Midnights so dreary, tired and weary. Silently pondering volumes extolling all by-now
obsolete lore. During my rather long nap - the weirdest tap! An ominous vibrating
sound disturbing my chamber’s antedoor. “This”, I whispered quietly, “I ignore”.&lt;/p&gt;
&lt;p&gt;Perfectly, the intellect remembers: the ghostly fires, a glittering ember. Inflamed by
lightning’s outbursts, windows cast penumbras upon this floor. Sorrowful, as one
mistreated, unhappy thoughts I heeded: That inimitable lesson in elegance - Lenore -
Is delighting, exciting…nevermore.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="3141" class="relative group"&gt;3.14.1 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#3141" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://discuss.python.org/t/python-3-14-1-is-now-available/105163?u=hugovk"&gt;2025-12-02&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Seki Takakazu (関 孝和; c. March 1642 – December 5, 1708) was a Japanese mathematician
and samurai who laid the foundations of Japanese mathematics, later known as &lt;em&gt;wasan&lt;/em&gt;
(和算, from &lt;em&gt;wa&lt;/em&gt; (“Japanese”) and &lt;em&gt;san&lt;/em&gt; (“calculation”).&lt;/p&gt;
&lt;p&gt;Seki was a contemporary of Isaac Newton and Gottfried Leibniz but worked independently.
He created a new algebraic system, worked on infinitesimal calculus, and is credited
with the discovery of Bernoulli numbers (before Bernoulli’s birth).&lt;/p&gt;
&lt;p&gt;Seki also
&lt;a href="https://dl.ndl.go.jp/en/pid/3508174/1/13"&gt;calculated &lt;em&gt;π&lt;/em&gt; to 11 decimal places&lt;/a&gt; using a
polygon with 131,072 sides inscribed within a circle, using an acceleration method now
known as Aitken’s delta-squared process, which was rediscovered by Alexander Aitken
in 1926.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo: &lt;a href="https://dl.ndl.go.jp/en/pid/3508174/1/13"&gt; A scan of Seki
Takakazu&amp;rsquo;s posthumous &lt;em&gt;Katsuyō Sanpō&lt;/em&gt; (1712) showing calculations of &lt;em&gt;π&lt;/em&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Steering Council results</title><link>https://hugovk.dev/blog/2025/steering-council-results/</link><pubDate>Sat, 13 Dec 2025 14:40:12 +0000</pubDate><guid>https://hugovk.dev/blog/2025/steering-council-results/</guid><description>&lt;p&gt;The &lt;a href="https://peps.python.org/pep-8107/"&gt;Python Steering Council 2026&lt;/a&gt; election
&lt;a href="https://discuss.python.org/t/steering-council-election-results-2026-term/105296/?u=hugovk"&gt;results are in&lt;/a&gt;
and congratulations to the new Python Steering Council!&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://bsky.app/profile/pumpichank.bsky.social/post/3m7vgyr7q5s23"&gt;Barry Warsaw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bsky.app/profile/corona10.bsky.social/post/3m7uuvphz3c2j"&gt;Donghee Na&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bsky.app/profile/pablogsal.com/post/3m7uyldewf22p"&gt;Pablo Galindo Salgado&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://fosstodon.org/@savannah/115712735914446109"&gt;Savannah Ostrowski&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://social.coop/@Yhg1s"&gt;Thomas Wouters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Welcome Savannah for the first time, and thank you to
&lt;a href="https://infosec.exchange/@gpshead"&gt;Greg Smith&lt;/a&gt; and
&lt;a href="https://bsky.app/profile/emilyemorehouse.bsky.social"&gt;Emily Morehouse&lt;/a&gt; for four and
three years&amp;rsquo; service each.&lt;/p&gt;
&lt;p&gt;Three are starting their sixth terms, and four members have been or are
&lt;a href="https://devguide.python.org/versions/"&gt;release managers&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://hugovk.github.io/python-steering-council/"&gt;chart above&lt;/a&gt; only covers the
Steering Council years. Let&amp;rsquo;s also not forget Guido van Rossum&amp;rsquo;s
&lt;a href="https://hugovk.github.io/python-steering-council/bdfl.html"&gt;BDFL years&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_9a6158b7d59b094d.webp 330w,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_5c921cecb895e283.webp 660w
 
 ,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_9575c67d2aa99f59.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_d592046b252fd865.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="2808"
 height="1138"
 class="mx-auto my-0 rounded-md"
 alt="The same chart but including Guido&amp;rsquo;s 28 year stint as BDFL."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_c90773272bc4696e.png" srcset="https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_5e600fea7c74a79b.png 330w,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_c90773272bc4696e.png 660w
 
 ,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_2038c25456619b65.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_c45c4ea893687c45.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;</description></item><item><title>Steering Council election</title><link>https://hugovk.dev/blog/2025/steering-council-election/</link><pubDate>Mon, 08 Dec 2025 19:50:58 +0000</pubDate><guid>https://hugovk.dev/blog/2025/steering-council-election/</guid><description>&lt;div class="flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900"&gt;
 &lt;span class="pe-3 text-primary-400"&gt;
 
 &lt;/span&gt;
 &lt;span class="dark:text-neutral-300"&gt;&lt;strong&gt;🗳️ Update:&lt;/strong&gt; See the &lt;a href="../steering-council-results/"&gt;results&lt;/a&gt;!&lt;/span&gt;
&lt;/div&gt;

&lt;p&gt;The Python Steering Council election is on!&lt;/p&gt;
&lt;p&gt;This year six candidates are running for the five seats. See
&lt;a href="https://peps.python.org/pep-8107/"&gt;PEP 8107&lt;/a&gt; for links to their nomination statements
and &lt;a href="https://peps.python.org/pep-0013/"&gt;PEP 13&lt;/a&gt; for more on Python language governance.&lt;/p&gt;
&lt;p&gt;I made a chart to show when the nominations arrived during the nomination period: six is
fewer than in previous years, but they didn&amp;rsquo;t all wait until the last three days like in
2023 and 2024.&lt;/p&gt;
&lt;p&gt;This year we&amp;rsquo;re also using a new voting system: with
&lt;a href="https://www.starvoting.org/multi_winner"&gt;&amp;ldquo;Multi-winner Bloc STAR&amp;rdquo;&lt;/a&gt; you give between 0-6
stars per candidate.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re in the core team, remember to vote by Friday!&lt;/p&gt;</description></item><item><title>Setting secrets in env vars</title><link>https://hugovk.dev/blog/2025/secrets-in-env-vars/</link><pubDate>Thu, 27 Nov 2025 12:55:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/secrets-in-env-vars/</guid><description>&lt;p&gt;Sometimes you need to set environment variables with secrets, API keys or tokens, but
they can be susceptible to exfiltration by malware, as seen during the
&lt;a href="https://about.gitlab.com/blog/gitlab-discovers-widespread-npm-supply-chain-attack/"&gt;recent Shai-Hulud attack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For publishing to PyPI, it&amp;rsquo;s
&lt;a href="https://blog.pypi.org/posts/2025-11-26-pypi-and-shai-hulud/"&gt;strongly recommended to use Trusted Publishing&lt;/a&gt;
rather than managing long-lived tokens on your machine.&lt;/p&gt;
&lt;p&gt;For other credentials, here&amp;rsquo;s how you can set them as env vars with 1Password CLI and
direnv on macOS.&lt;/p&gt;
&lt;h2 id="1password" class="relative group"&gt;1Password &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#1password" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The 1Password password manager has a
&lt;a href="]%28https://1password.com/downloads/command-line%29"&gt;command-line interface called &lt;code&gt;op&lt;/code&gt;&lt;/a&gt;.
Thanks to Simon Willison for his useful
&lt;a href="https://til.simonwillison.net/macos/1password-terminal"&gt;TIL post&lt;/a&gt; about it.&lt;/p&gt;
&lt;p&gt;First &lt;a href="https://developer.1password.com/docs/cli/get-started/"&gt;install the CLI&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install 1password-cli
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then open 1Password &amp;gt; Settings &amp;gt; Developer &amp;gt; Integrate with 1Password CLI.&lt;/p&gt;
&lt;p&gt;Then you can use the &lt;code&gt;op&lt;/code&gt; command with your password item&amp;rsquo;s name in 1Password:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; op item get &lt;span class="s2"&gt;&amp;#34;My secret item&amp;#34;&lt;/span&gt; --fields password
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[use &amp;#39;op item get xkm4wrtpvnq8hcjd3yfzs2belg --reveal&amp;#39; to reveal]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This obscures the actual password, and gives the item&amp;rsquo;s ID. You can use this ID:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; op item get xkm4wrtpvnq8hcjd3yfzs2belg --fields password
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[use &amp;#39;op item get xkm4wrtpvnq8hcjd3yfzs2belg --reveal&amp;#39; to reveal]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Use &lt;code&gt;--reveal&lt;/code&gt; to see the actual password:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; op item get &lt;span class="s2"&gt;&amp;#34;My secret item&amp;#34;&lt;/span&gt; --fields password --reveal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;my-secret-password
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; op item get xkm4wrtpvnq8hcjd3yfzs2belg --fields password --reveal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;my-secret-password
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Alternatively, use a
&lt;a href="https://developer.1password.com/docs/cli/secret-reference-syntax/"&gt;secret reference&lt;/a&gt;.
Open the item in 1Password, next to the password click ⌄ and select Copy Secret
Reference then &lt;code&gt;op read &lt;/code&gt;it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; op &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;op://MyVault/xkm4wrtpvnq8hcjd3yfzs2belg/password&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;my-secret-password
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The secret reference is made up of the vault name and item ID, and &lt;code&gt;op read&lt;/code&gt; doesn&amp;rsquo;t
need &lt;code&gt;--reveal&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Take your pick which one you prefer.&lt;/p&gt;
&lt;h2 id="direnv" class="relative group"&gt;direnv &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#direnv" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://direnv.net/"&gt;direnv&lt;/a&gt; is a handy shell tool that can load and unload environment
variables depending on your current directory.&lt;/p&gt;
&lt;p&gt;Next, let&amp;rsquo;s set up direnv so that when you &lt;code&gt;cd&lt;/code&gt; into a certain directory, it fetches the
password from 1Password and sets it as an env var. When &lt;code&gt;cd&lt;/code&gt;ing out, the env var is
unloaded.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://direnv.net/docs/installation.html"&gt;Install&lt;/a&gt; direnv:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install direnv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then &lt;a href="https://direnv.net/docs/hook.html"&gt;hook direnv into your shell&lt;/a&gt;. If you use
&lt;a href="https://ohmyz.sh/"&gt;Oh My Zsh&lt;/a&gt;, edit &lt;code&gt;~/.zshrc&lt;/code&gt; and add &lt;code&gt;direnv&lt;/code&gt; to the plugins list:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;plugins=(git gitfast gh you-should-use direnv)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Restart/reload your shell:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;source ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(I first got &amp;ldquo;Warning: direnv not found. Please install direnv and ensure it&amp;rsquo;s in your
PATH before using this plugin&amp;rdquo; so needed to move
&lt;code&gt;eval &amp;quot;$(/opt/homebrew/bin/brew shellenv)&amp;quot;&lt;/code&gt; before &lt;code&gt;source $ZSH/oh-my-zsh.sh&lt;/code&gt;.)&lt;/p&gt;
&lt;p&gt;Now, in your project directory, create a file called &lt;code&gt;.envrc&lt;/code&gt; containing an env var
export that calls &lt;code&gt;op&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# .envrc&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;MY_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;op &lt;span class="nb"&gt;read&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;op://MyVault/xkm4wrtpvnq8hcjd3yfzs2belg/password&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On first run, you need to explicitly allow &lt;code&gt;direnv&lt;/code&gt; to run for this directory. You also
need to do this when editing &lt;code&gt;.envrc&lt;/code&gt; later:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;direnv allow .
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If 1Password is set up with a passkey, it will now prompt to authorise:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; my-project
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;direnv: loading ~/my-project/.envrc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;direnv: export +DEBUG +MY_SECRET
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sometimes it shows a warning because of the 1Password UI prompt, but it&amp;rsquo;s okay:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; my-project
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;direnv: loading ~/my-project/.envrc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;direnv: ([/opt/homebrew/bin/direnv export zsh]) is taking a while to execute. Use CTRL-C to give up.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;direnv: export +DEBUG +MY_SECRET
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then you can access the env var in your scripts:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;-nope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MY_SECRET&lt;/span&gt;&lt;span class="p"&gt;-nope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;my-secret-password
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But when leaving the directory, you can no longer access it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ..
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;direnv: unloading
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;DEBUG&lt;/span&gt;&lt;span class="p"&gt;-nope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;nope
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MY_SECRET&lt;/span&gt;&lt;span class="p"&gt;-nope&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;nope
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="bonus" class="relative group"&gt;Bonus &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#bonus" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;1Password offers
&lt;a href="https://github.com/1Password/for-open-source"&gt;Team accounts to open-source projects&lt;/a&gt; as
a way of giving back to the open source community.&lt;/p&gt;
&lt;h2 id="update" class="relative group"&gt;Update &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#update" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;2026-01-11:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;See how &lt;a href="https://hachyderm.io/@nedbat/115621970327022883"&gt;Ned Batchelder&lt;/a&gt; explicitly
&lt;a href="https://github.com/nedbat/dotfiles/blob/main/dot_config/shellrc/opvars.sh"&gt;sets and unsets env vars&lt;/a&gt;,
and updates the prompt and window title to make it clear they&amp;rsquo;re active.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;See how &lt;a href="https://mstdn.social/@miguelgrinberg/115718649906994917"&gt;Miguel Grinberg&lt;/a&gt;
uses
&lt;a href="https://blog.miguelgrinberg.com/post/how-to-securely-store-secrets-in-environment-variables"&gt;process substitution&lt;/a&gt;
to limit access to secrets from Bash scripts.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&lt;a href="https://archive.org/details/bearings1218951896cycl/page/n1161/mode/2up"&gt;Slaymaker Barry Co. Sprocket Locks&lt;/a&gt;
in
&lt;a href="https://library.si.edu/digital-library/book/bearings"&gt;The Bearings (vol. XII no. 23, 2nd January 1896)&lt;/a&gt;
in the &lt;a href="https://library.si.edu/digital-library/"&gt;Smithsonian Libraries and Archives&lt;/a&gt;,
with &lt;a href="https://library.si.edu/copyright"&gt;no known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Python Core Sprint 2025</title><link>https://hugovk.dev/blog/2025/python-core-sprint/</link><pubDate>Mon, 03 Nov 2025 08:58:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/python-core-sprint/</guid><description>&lt;p&gt;🐍🏃In September, the annual Python Core Sprint was hosted by
&lt;a href="https://developer.arm.com/"&gt;Arm&lt;/a&gt; in Cambridge, UK!&lt;/p&gt;
&lt;p&gt;The plan: put 35 core developers and 13 special guests in a room for a week, and see
what they cook up.&lt;/p&gt;
&lt;h2 id="monday-highlights" class="relative group"&gt;Monday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#monday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;We kicked off the first day with a round of five-word intros (mine: &amp;ldquo;three&amp;rdquo;, &amp;ldquo;dot&amp;rdquo;,
&amp;ldquo;fourteen&amp;rdquo;, &amp;ldquo;release&amp;rdquo;, &amp;ldquo;manager&amp;rdquo;), lots of talks, and lots of discussion about talks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/fidget-spinner"&gt;Ken Jin Ooi&lt;/a&gt; - Building a JIT Community &lt;em&gt;and&lt;/em&gt; Demo
[effect] of new C API&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mastodon.social/@antocuni"&gt;Antonio Cuni&lt;/a&gt; - Tracing JITs on real code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mastodon.social/@brettcannon"&gt;Brett Cannon&lt;/a&gt; - WASI update &lt;em&gt;and&lt;/em&gt; Precompiled
binaries from python.org&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hoodmane"&gt;Hood Chatham&lt;/a&gt; - Upstreaming Pyodide FFI&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloudisland.nz/@freakboy3742"&gt;Russell Keith-Magee&lt;/a&gt; - Managing cross-platform
wheel builds&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python/steering-council"&gt;Steering Council&lt;/a&gt; -
&lt;a href="https://peps.python.org/pep-0793/"&gt;PEP 793&lt;/a&gt; and abi3/abi3t/abi4&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mjp41"&gt;Matthew Parkinson&lt;/a&gt; - Designing Deep Immutability&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did some &lt;del&gt;sprint&lt;/del&gt; spring cleaning of our PyPI projects, dropping support for
&lt;a href="https://devguide.python.org/versions/"&gt;then-&lt;em&gt;almost&lt;/em&gt;-EOL&lt;/a&gt;
&lt;a href="https://peps.python.org/pep-0596/"&gt;Python 3.9&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/python/blurb/pull/68"&gt;python/blurb#68&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python/cherry-picker/pull/164"&gt;python/cherry-picker#164&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python/peps/pull/4587"&gt;python/peps#4587&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python/pyperformance/pull/414"&gt;python/pyperformance#414&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/sphinx-contrib/sphinx-lint/pull/145"&gt;sphinx-contrib/sphinx-lint#145&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And because &lt;a href="https://fosstodon.org/@mariatta"&gt;Mariatta&lt;/a&gt; wasn&amp;rsquo;t with us, here&amp;rsquo;s the
all-important
&lt;a href="https://mariatta.ca/posts/python-core-sprint-2024-day-1/#python-conference-t-shirts"&gt;Python T-shirt census&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.python.org/2016/09/python-core-development-sprint-2016-36/"&gt;Menlo Park 2016 core sprint&lt;/a&gt;
(Greg, Guido)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ep2023.europython.eu/"&gt;EuroPython 2023&lt;/a&gt; (Antonio)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ep2023.europython.eu/"&gt;EuroPython 2024 volunteer&lt;/a&gt; (Hugo)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2016.pycon.ca/en/"&gt;PyCon Canada 2016&lt;/a&gt; (Brett)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://us.pycon.org/"&gt;PyCon US&lt;/a&gt; Pittsburgh Charlas (Thomas)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2025/python-core-sprint-2025/"&gt;Cambridge 2025 core sprint&lt;/a&gt;
(Diego)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/python-core-sprint/room_hu_c930c4d517083afc.webp 330w,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_3ee776f0d12a246d.webp 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_94f726c3a8f4346b.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_9a9b597e39fdb7fb.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="A room of people at desks with monitors. Steve has a mic and is asking a question to Brett who&amp;rsquo;s standing at and presenting."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/python-core-sprint/room_hu_579e4611bc5dae5a.jpg" srcset="https://hugovk.dev/blog/2025/python-core-sprint/room_hu_9a5805e93da89778.jpg 330w,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_579e4611bc5dae5a.jpg 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_1f0d7b651963784f.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_2c4386562c798b7.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;The sprint room&lt;/small&gt;&lt;/center&gt;
&lt;h2 id="tuesday-highlights" class="relative group"&gt;Tuesday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#tuesday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Release day?&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d originally planned to release &lt;a href="https://peps.python.org/pep-0745/"&gt;Python 3.14.0rc3&lt;/a&gt;
on the Tuesday, but the morning was full of presentations, and the afternoon had an
early departure for the social event, so I moved it to Wednesday instead.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://chaos.social/@trallard"&gt;Tania Allard&lt;/a&gt; gave a presentation about the different
types of mentorship and how we can improve, followed by an open discussion.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://infosec.exchange/@gpshead"&gt;Gregory P. Smith&lt;/a&gt; gave a demo on how we can use
tools like Claude with CPython.&lt;/p&gt;
&lt;p&gt;Tania, &lt;a href="https://publicidentity.net/@jezdez"&gt;Jannis Leidel&lt;/a&gt;,
&lt;a href="https://hachyderm.io/@willingc"&gt;Carol Willing&lt;/a&gt; and I discussed the
&lt;a href="https://github.com/psf/user-success-wg"&gt;User Success Workgroup&lt;/a&gt; and we came up with
some ideas on next steps.&lt;/p&gt;
&lt;p&gt;We ended the day with a punting tour on the river Cam and dinner at Jesus College, thank
you, Arm!&lt;/p&gt;
&lt;p&gt;And &lt;a href="https://social.coop/@Yhg1s"&gt;Thomas Wouters&lt;/a&gt; gave a fun session of his
&lt;a href="https://discuss.python.org/t/shall-we-play-a-game/96085"&gt;Feuding Pythonistas game&lt;/a&gt;
(spoiler: people are wrong on the internet).&lt;/p&gt;
&lt;p&gt;Python/tech t-shirt census:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python &lt;del&gt;2&lt;/del&gt;3.7, Menlo Park 201&lt;del&gt;6&lt;/del&gt;7 core sprint (Carl)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2025.pycon.it/en"&gt;PyCon Italia 2025&lt;/a&gt; (Antonio)&lt;/li&gt;
&lt;li&gt;Python 3.14 pie (Hugo)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://why2025.org/"&gt;WHY 2025&lt;/a&gt; (Thomas)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://us.pycon.org/2025/"&gt;PyCon US 2025&lt;/a&gt; (Diego)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pycascades.com/"&gt;PyCascades&lt;/a&gt; (Guido)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ep2022.europython.eu/"&gt;EuroPython 2022&lt;/a&gt; (Mark)&lt;/li&gt;
&lt;li&gt;Pythonista (Jacob)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_61c63533d3d13b1c.webp 330w,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_d25277aa548954ef.webp 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_4e23566483c7ebba.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_5d7a9544815d4711.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="People sitting in a small boat, with a man standing at the back with a long pole in the water, in front of grand Cambridge college"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_62450ce4fe807b64.jpg" srcset="https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_8560c6a444104a22.jpg 330w,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_62450ce4fe807b64.jpg 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_bb4cbf0221fa1551.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_407c364d30c9c790.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Punting on the Cam&lt;/small&gt;&lt;/center&gt;
&lt;h2 id="wednesday-highlights" class="relative group"&gt;Wednesday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#wednesday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Release day?&lt;/p&gt;
&lt;p&gt;The Steering Council asked for an extra day to decide about a possible typing revert
(&lt;a href="https://github.com/python/steering-council/issues/307"&gt;python/steering-council#307&lt;/a&gt;),
so not today.&lt;/p&gt;
&lt;p&gt;Lightning talks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;⚡ &lt;a href="https://github.com/gvanrossum"&gt;Guido van Rossum&lt;/a&gt; on collecting an oral history&lt;/li&gt;
&lt;li&gt;⚡ &lt;a href="https://mastodon.social/@EWDurbin"&gt;Ee Durbin&lt;/a&gt; on modernising the last bits of
infra and bots&lt;/li&gt;
&lt;li&gt;⚡ &lt;a href="https://github.com/zooba"&gt;Steve Dower&lt;/a&gt; demoed the new Windows installer&lt;/li&gt;
&lt;li&gt;⚡ &lt;a href="https://github.com/larryhastings"&gt;Larry Hastings&lt;/a&gt; on a &lt;code&gt;linked_list&lt;/code&gt; date type&lt;/li&gt;
&lt;li&gt;⚡ &lt;a href="https://github.com/aa-turner"&gt;Adam Turner&lt;/a&gt; asked shall we close old issues&lt;/li&gt;
&lt;li&gt;⚡ Greg shared a &lt;a href="https://github.com/python/peps/pull/4592"&gt;draft PEP&lt;/a&gt; for timestamps
on async tracebacks&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Carol, Adam, Thomas, &lt;a href="https://mastodon.social/@encukou"&gt;Petr Viktorin&lt;/a&gt; and I discussed a
number of docs topics.&lt;/p&gt;
&lt;p&gt;I released the
&lt;a href="https://github.com/python/python-docs-theme/releases/tag/2025.9.2"&gt;Python Docs Sphinx Theme &lt;/a&gt;
with more translations.&lt;/p&gt;
&lt;p&gt;We had a Q&amp;amp;A session with the Steering Council, three in-person and two joining
remotely.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fosstodon.org/@Monorepo"&gt;Jacob Coffee&lt;/a&gt; and I looked into upgrading the
&lt;a href="https://blog.python.org/"&gt;Python Insider&lt;/a&gt; and &lt;a href="https://pyfound.blogspot.com/"&gt;PSF&lt;/a&gt;
blogs into something a little more modern.&lt;/p&gt;
&lt;p&gt;T-shirt census:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://2025.pycon.gr/en/"&gt;PyCon Greece 2025&lt;/a&gt; (Hugo)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2025.pycon.kr/"&gt;PyCon Korea 2025&lt;/a&gt; (Donghee)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pyladies.com/"&gt;PyLadies&lt;/a&gt; (Savannah, Petr)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ep2023.europython.eu/"&gt;EuroPython 2024&lt;/a&gt; (Lys)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://2023.pycon.it/en"&gt;PyCon Italia 2023&lt;/a&gt; (Antonio)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2025/python-core-sprint-2025/"&gt;Cambridge 2025 core sprint&lt;/a&gt;
(Guido)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_1bb9c23505c96c7a.webp 330w,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_fa2ba443e179baad.webp 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_b8f25ceb6f62dd61.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_bee77c3731d85ed4.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="Steering Council Q&amp;amp;A: Greg, Pablo and Donghee on stools and Barry and Emily on screen."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_a1a0165efcdec87c.jpg" srcset="https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_5b14da76d06e5026.jpg 330w,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_a1a0165efcdec87c.jpg 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_a9d200015128fa9f.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_f7e2d43aff97bc3.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;The Python Steering Council&lt;/small&gt;&lt;/center&gt;
&lt;h2 id="thursday-highlights" class="relative group"&gt;Thursday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thursday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Release day? Yes!&lt;/p&gt;
&lt;p&gt;The Steering Council
&lt;a href="https://discuss.python.org/t/types-uniontype-was-merged-with-wrong-class-not-even-a-class/102275/30"&gt;decided not to revert&lt;/a&gt;,
so full steam ahead with the
&lt;a href="https://discuss.python.org/t/python-3-14-0rc3-is-go/103815"&gt;release&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fosstodon.org/@savannah"&gt;Savannah Ostrowski&lt;/a&gt;, release manager for 3.16 and
3.17, &lt;a href="https://bsky.app/profile/savannah.dev/post/3lz4ek7aw6224"&gt;shadowed&lt;/a&gt; to see what
the process looks like (not as bad as it looks like in
&lt;a href="https://peps.python.org/pep-0101/"&gt;PEP 101&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Time for a couple of &lt;a href="https://github.com/python/release-tools/pull/280"&gt;quick&lt;/a&gt;
&lt;a href="https://github.com/python/peps/pull/4596"&gt;PRs&lt;/a&gt; and an interview with
&lt;a href="https://bsky.app/profile/pablogsal.com"&gt;Pablo Galindo Salgado&lt;/a&gt; and
&lt;a href="https://mastodon.social/@ambv"&gt;Łukasz Langa&lt;/a&gt; on the
&lt;a href="https://creators.spotify.com/pod/profile/corepy/episodes/Episode-26-1-CPython-Sprint-Week-in-Cambridge-UK--Part-1-e39jabg"&gt;core.py podcast&lt;/a&gt;,
along with 29 others!&lt;/p&gt;
&lt;p&gt;T-census:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://web.archive.org/web/20160419232557/http://ua.pycon.org/"&gt;PyCon UA&lt;/a&gt; 2016
(Łukasz)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2025/python-core-sprint-2025/"&gt;Cambridge 2025 core sprint&lt;/a&gt;
(Donghee)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.readthedocs.com/read-the-docs-2014-stats/"&gt;Read the Docs Budapest 2014&lt;/a&gt;
(Petr)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labs.quansight.org/"&gt;Quansight Labs&lt;/a&gt; &amp;ldquo;Sustaining the future of Open Source&amp;rdquo;
hoodie (Lys)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_7ffa4f2454806231.webp 330w,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_75e766e57bd712fb.webp 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_3acbdca409ecaf7e.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_dd4dcee303b9e3.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="The 3.14 release room, two laptops on a table and the release CI build shown on a screen. The laptop with the &amp;ldquo;365 PARTYGIRL&amp;rdquo; sticker isn&amp;rsquo;t mine."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_eeb16b37a07abe2d.jpg" srcset="https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_8fa2907489714b3f.jpg 330w,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_eeb16b37a07abe2d.jpg 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_3cbba1771ab14a41.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_43224075190693fb.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;The 3.14.0rc3 release in progress&lt;/small&gt;&lt;/center&gt;
&lt;h2 id="friday-highlights" class="relative group"&gt;Friday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#friday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I went to Manchester to attend &lt;a href="https://2025.pyconuk.org/"&gt;PyCon UK&lt;/a&gt;. Some highlights:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=gDvwRpl9erE&amp;amp;list=PLrkpavSsBQZ6bnFa93KWXtMJoBA-a4_f_&amp;amp;index=2"&gt;Python&amp;rsquo;s True Superpower, Hynek Schlawack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=CtrsssksCNU&amp;amp;list=PLrkpavSsBQZ41YpNF6EUDUB5wAwwfbKPV&amp;amp;index=7"&gt;Ada by Emily Holyoake, a rehearsed reading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtube.com/watch?v=ri3Wli6PVVo&amp;amp;list=PLrkpavSsBQZ62noXYqRezmjdE7_CCbT9_&amp;amp;index=2"&gt;Localization and translation of programming languages, Felienne Hermans&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=vrVXgeD2fts&amp;amp;list=PLrkpavSsBQZ62noXYqRezmjdE7_CCbT9_&amp;amp;index=7"&gt;The tale of PEP 765 SyntaxWarning on &lt;code&gt;return&lt;/code&gt; in &lt;code&gt;finally&lt;/code&gt;, Irit Katriel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=sxT2UikIyeQ&amp;amp;list=PLrkpavSsBQZ6MjNYXtWm_YFEcrLkCxN2w&amp;amp;index=3"&gt;Oh no! Your project became really popular! Deb Nicholson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=3MmfY5UIquk&amp;amp;list=PLrkpavSsBQZ6MjNYXtWm_YFEcrLkCxN2w&amp;amp;index=6"&gt;Why &lt;code&gt;len('😶‍🌫️') == 4&lt;/code&gt; and other weird things you should know about strings in Python, Yngve Mardal Moe &amp;amp; Marie Roald&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The conference also included &lt;a href="https://2025.pyconuk.org/sprints/"&gt;sprints&lt;/a&gt;, and Adam and
I ran the CPython sprint. We had a big table full of contributors and a few made their
very first contributions, which is always rewarding for all involved!&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_7705c4b1b0800a65.webp 330w,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_e042b1b5b21fa37e.webp 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_c7712ec7e8ffdc9d.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_cfe6dc5f933df1f9.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="Another roomful of people working at laptops around tables"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_84ef573faee269e3.jpg" srcset="https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_d7260b13c6f04e7f.jpg 330w,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_84ef573faee269e3.jpg 660w
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_4be8f07050ac4965.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_f03d5f12e31eee7e.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;PyCon UK sprint&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;Some numbers for me during the week:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Python release candidates released: 1&lt;/li&gt;
&lt;li&gt;Issues created: 1&lt;/li&gt;
&lt;li&gt;PRs created: 8&lt;/li&gt;
&lt;li&gt;Issues closed: 7&lt;/li&gt;
&lt;li&gt;PRs merged: 28&lt;/li&gt;
&lt;li&gt;PRs closed: 1&lt;/li&gt;
&lt;li&gt;Total issues involved with: 18&lt;/li&gt;
&lt;li&gt;Total PRs involved with: 70&lt;/li&gt;
&lt;li&gt;Repositories affected: 19&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="thank-you" class="relative group"&gt;Thank you &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thank-you" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Huge thanks to &lt;a href="https://bsky.app/profile/did:plc:4vk3ag3wqcgrcrl2ax3xzcxt"&gt;Diego Russo&lt;/a&gt;
and &lt;a href="https://developer.arm.com/"&gt;Arm&lt;/a&gt; for arranging and hosting us. The core sprint is
always a highlight of the year and an incredibly productive week.&lt;/p&gt;
&lt;p&gt;Read writeups by
&lt;a href="https://community.arm.com/arm-community-blogs/b/tools-software-ides-blog/posts/cpython-core-dev-sprint-2025-at-arm-cambridge-the-biggest-one-yet"&gt;Diego&lt;/a&gt;
and
&lt;a href="https://antocuni.eu/2025/09/24/tracing-jits-in-the-real-world--cpython-core-dev-sprint/"&gt;Antonio&lt;/a&gt;,
and I recommend listening to Łukasz and Pablo&amp;rsquo;s core.py podcast for interviews with 18
(&lt;a href="https://creators.spotify.com/pod/profile/corepy/episodes/Episode-26-1-CPython-Sprint-Week-in-Cambridge-UK--Part-1-e39jabg"&gt;part one&lt;/a&gt;)
and 12 sprinters
(&lt;a href="https://creators.spotify.com/pod/profile/corepy/episodes/Episode-26-2-CPython-Sprint-Week-in-Cambridge-UK--Part-2-e3a1hot"&gt;part two&lt;/a&gt;).
They&amp;rsquo;re long, but it&amp;rsquo;s fascinating to hear all the different things everyone is working
on.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;Header photo by Arm&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Three times faster with lazy imports</title><link>https://hugovk.dev/blog/2025/lazy-imports/</link><pubDate>Sun, 19 Oct 2025 16:05:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/lazy-imports/</guid><description>&lt;p&gt;&lt;a href="https://peps.python.org/pep-0810/"&gt;PEP 810&lt;/a&gt; proposes &amp;ldquo;explicit lazy imports&amp;rdquo; for Python
3.15:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Lazy imports defer the loading and execution of a module until the first time the
imported name is used, in contrast to ‘normal’ imports, which eagerly load and execute
a module at the point of the import statement.&lt;/p&gt;
&lt;p&gt;By allowing developers to mark individual imports as lazy with explicit syntax, Python
programs can reduce startup time, memory usage, and unnecessary work. This is
particularly beneficial for command-line tools, test suites, and applications with
large dependency graphs.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It&amp;rsquo;s not been accepted yet, but let&amp;rsquo;s try out the
&lt;a href="https://github.com/LazyImportsCabal/cpython/tree/lazy"&gt;reference implementation&lt;/a&gt; on one
of my CLI tools, &lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="setup" class="relative group"&gt;Setup &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#setup" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;First fetch the reference implementation. From a CPython checkout:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git remote add LazyImportsCabal https://github.com/LazyImportsCabal/cpython
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch LazyImportsCabal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gco lazy &lt;span class="c1"&gt;# see https://hugovk.dev/blog/2025/my-most-used-command-line-commands/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Because we want to install NumPy and pandas, let&amp;rsquo;s pretend to be Python 3.14 so we can
use the binary wheels instead of having to build from source:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/Include/patchlevel.h
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/Include/patchlevel.h
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /* Version parsed out into numeric values */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /*--start constants--*/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #define PY_MAJOR_VERSION 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-#define PY_MINOR_VERSION 15
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+#define PY_MINOR_VERSION 14
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #define PY_MICRO_VERSION 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_ALPHA
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #define PY_RELEASE_SERIAL 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /* Version as a string */
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-#define PY_VERSION &amp;#34;3.15.0a0&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+#define PY_VERSION &amp;#34;3.14.0a0&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /*--end constants--*/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;--- a/configure.ac
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+++ b/configure.ac
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-m4_define([PYTHON_VERSION], [3.15])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+m4_define([PYTHON_VERSION], [3.14])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Build non-debug CPython with optimisations:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GDBM_CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-I&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix gdbm&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/include&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;GDBM_LIBS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-L&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix gdbm&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/lib -lgdbm&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ./configure --enable-optimizations --with-lto &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --with-system-libmpdec --config-cache &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --with-openssl&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix openssl@3&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; make -s -j8
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Install NumPy and pandas:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./python.exe -m pip install numpy pandas
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And then an editable install of the CLI, because we&amp;rsquo;ll also test changing the imports:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./python.exe -m pip install -e ~/github/pypistats
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Let&amp;rsquo;s check the dependencies with &lt;a href="https://github.com/tox-dev/pipdeptree"&gt;pipdeptree&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uvx &lt;span class="s2"&gt;&amp;#34;pipdeptree[graphviz]&amp;#34;&lt;/span&gt; --python ./python.exe --packages pypistats --graph-output svg &amp;gt; pipdeptree.svg
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;pypistats has seven direct dependencies, which result in a total of 41 dependencies six
layers deep, not counting NumPy and pandas:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 
 
 
 
 
 
 
 
 
 
 
 
 &lt;picture class="mx-auto my-0 rounded-md" &gt;
 &lt;img
 src="https://hugovk.dev/blog/2025/lazy-imports/pipdeptree.svg"
 width="1355"
 height="729"
 class="mx-auto my-0 rounded-md"
 alt="A tree of dependencies: seven wide, and about six layers deep."
 loading="lazy" decoding="async"
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="benchmarks" class="relative group"&gt;Benchmarks &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#benchmarks" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Let&amp;rsquo;s benchmark running &lt;code&gt;pypistats --help&lt;/code&gt;, which is meant to be quick, using
&lt;a href="https://github.com/sharkdp/hyperfine"&gt;hyperfine&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install hyperfine
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="inline-imports" class="relative group"&gt;Inline imports &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#inline-imports" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;In the pypistats CLI, I had already applied the trick of moving heavier imports into the
functions that call them (the PEP calls these
&lt;a href="https://peps.python.org/pep-0810/#motivation"&gt;&amp;ldquo;inline imports&amp;rdquo;&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Instead of the &lt;a href="https://peps.python.org/pep-0810/#specification"&gt;&lt;code&gt;lazy&lt;/code&gt; keyword&lt;/a&gt;, I&amp;rsquo;m
using the
&lt;a href="https://peps.python.org/pep-0810/#global-lazy-imports-control"&gt;&lt;code&gt;PYTHON_LAZY_IMPORTS&lt;/code&gt; env var&lt;/a&gt;
here to make it easy to compare two different runs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine --warmup &lt;span class="m"&gt;10&lt;/span&gt; --runs &lt;span class="m"&gt;20&lt;/span&gt; --export-json out.json &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; &amp;#34;./python.exe -m pypistats --help&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; &amp;#34;PYTHON_LAZY_IMPORTS=on ./python.exe -m pypistats --help&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: ./python.exe -m pypistats --help
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Time (mean ± σ): 46.2 ms ± 1.1 ms [User: 38.8 ms, System: 6.4 ms]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Range (min … max): 45.1 ms … 49.6 ms 20 runs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 2: PYTHON_LAZY_IMPORTS=on ./python.exe -m pypistats --help
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Time (mean ± σ): 35.3 ms ± 0.5 ms [User: 29.5 ms, System: 4.8 ms]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Range (min … max): 34.6 ms … 36.3 ms 20 runs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Summary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; PYTHON_LAZY_IMPORTS=on ./python.exe -m pypistats --help ran
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 1.31 ± 0.04 times faster than ./python.exe -m pypistats --help
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Plotted with
&lt;a href="https://github.com/sharkdp/hyperfine/tree/master/scripts"&gt;plot_progression.py&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 
 srcset="https://hugovk.dev/blog/2025/lazy-imports/inline-imports_hu_e3be42cb0da8959c.webp"
 
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="640"
 height="480"
 class="mx-auto my-0 rounded-md"
 alt="A progression chart of 20 runs for each benchmark: non-lazy runs are about 46 ms, lazy are about 35 ms."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/lazy-imports/inline-imports.png"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;From 46 to 35 milliseconds, or, 1.31 times faster, not bad.&lt;/p&gt;
&lt;h3 id="fully-lazy" class="relative group"&gt;Fully lazy &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#fully-lazy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;But we no longer need the inline imports trick with PEP 810!&lt;/p&gt;
&lt;p&gt;I modified the CLI so all imports are at the top, and also removed &lt;code&gt;if TYPE_CHECKING:&lt;/code&gt;
guards. Here&amp;rsquo;s a
&lt;a href="https://github.com/hugovk/pypistats/commit/646bc6f70656df26d55a0bf4977a878a2b4379e5"&gt;diff&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; hyperfine --warmup &lt;span class="m"&gt;10&lt;/span&gt; --runs &lt;span class="m"&gt;20&lt;/span&gt; --export-json out2.json &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; &amp;#34;./python.exe -m pypistats --help&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; &amp;#34;PYTHON_LAZY_IMPORTS=on ./python.exe -m pypistats --help&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 1: ./python.exe -m pypistats --help
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Time (mean ± σ): 104.1 ms ± 1.6 ms [User: 88.2 ms, System: 14.5 ms]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Range (min … max): 101.9 ms … 109.5 ms 20 runs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Benchmark 2: PYTHON_LAZY_IMPORTS=on ./python.exe -m pypistats --help
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Time (mean ± σ): 35.7 ms ± 0.5 ms [User: 29.8 ms, System: 4.8 ms]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; Range (min … max): 34.7 ms … 36.5 ms 20 runs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Summary
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; PYTHON_LAZY_IMPORTS=on ./python.exe -m pypistats --help ran
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; 2.92 ± 0.06 times faster than ./python.exe -m pypistats --help
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 
 srcset="https://hugovk.dev/blog/2025/lazy-imports/fully-lazy_hu_a179eb64034f4f.webp"
 
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="640"
 height="480"
 class="mx-auto my-0 rounded-md"
 alt="A progression chart of 20 runs for each benchmark: non-lazy runs are about 104 ms, lazy are about 36 ms."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/lazy-imports/fully-lazy.png"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;From 104 to 36 milliseconds, or 2.92 times faster, much better!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&lt;a target="_blank" rel="noopener noreferrer" href="https://flickr.com/photos/usnationalarchives/4272370812/"&gt;
&amp;ldquo;Lazy Man Fishing&amp;rdquo; at Cascade Locks on the Columbia River 05/1973&lt;/a&gt; in the
&lt;a target="_blank" rel="noopener noreferrer" href="https://flickr.com/photos/usnationalarchives/"&gt;U.S.
National Archives &lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Releasing Python 3.14.0</title><link>https://hugovk.dev/blog/2025/releasing-python-3-14-0/</link><pubDate>Sat, 11 Oct 2025 17:04:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/releasing-python-3-14-0/</guid><description>&lt;h2 id="prologue" class="relative group"&gt;Prologue &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#prologue" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I livetooted the release of Python 3.14.0. Here it is in blogpost form!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="one-week" class="relative group"&gt;One week &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#one-week" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Only &lt;a href="https://peps.python.org/pep-0745/"&gt;one week left&lt;/a&gt; until the release of
&lt;a href="https://docs.python.org/3.14/whatsnew/3.14.html"&gt;Python 3.14.0 final&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;What are you looking forward to?&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115293209822115988"&gt;&lt;em&gt;Tue, Sep 30, 2025, 15:19 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="three-days" class="relative group"&gt;Three days &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#three-days" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Three days until release and a bug in the Linux kernel has turned a dozen
&lt;a href="https://buildbot.python.org/#/release_status"&gt;buildbots&lt;/a&gt; red&amp;hellip;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s already been fixed in the kernel, but will take some time to bubble up. We&amp;rsquo;ll skip
that test for relevant kernel versions in the meantime.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_815c767de9597868.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_be4462eb030ee1f.webp 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_2076cdb368f0ba41.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_5e461471b3a31fab.webp 1278w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1278"
 height="632"
 class="mx-auto my-0 rounded-md"
 alt="Python Release Status Dashboard: 3.15-3.13 are red and &amp;lsquo;✘ Unreleasable: Tier-1 build failed&amp;rsquo;"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_9863cdf67749ebc6.png" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_947f187e28b8399d.png 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_9863cdf67749ebc6.png 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_2d2f04ebaa5e8086.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red.png 1278w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_72810cddba217e3d.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_f69c0158cdbae1be.webp 660w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_f005d9bd8dd49d9.webp 772w
 
 
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_f005d9bd8dd49d9.webp 772w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="772"
 height="678"
 class="mx-auto my-0 rounded-md"
 alt="3.14 Tier-1 build failed (5), Tier-2 build failed (2), Tier-3 build failed (6), Disconnected Tier-1 builder (with recent build) (1), Disconnected Tier-2 builder (1), Disconnected Tier-3 builder (1), Disconnected Tierless builder (1), Warnings from Tier-1 build (1), Warnings from Tier-2 build (3), Warnings from Tier-3 build (2), Unstable build failed (18), Warnings from unstable build (4), Disconnected unstable builder (30), No problem detected (72)"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_e20d0fe186865b0f.png" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_aa04ebac0939e5dd.png 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_e20d0fe186865b0f.png 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details.png 772w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details.png 772w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_fb625cd70c83c1da.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_d1832d5cf292a583.webp 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_ca4cc1e6d7a91aa.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_382cf8e78f5b3bf8.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1382"
 height="700"
 class="mx-auto my-0 rounded-md"
 alt="ERROR: test_aead_aes_gcm (test.test_socket.LinuxKernelCryptoAPI.test_aead_aes_gcm)"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_11957d2edb142073.png" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_b54b280e096658d1.png 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_11957d2edb142073.png 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_39257eb41152c72d.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_7788575261e2559a.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115316079113681802"&gt;&lt;em&gt;Sat, Oct 4, 2025, 16:15 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="green" class="relative group"&gt;Green &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#green" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;And back to green!&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_a389d27f6eac7906.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_b8cac41bcbd2e951.webp 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_f4592446513e783a.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_b143c9c867ddb722.webp 1138w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1138"
 height="644"
 class="mx-auto my-0 rounded-md"
 alt="Python Release Status Dashboard: 3.15 is red: ✘ Unreleasable Tier-1 build failed. 3.14 is green: ✔ Releasable Disconnected Tier-2 builder. 3.13 is orange: ⚠ Concern Tierless build failed"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_f5675c1cf598259a.png" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_fbd69268f30704db.png 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_f5675c1cf598259a.png 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_b17af36243e6f0f3.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green.png 1138w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115321909749435460"&gt;&lt;em&gt;Sun, Oct 5, 2025, 16:58 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="release-day" class="relative group"&gt;Release day! &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#release-day" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;First off, check blockers and buildbots.&lt;/p&gt;
&lt;p&gt;A new &lt;a href="https://github.com/python/cpython/labels/release-blocker"&gt;release-blocker&lt;/a&gt;
appeared yesterday (because of course) but it can wait until 3.14.1.&lt;/p&gt;
&lt;p&gt;Three &lt;a href="https://github.com/python/cpython/labels/deferred-blocker"&gt;deferred-blockers&lt;/a&gt; are
also waiting until 3.14.1.&lt;/p&gt;
&lt;p&gt;A new tier-2 &lt;a href="https://buildbot.python.org/#/release_status"&gt;buildbot&lt;/a&gt; failure appeared
yesterday (because of course) but it had previously been offline for a month and will
need some reconfiguration. Can ignore.&lt;/p&gt;
&lt;p&gt;OK, let&amp;rsquo;s make a Python!&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115331985979927140"&gt;&lt;em&gt;Tue, Oct 7, 2025, 11:40 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="run_releasepy" class="relative group"&gt;&lt;code&gt;run_release.py&lt;/code&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#run_releasepy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Next up, merge and backport the
&lt;a href="https://github.com/python/cpython/pull/139631"&gt;final change&lt;/a&gt; to
&lt;a href="https://docs.python.org/3/whatsnew/3.14.html"&gt;What&amp;rsquo;s New in Python 3.14&lt;/a&gt; to declare it
latest stable.&lt;/p&gt;
&lt;p&gt;Now start &lt;code&gt;run_release.py&lt;/code&gt;, the main release automation script, which does a bunch of
pre-checks, runs blurb to create a merged changelog, bumps some numbers, and pushes a
branch and tag to my fork. It&amp;rsquo;ll go upstream at the end of a successful build.&lt;/p&gt;
&lt;p&gt;Then kick off the &lt;a href="https://github.com/python/release-tools/actions/runs/18308460797"&gt;CI&lt;/a&gt;
to build source zips, docs and Android binaries.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_45de08b244a5c71d.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_2fdb04223bdbbe58.webp 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_c5f3422d466faf5c.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_5fef3a1166d5016b.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1650"
 height="720"
 class="mx-auto my-0 rounded-md"
 alt="A GitHub Actions build matrix showing an initial verify-input followed by parallel build-source (itself followed by test-source), build-docs, and build-android (consisting of aarch64 and x86_64 jobs)."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_6394a89296439a5b.png" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_1c05f932c3e5cc5.png 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_6394a89296439a5b.png 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_8b2ed52792df0c52.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_ec0ac4d2f88e1785.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115332234337389060"&gt;&lt;em&gt;Tue, Oct 7, 2025, 12:43 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="installers" class="relative group"&gt;Installers &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#installers" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;(That&amp;rsquo;s actually the second CI attempt, we had to update some script arguments following
an Android test runner update.)&lt;/p&gt;
&lt;p&gt;This build takes about half an hour.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also informed the Windows and macOS release managers about the tag and they will
start up installer builds.&lt;/p&gt;
&lt;p&gt;This takes a few hours, so I&amp;rsquo;ve got time to finish up the release notes.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://peps.python.org/pep-0101/"&gt;PEP 101&lt;/a&gt; is the full process, but much is automated
and we don&amp;rsquo;t need to follow it all manually.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115332266999944269"&gt;&lt;em&gt;Tue, Oct 7, 2025, 12:52 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="windows" class="relative group"&gt;Windows &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#windows" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The
&lt;a href="https://dev.azure.com/Python/cpython/_build/results?buildId=164907&amp;amp;view=results"&gt;Windows build&lt;/a&gt;
has been started.&lt;/p&gt;
&lt;p&gt;The jobs with profile-guided optimisation (PGO) build once, then collect a profile by
running the tests, and then build again using that profile, to see how &amp;lsquo;real&amp;rsquo; code
executes and optimises for that.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_28925c30d5560327.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_ad4a1e1a0b89091c.webp 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_40c943f220ccd59c.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_dac2b1933158c521.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="3052"
 height="1504"
 class="mx-auto my-0 rounded-md"
 alt="The Windows build on Azure Pipelines. Lots of boxes for each of &amp;lsquo;build binaries&amp;rsquo;, &amp;lsquo;sign binaries&amp;rsquo;, &amp;lsquo;generate layouts&amp;rsquo;, &amp;lsquo;pack&amp;rsquo;, &amp;rsquo;test&amp;rsquo; and finally &amp;lsquo;publish&amp;rsquo;. So far nearing the end of the build binaries stage."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_798f4984f9fee523.png" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_a22a60701eb13f13.png 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_798f4984f9fee523.png 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_51ab074e9b5647bf.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_7e71d3bd21dfa235.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Meanwhile, the docs+source+Android build has finished and the artifacts have been copied
to where they need to go with SBOMs created.&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115332496234142659"&gt;&lt;em&gt;Tue, Oct 7, 2025, 13:50 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="macos" class="relative group"&gt;macOS &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#macos" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The Windows build is ready and macOS is underway.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_c6f4018cf81247af.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_ba20a8a531a2af28.webp 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_9f38bbf57031b5b.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_2f0414d15dba433d.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1372"
 height="1388"
 class="mx-auto my-0 rounded-md"
 alt="Terminal prompt showing the output of run_release.py with lots of checked tasks and ending with: Waiting for files: Linux ✅ Windows ✅ Mac ❌"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_6d5a9fc36155722c.png" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_43e4024db2eb6365.png 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_6d5a9fc36155722c.png 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_c4247a1a0d0378e6.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_38027238b4c95f16.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115332912443887856"&gt;&lt;em&gt;Tue, Oct 7, 2025, 15:36 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="final-steps" class="relative group"&gt;Final steps &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#final-steps" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;macOS installer done, next on to the final publishing and announcing steps.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_ec8fc101f4d8cc14.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_892c52554c61537.webp 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_5ffcaeaa7fdc1ac3.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_d59b794606aa4ce7.webp 1170w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1170"
 height="128"
 class="mx-auto my-0 rounded-md"
 alt="Terminal showing: ✅ Wait until all files are ready. Go to https://www.python.org/admin/downloads/release/add/ and create a new release. Have you already created a new release for 3.14.0? Enter yes or no:"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_ffef6ff1316a32ec.png" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_4fc638a9af68f240.png 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_ffef6ff1316a32ec.png 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_8e7f67d1cb434d9d.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release.png 1170w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115333249425273213"&gt;&lt;em&gt;Tue, Oct 7, 2025, 17:02 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="-its-out" class="relative group"&gt;🚀 It&amp;rsquo;s out! &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#-its-out" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;🥧 Please install and enjoy
&lt;a href="https://discuss.python.org/t/python-3-14-0-final-is-here/104210?u=hugovk"&gt;Python 3.14&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/feature_hu_efee4cbe1d39df2c.webp"
 
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="538"
 height="507"
 class="mx-auto my-0 rounded-md"
 alt="Two snakes enjoying a pie with 3.14 on the top and π crimping."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/feature.png"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115333348041057866"&gt;&lt;em&gt;Tue, Oct 7, 2025, 17:27 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;
&lt;h2 id="finally" class="relative group"&gt;Finally &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#finally" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;And the last few tasks: announce also on the
&lt;a href="https://blog.python.org/2025/10/python-3140-final-is-here.html"&gt;blog&lt;/a&gt; &amp;amp;
&lt;a href="https://mail.python.org/archives/list/python-list@python.org/thread/UDOUFTTWDO7IXHLPQSHTEW2FWGF7ZY2C/"&gt;mailing lists&lt;/a&gt;,
update the &lt;a href="https://peps.python.org/pep-0745/"&gt;PEP&lt;/a&gt; &amp;amp;
&lt;a href="https://www.python.org/downloads/"&gt;downloads landing page&lt;/a&gt;, fix
&lt;a href="https://discuss.python.org/t/python-3-14-0-final-is-here/104210?u=hugovk"&gt;Discourse post&lt;/a&gt;
links, unlock the &lt;a href="https://github.com/python/cpython/tree/3.14"&gt;&lt;code&gt;3.14&lt;/code&gt; branch&lt;/a&gt; for the
core team to start landing PRs that didn&amp;rsquo;t need to be in the RC, and eat the pie.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_f3dacf1299e8d746.webp 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_1f4efb3c3fec5a55.webp 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_4aa2808a2ce3f41f.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_8440b9b0753887e8.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="3325"
 height="2494"
 class="mx-auto my-0 rounded-md"
 alt="A circular lemon meringue pie in front of a Mac showing the release logo."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_1b6cc4420f097762.jpg" srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_db302a2c5b55d083.jpg 330w,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_1b6cc4420f097762.jpg 660w
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_773b2d1c8c707928.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_edcaa9663bf9bd8c.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;A HUGE thanks to &lt;a href="https://mastodon.social/@sovtechfund"&gt;@sovtechfund&lt;/a&gt;
&lt;a href="https://hugovk.dev/blog/2025/im-excited-to-join-the-sovereign-tech-fellowship/"&gt;Fellowship&lt;/a&gt;
for allowing me to dedicate my time on getting this out 🎉&lt;/p&gt;
&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;
&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;
&lt;a href="https://mastodon.social/@hugovk/115333826029083841"&gt;&lt;em&gt;Tue, Oct 7, 2025, 19:28 EEST&lt;/em&gt;&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Ready prek go!</title><link>https://hugovk.dev/blog/2025/ready-prek-go/</link><pubDate>Sat, 06 Sep 2025 15:50:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/ready-prek-go/</guid><description>&lt;p&gt;I&amp;rsquo;ve been using &lt;a href="https://prek.j178.dev/"&gt;prek&lt;/a&gt; recently as a drop-in replacement for
&lt;a href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It uses uv for managing Python virtual environments and dependencies, is rewritten in
Rust (because of course) and uses the same &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; as pre-commit.&lt;/p&gt;
&lt;p&gt;Its homepage says it&amp;rsquo;s not yet production ready, but
&lt;a href="https://prek.j178.dev/#who-is-using-prek"&gt;several projects like Apache Airflow and PDM&lt;/a&gt;
are already using it. I&amp;rsquo;ve been using it for a while and reporting issues as I find
them; the maintainer is quick with fixes and releases. All the hooks I need are now
supported.&lt;/p&gt;
&lt;h2 id="getting-started" class="relative group"&gt;Getting started &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#getting-started" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;First install using one of the &lt;a href="https://prek.j178.dev/installation/"&gt;many methods&lt;/a&gt;,
then:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; my-repo
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pre-commit uninstall &lt;span class="c1"&gt;# remove the old hooks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;prek install &lt;span class="c1"&gt;# install the new hooks&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;prek run --all-files &lt;span class="c1"&gt;# run all lints on all files&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;my commit&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# run on the changed files in a commit&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="benchmarks" class="relative group"&gt;Benchmarks &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#benchmarks" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;prek is noticeably quicker at installing hooks.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;⚡ About &lt;a href="https://prek.j178.dev/benchmark/"&gt;10x faster&lt;/a&gt; than pre-commit and uses
only a third of disk space.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;This 10x is a comparison of installing hooks using the excellent
&lt;a href="https://github.com/sharkdp/hyperfine"&gt;hyperfine&lt;/a&gt; benchmarking tool.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my own comparison.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;In the first test, I initially ran &lt;code&gt;pre-commit clean&lt;/code&gt; or &lt;code&gt;prek clean&lt;/code&gt; to clear their
caches. I then ran each tool with &lt;code&gt;run --all-files&lt;/code&gt; in serial on the 126 repos I have
cloned right now, 84 of which have a &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file. Each then
downloads and installs the hooks when needed, then runs the lint tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Second, because running the lint tools should be independent and constant time for
both tools, the next test ran &lt;code&gt;install-hooks&lt;/code&gt; instead of &lt;code&gt;run --all files&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To get an idea of the amount of work for these two tests, pre-commit reported
initialising environments 217 times.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, let&amp;rsquo;s run hyperfine on the
&lt;a href="https://github.com/python-pillow/Pillow/blob/eef4848a0a7ca0ea5ecd1f647603d85526d88ca7/.pre-commit-config.yaml"&gt;Pillow config&lt;/a&gt;,
which installs hooks from 14 repos:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;❯ hyperfine &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--prepare &lt;span class="s1"&gt;&amp;#39;rm -rf ~/.cache/prek/ &amp;amp;&amp;amp; rm -rf ~/.cache/pre-commit &amp;amp;&amp;amp; rm -rf ~/.cache/uv&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--setup &lt;span class="s1"&gt;&amp;#39;prek --version &amp;amp;&amp;amp; pre-commit --version&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;prek install-hooks&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt;&amp;#39;pre-commit install-hooks&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="results" class="relative group"&gt;Results &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#results" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/th&gt;
 &lt;th&gt;&lt;code&gt;prek&lt;/code&gt;&lt;/th&gt;
 &lt;th&gt;Times faster&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;run --all-files&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;17m13s&lt;/td&gt;
 &lt;td&gt;7m59s&lt;/td&gt;
 &lt;td&gt;2.16&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;install-hooks&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;10m48s&lt;/td&gt;
 &lt;td&gt;2m24s&lt;/td&gt;
 &lt;td&gt;4.50&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;39.841s&lt;/td&gt;
 &lt;td&gt;5.539s&lt;/td&gt;
 &lt;td&gt;7.19&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The hyperfine results:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Benchmark 1: prek install-hooks
 Time (mean ± σ): 5.539 s ± 0.176 s [User: 8.973 s, System: 5.692 s]
 Range (min … max): 5.231 s … 5.834 s 10 runs

Benchmark 2: pre-commit install-hooks
 Time (mean ± σ): 39.841 s ± 2.017 s [User: 19.760 s, System: 8.203 s]
 Range (min … max): 36.930 s … 43.976 s 10 runs

Summary
 prek install-hooks ran
 7.19 ± 0.43 times faster than pre-commit install-hooks
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Give it a try and give it a &lt;a href="https://www.star-history.com/#j178/prek&amp;amp;Date"&gt;⭐&lt;/a&gt;!&lt;/p&gt;
&lt;h2 id="bonus" class="relative group"&gt;Bonus &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#bonus" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;These are the aliases I have set for pre-commit and prek:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pci&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pre-commit install --allow-missing-config&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pcu&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pre-commit uninstall&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pca&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pre-commit autoupdate --jobs 0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pcr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pre-commit run --all-files&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pki&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;prek install --allow-missing-config&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pku&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;prek uninstall&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pka&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;prek autoupdate --jobs 0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;alias&lt;/span&gt; &lt;span class="nv"&gt;pkr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;prek run --all-files&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;install&lt;/code&gt;&amp;rsquo;s &lt;code&gt;--allow-missing-config&lt;/code&gt; prevents failing with an error code when a repo
has no config file&lt;/li&gt;
&lt;li&gt;&lt;code&gt;autoupdate&lt;/code&gt;&amp;rsquo;s &lt;code&gt;--jobs 0&lt;/code&gt; uses all the available threads to make it faster&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/sdasmarchives/51500757435/"&gt;AH-1G
Aircraft Maintenance Test Flight Handbook and handwritten checklist for helicopters&lt;/a&gt;
in the
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/sdasmarchives/"&gt;San
Diego Air and Space Museum Archive&lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>EuroPython 2025: A roundup of writeups</title><link>https://hugovk.dev/blog/2025/europython-2025-a-roundup-of-writeups/</link><pubDate>Mon, 25 Aug 2025 13:59:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/europython-2025-a-roundup-of-writeups/</guid><description>&lt;p&gt;Some out-of-context quotes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;We can just bump the version and move on.&amp;rdquo;&lt;/em&gt; &amp;ndash; Dr. Brett Cannon&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;You just show up. That’s it.&amp;rdquo;&lt;/em&gt; &amp;ndash; Rodrigo Girão Serrão&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;If it kwargs like a dorg, it&amp;rsquo;s a dorg.&amp;rdquo;&lt;/em&gt; &amp;ndash; Sebastián Ramírez&lt;/li&gt;
&lt;li&gt;&lt;em&gt;&amp;ldquo;Our job will be to put the human in.&amp;rdquo;&lt;/em&gt; &amp;ndash; Paul Everitt&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;20 July 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://georgiker.com/blog/is-ai-leaving-python-community-behind/"&gt;Is AI Leaving the Python Community Behind?&lt;/a&gt;
by &lt;a href="https://mastodon.social/@georgically/114891414072680058"&gt;Georgi Ker&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;21 July 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://wsvincent.com/europython2025-recap/"&gt;EuroPython 2025 in Prague Recap&lt;/a&gt; by
&lt;a href="https://fosstodon.org/@wsvincent/114891651350217561"&gt;Will Vincent&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://honzajavorek.cz/blog/tydenni-poznamky-nataceni-promo-videa-a-europython/#europython"&gt;Týdenní poznámky: Natáčení promo videa a EuroPython&lt;/a&gt;
by &lt;a href="https://mastodonczech.cz/@honzajavorek/114892035602713856"&gt;Honza Javorek&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;23 July 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://raffaella.bearblog.dev/first-europython/"&gt;First EuroPython&lt;/a&gt; by
&lt;a href="https://mastodon.social/@raffaellasuardini/114908865884572072"&gt;Raffaella Suardini&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;24 July 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cheuk.dev/posts/2025-07-24-europython25-recap/"&gt;EuroPython 2025 Recap&lt;/a&gt; by
&lt;a href="https://fosstodon.org/@cheukting_ho/114913173274810806/"&gt;Cheuk Ting Ho&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;11 August 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mathspp.com/blog/personal-highlights-of-europython-2025"&gt;Personal highlights of EuroPython 2025&lt;/a&gt;
by &lt;a href="https://bsky.app/profile/mathspp.com/post/3lw4z7e4nds2p"&gt;Rodrigo Girão Serrão&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;12 August 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://discourse.ubuntu.com/t/europython-2025-i-came-for-the-language-but-stayed-for-the-community/65986"&gt;EuroPython 2025: “I came for the language, but stayed for the community”&lt;/a&gt;
by &lt;a href="https://fosstodon.org/@jugmac00/115016290243228831"&gt;Jürgen Gmach&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;17 August 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gaudengalea.com/blog/pyladies-pacman-and-public-health/"&gt;PyLadies, PacMan, and Public Health&lt;/a&gt;
by Gauden Galea&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;21 August 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gihyo.jp/article/2025/08/europython-2025-01"&gt;EuroPython 2025開幕までの道のり⁠⁠、Day 1の注目セッション&lt;/a&gt;
by &lt;a href="https://github.com/takanory"&gt;Takanori Suzuki&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;8 September 2025&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.qstars.nl/posts/europython-2025/"&gt;Recap EuroPython 2025&lt;/a&gt; by Victor
Brinkhorst&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And a bunch of LinkedIn posts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/activity-7353091873093181442-jUko"&gt;Alicja Kocieniewska&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/diegor_back-fromeuropython-2025and-still-buzzing-activity-7353354177646845953-4nNo"&gt;Diego Russo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/ece-akdeniz_europython2025-python-ai-ugcPost-7353399096734375936-8_LF"&gt;Ece Akdeniz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/jodieburchell_last-weeks-europython-was-one-of-the-most-ugcPost-7354418947993042947-NsXG/"&gt;Jodie Burchell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/kseniia-usyk_europython2025-pythoncommunity-opensource-ugcPost-7352790573398818818-RkFf"&gt;Kseniia Usyk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/lara-kraemer_i-went-to-my-first-europython-in-prague-ugcPost-7355554412565536769-Au4o"&gt;Lara Krämer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/libor-vanek_europython-europython2025-activity-7353313753125322754-feTT"&gt;Libor Vaněk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/marco-richetta_a-week-since-europython-ended-the-post-conference-ugcPost-7354551181865578496-cTw1"&gt;Marco Richetta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/o-yefymenko_last-week-i-participated-in-a-major-conference-activity-7355536964810416153-sPP2"&gt;Olena Yefymenko&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/activity-7355237790462148608-VYL-"&gt;Vassiliki Dalakiari&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finally, the &lt;a href="https://www.flickr.com/photos/europython"&gt;official photos&lt;/a&gt; and
&lt;a href="https://www.youtube.com/@EuroPythonConference"&gt;videos&lt;/a&gt; should be up soon, and here are
&lt;a href="https://www.flickr.com/photos/hugovk/collections/72157724094370661/"&gt;my photos&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="update" class="relative group"&gt;Update &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#update" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;2025-09-06: Added Gauden Galea&amp;rsquo;s writeup&lt;/li&gt;
&lt;li&gt;2025-09-09: Added Victor Brinkhorst&amp;rsquo;s writeup&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo: Savannah Bailey&amp;rsquo;s keynote
(&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;CC
BY-NC-SA 2.0&lt;/a&gt;
&lt;a href="https://www.flickr.com/photos/hugovk/54735485531/in/album-72177720328581986"&gt;Hugo van Kemenade&lt;/a&gt;).&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Run coverage on tests</title><link>https://hugovk.dev/blog/2025/run-coverage-on-tests/</link><pubDate>Fri, 27 Jun 2025 17:41:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/run-coverage-on-tests/</guid><description>&lt;p&gt;I recommend
&lt;a href="https://nedbatchelder.com/blog/201106/running_coverage_on_your_tests.html"&gt;running&lt;/a&gt;
&lt;a href="https://nedbatchelder.com/blog/201908/dont_omit_tests_from_coverage.html"&gt;coverage&lt;/a&gt;
&lt;a href="https://nedbatchelder.com/blog/202008/you_should_include_your_tests_in_coverage.html"&gt;on your tests&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a couple of reasons why, from the past couple of months.&lt;/p&gt;
&lt;h2 id="example-one" class="relative group"&gt;Example one &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#example-one" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;When writing tests, it&amp;rsquo;s common to copy and paste test functions, but sometimes you
forget to rename the new one (see also: the
&lt;a href="https://pvs-studio.com/en/blog/posts/cpp/0260/"&gt;Last Line Effect&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_get_install_to_run_with_platform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patched_installs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;installs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_install_to_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;lt;none&amp;gt;&amp;#34;&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="s2"&gt;&amp;#34;1.0-32&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PythonCore-1.0-32&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;executable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python.exe&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;installs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_install_to_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;lt;none&amp;gt;&amp;#34;&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="s2"&gt;&amp;#34;2.0-arm64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PythonCore-2.0-arm64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;executable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;python.exe&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_get_install_to_run_with_platform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patched_installs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;installs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_install_to_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;lt;none&amp;gt;&amp;#34;&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="s2"&gt;&amp;#34;1.0-32&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowed&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PythonCore-1.0-32&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;executable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pythonw.exe&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;installs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_install_to_run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;lt;none&amp;gt;&amp;#34;&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="s2"&gt;&amp;#34;2.0-arm64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;windowed&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PythonCore-2.0-arm64&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;executable&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pythonw.exe&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The tests pass, but the first one is never run because its name is redefined. This
clearly shows up as a non-run test in the coverage report. In this
&lt;a href="https://github.com/python/pymanager/pull/17"&gt;case&lt;/a&gt;, we only need to rename one of them,
and both are covered and pass.&lt;/p&gt;
&lt;p&gt;But &lt;a href="https://github.com/python/cpython/pull/109139"&gt;sometimes&lt;/a&gt; there&amp;rsquo;s a bug in the test
which would cause it to fail, but we just don&amp;rsquo;t know because it&amp;rsquo;s not run.&lt;/p&gt;
&lt;p&gt;&lt;div class="flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900"&gt;
 &lt;span class="pe-3 text-primary-400"&gt;
 &lt;span class="icon relative inline-block px-1 align-text-bottom"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"&gt;&lt;path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;
 &lt;span class="dark:text-neutral-300"&gt;&lt;strong&gt;Tip 1:&lt;/strong&gt; This can also be found by
&lt;a href="https://docs.astral.sh/ruff/rules/redefined-while-unused/"&gt;Ruff&amp;rsquo;s F811 rule&lt;/a&gt;.&lt;/span&gt;
&lt;/div&gt;
   &lt;div class="flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900"&gt;
 &lt;span class="pe-3 text-primary-400"&gt;
 &lt;span class="icon relative inline-block px-1 align-text-bottom"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"&gt;&lt;path fill="currentColor" d="M112.1 454.3c0 6.297 1.816 12.44 5.284 17.69l17.14 25.69c5.25 7.875 17.17 14.28 26.64 14.28h61.67c9.438 0 21.36-6.401 26.61-14.28l17.08-25.68c2.938-4.438 5.348-12.37 5.348-17.7L272 415.1h-160L112.1 454.3zM191.4 .0132C89.44 .3257 16 82.97 16 175.1c0 44.38 16.44 84.84 43.56 115.8c16.53 18.84 42.34 58.23 52.22 91.45c.0313 .25 .0938 .5166 .125 .7823h160.2c.0313-.2656 .0938-.5166 .125-.7823c9.875-33.22 35.69-72.61 52.22-91.45C351.6 260.8 368 220.4 368 175.1C368 78.61 288.9-.2837 191.4 .0132zM192 96.01c-44.13 0-80 35.89-80 79.1C112 184.8 104.8 192 96 192S80 184.8 80 176c0-61.76 50.25-111.1 112-111.1c8.844 0 16 7.159 16 16S200.8 96.01 192 96.01z"/&gt;&lt;/svg&gt;
&lt;/span&gt;
 &lt;/span&gt;
 &lt;span class="dark:text-neutral-300"&gt;&lt;strong&gt;Tip 2:&lt;/strong&gt;
&lt;a href="https://docs.pytest.org/en/stable/how-to/parametrize.html"&gt;pytest&amp;rsquo;s parametrize&lt;/a&gt; is a
great way to combine similar test functions with different input data.&lt;/span&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;h2 id="example-two" class="relative group"&gt;Example two &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#example-two" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;This is more subtle:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;im&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;RGB&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;#f00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;#f00&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;#0f0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;append_images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;RGB&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;color&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;color&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;im_reloaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;roundtrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;save_all&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;append_images&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;append_images&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;assert_image_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;im_reloaded&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im_reloaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MpoImagePlugin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MpoImageFile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;im_reloaded&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mpinfo&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;im_reloaded&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mpinfo&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;45056&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0100&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;im_expected&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;append_images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;im_reloaded&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;seek&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im_reloaded&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tell&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;assert_image_similar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;im_reloaded&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;im_expected&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s not so obvious when looking at the code, but Codecov highlights a problem:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_bc400a05d72e8144.webp 330w,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_f31ff28b88fadcc3.webp 660w
 
 ,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_3b6766957c9cd7fd.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_5c396df3c22389e8.webp 1290w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1290"
 height="726"
 class="mx-auto my-0 rounded-md"
 alt="The same code, but Codecov has flagged the last two lines were not covered"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_4e6b566be4276c38.png" srcset="https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_860efdeee832f948.png 330w,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_4e6b566be4276c38.png 660w
 
 ,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_a7b7dce21898e4e6.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage.png 1290w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;append_images&lt;/code&gt; generator is being consumed inside &lt;code&gt;roundtrip()&lt;/code&gt;, so we have nothing
to iterate over in the &lt;code&gt;for&lt;/code&gt; loop &amp;ndash; hence no coverage. The
&lt;a href="https://github.com/python-pillow/Pillow/pull/8979#discussion_r2172250301"&gt;fix&lt;/a&gt; is to
use a list instead of a generator.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo: Misplaced manhole cover
(&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;CC
BY-NC-SA 2.0&lt;/a&gt;
&lt;a href="https://www.flickr.com/photos/hugovk/10375735084/"&gt;Hugo van Kemenade&lt;/a&gt;).&lt;/small&gt;&lt;/p&gt;</description></item><item><title>PEPs &amp; Co.</title><link>https://hugovk.dev/blog/2025/peps-and-co/</link><pubDate>Wed, 14 May 2025 15:45:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/peps-and-co/</guid><description>&lt;h2 id="peps" class="relative group"&gt;PEPs &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#peps" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s &lt;a href="https://barry.warsaw.us/"&gt;Barry Warsaw&lt;/a&gt; on the origin of
&lt;a href="https://peps.python.org/"&gt;PEPs&lt;/a&gt;, or Python Enhancement Proposals (edited from
&lt;a href="https://www.youtube.com/embed/7NrPCsH0mBU?start=1662&amp;amp;end=1803"&gt;PyBay 2017&lt;/a&gt;):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I like backronyms. For those who don&amp;rsquo;t know: a backronym is where you come up with the
acronym first and then you come up with the thing that the acronym stands for. And I
like funny sounding words, like &lt;a href="https://peps.python.org/pep-0401/"&gt;FLUFL&lt;/a&gt; was one of
those. When we were working for CNRI, they also ran the IETF conferences. The IETF is
the Internet Engineering Task Force, and they&amp;rsquo;re the ones who come up with the RFCs.
If you look at &lt;a href="https://datatracker.ietf.org/doc/html/rfc822"&gt;RFC 822&lt;/a&gt;, it defines
what an email message looks like.&lt;/p&gt;
&lt;p&gt;We got to a point, because we were at CNRI we were more intimately involved in the
IETF and how they do standards and things, we observed at the time that there were so
many interesting ideas coming in being proposed for Python that Guido really just
didn&amp;rsquo;t have time to dive into the details of everything.&lt;/p&gt;
&lt;p&gt;So I thought: well, we have this RFC process, let&amp;rsquo;s try to mirror some of that so that
we can capture the essence of an idea in a document that would serve as a point of
discussion, and that Guido could let people discuss and then come in and read the
summary of the discussion.&lt;/p&gt;
&lt;p&gt;And I was just kind of thinking: well, PEPs, that&amp;rsquo;s kind of peppy, it&amp;rsquo;s kind of a
funny sounding word. I came up with the word and then I backronymed it into Python
Enhancement Proposal. And then I wrote &lt;a href="https://peps.python.org/pep-0000/"&gt;PEP 0&lt;/a&gt; and
&lt;a href="https://peps.python.org/pep-0001/"&gt;PEP 1&lt;/a&gt;. PEP 0 was originally handwritten, and so I
was the first PEP author because I came up with the name PEP.&lt;/p&gt;
&lt;p&gt;But the really interesting thing is that you see the E.P. part used in a lot of other
places, like Debian has DEPs now. There&amp;rsquo;s a lot of other communities that have these
enhancement proposals so it&amp;rsquo;s kind of interesting. And then the format of the PEP was
directly from that idea of the RFC&amp;rsquo;s standard.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="-co" class="relative group"&gt;&amp;amp; Co. &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#-co" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Here&amp;rsquo;s a collection of enhancement proposals from different communities.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Acronym&lt;/th&gt;
 &lt;th&gt;Name&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;AIP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://cwiki.apache.org/confluence/display/AIRFLOW/Airflow&amp;#43;Improvement&amp;#43;Proposals"&gt;Airflow Improvement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;AIP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://google.aip.dev/"&gt;API Improvement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;APE&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/astropy/astropy-APEs"&gt;Astropy Proposals for Enhancement&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;BIP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/bitcoin/bips"&gt;Bitcoin Improvement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/conda-incubator/ceps"&gt;Conda Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;CFEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/conda-forge/cfep"&gt;conda-forge&amp;rsquo;s Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://dep-team.pages.debian.net/"&gt;Debian Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;DEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/django/deps"&gt;Django Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;FEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://codeberg.org/fediverse/fep"&gt;Fediverse Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;GLEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://www.gentoo.org/glep/glep-0001.html"&gt;Gentoo Linux Enhancement Proposal&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;IPEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/ipython/ipython/wiki/IPEPs:-IPython-Enhancement-Proposals"&gt;IPython Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;JEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://openjdk.org/jeps/0"&gt;JDK Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;JEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/jmespath/jmespath.jep"&gt;JMESPath Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;JEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://jupyter.org/enhancement-proposals/README.html"&gt;Jupyter Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;KEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://www.kubernetes.dev/resources/keps/"&gt;Kubernetes Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;NEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://numpy.org/neps/"&gt;NumPy Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PEEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/pypa/pipenv/blob/main/peeps/PEEP-000.md"&gt;Pipenv Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;PEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://peps.python.org/"&gt;Python Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SKIP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://scikit-image.org/docs/stable/skips/"&gt;scikit-image proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SLEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://scikit-learn-enhancement-proposals.readthedocs.io"&gt;Scikit-learn enhancement proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SPEC&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://scientific-python.org/specs/"&gt;Scientific Python Ecosystem Coordination&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;TIP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://core.tcl-lang.org/tips/doc/trunk/index.md"&gt;Tcl Improvement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;WEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/writethedocs/weps"&gt;Write the Docs Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;XEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://xmpp.org/extensions/"&gt;XMPP Extension Protocols&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;YTEP&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://ytep.readthedocs.io/"&gt;yt Enhancement Proposals&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Are there more? Let me know!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/library_of_congress/2179931106/"&gt;Grand
Grocery Co., Lincoln, Nebraska, USA (1942)&lt;/a&gt; by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/library_of_congress/"&gt;The
Library of Congress&lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>My most used command-line commands</title><link>https://hugovk.dev/blog/2025/my-most-used-command-line-commands/</link><pubDate>Fri, 04 Apr 2025 16:50:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/my-most-used-command-line-commands/</guid><description>&lt;p&gt;Following
&lt;a href="https://micro.webology.dev/2025/01/02/my-most-used-commands-in/"&gt;Jeff Triplett&amp;rsquo;s lead&lt;/a&gt;,
here&amp;rsquo;s a list of my most used terminal commands.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;No.&lt;/th&gt;
 &lt;th&gt;Total&lt;/th&gt;
 &lt;th&gt;Command&lt;/th&gt;
 &lt;th&gt;Info&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1.&lt;/td&gt;
 &lt;td&gt;1239&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;rg&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/BurntSushi/ripgrep"&gt;ripgrep&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2.&lt;/td&gt;
 &lt;td&gt;1038&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;c&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;a href="https://www.jetbrains.com/pycharm/"&gt;&lt;code&gt;pycharm&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.&lt;/td&gt;
 &lt;td&gt;847&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;gc&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;git commit --verbose&lt;/code&gt; via &lt;a href="https://github.com/ohmyzsh/ohmyzsh"&gt;Oh My Zsh&lt;/a&gt; &lt;a href="https://github.com/ohmyzsh/ohmyzsh/tree/master/plugins/git"&gt;Git plugin&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4.&lt;/td&gt;
 &lt;td&gt;559&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;gco&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;git checkout&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5.&lt;/td&gt;
 &lt;td&gt;518&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;git&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6.&lt;/td&gt;
 &lt;td&gt;384&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;./python.exe&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Python &lt;a href="https://devguide.python.org/"&gt;built from source&lt;/a&gt; on macOS&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7.&lt;/td&gt;
 &lt;td&gt;365&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;cd&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;8.&lt;/td&gt;
 &lt;td&gt;359&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;cat&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;a href="https://github.com/sharkdp/bat"&gt;&lt;code&gt;bat -p&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;9.&lt;/td&gt;
 &lt;td&gt;336&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;gs&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;scmpuff_status&lt;/code&gt; via &lt;a href="https://mroth.github.io/scmpuff/"&gt;scmpuff&lt;/a&gt; to give numeric shortcuts for files&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;10.&lt;/td&gt;
 &lt;td&gt;326&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;gl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;git log&lt;/code&gt; via Oh My Zsh Git plugin&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;11.&lt;/td&gt;
 &lt;td&gt;315&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;gb&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;git branch&lt;/code&gt; via Oh My Zsh Git plugin&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;12.&lt;/td&gt;
 &lt;td&gt;308&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;p&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;python&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;13.&lt;/td&gt;
 &lt;td&gt;263&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ga&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;git add&lt;/code&gt; via Oh My Zsh Git plugin&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;14.&lt;/td&gt;
 &lt;td&gt;227&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;l&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;ls -lah&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;15.&lt;/td&gt;
 &lt;td&gt;214&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://docs.astral.sh/uv/"&gt;Python package manager&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;16.&lt;/td&gt;
 &lt;td&gt;203&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;f&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;find . | rg&lt;/code&gt; (TODO: learn &lt;a href="https://github.com/sharkdp/fd"&gt;&lt;code&gt;fd&lt;/code&gt;&lt;/a&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;17.&lt;/td&gt;
 &lt;td&gt;189&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;python&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;18.&lt;/td&gt;
 &lt;td&gt;175&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;gd&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;git diff&lt;/code&gt; via Oh My Zsh Git plugin&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;19.&lt;/td&gt;
 &lt;td&gt;174&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;rm&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;20.&lt;/td&gt;
 &lt;td&gt;165&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;em&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;the &lt;a href="https://github.com/hugovk/em-keyboard"&gt;CLI emoji keyboard&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;21.&lt;/td&gt;
 &lt;td&gt;164&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;gh&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;GH_PAGER=&amp;quot;less -FRX&amp;quot; gh&lt;/code&gt;, mostly for &lt;code&gt;gh co &amp;lt;PR number&amp;gt;&lt;/code&gt;, sometimes &lt;code&gt;gh cache delete --all&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;22.&lt;/td&gt;
 &lt;td&gt;145&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;open&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;23.&lt;/td&gt;
 &lt;td&gt;120&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;mv&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;24.&lt;/td&gt;
 &lt;td&gt;120&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;make&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;for building Python, docs, devguide and PEPs&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;25.&lt;/td&gt;
 &lt;td&gt;117&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;cp&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;26.&lt;/td&gt;
 &lt;td&gt;116&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;tox&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;27.&lt;/td&gt;
 &lt;td&gt;116&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pypi&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;see below&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;28.&lt;/td&gt;
 &lt;td&gt;111&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;bc&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;/usr/local/bin/bcompare&lt;/code&gt; &lt;a href="https://www.scootersoftware.com/"&gt;Beyond Compare&lt;/a&gt; diff tool&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;29.&lt;/td&gt;
 &lt;td&gt;110&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;piu&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;uv pip install --system -U&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;30.&lt;/td&gt;
 &lt;td&gt;110&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pcr&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aliased to &lt;code&gt;pre-commit run --all-files&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;pypi&lt;/code&gt; is a little function in my dotfiles for opening the PyPI page for a package,
either &lt;code&gt;pypi &amp;lt;package&amp;gt;&lt;/code&gt; or let it try and guess the name from the current directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pypi &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;python setup.py --name&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="nb"&gt;local&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	open https://pypi.org/project/&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The macOS/iTerm/Oh My Zsh command to get this list:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;history | awk '{print $2}' | sort | uniq --count | sort --numeric-sort --reverse | head -30&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo: Close-up the typebars of a Olympia Büromaschinenwerke A.G. Erfurt
typewriter
(&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;CC
BY-NC-SA 2.0&lt;/a&gt;
&lt;a href="https://www.flickr.com/photos/hugovk/1482906651/"&gt;Hugo van Kemenade&lt;/a&gt;).&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Free-threaded Python on GitHub Actions</title><link>https://hugovk.dev/blog/2025/free-threaded-python-on-github-actions/</link><pubDate>Tue, 25 Mar 2025 10:25:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/free-threaded-python-on-github-actions/</guid><description>&lt;p&gt;GitHub Actions now supports &lt;em&gt;experimental&lt;/em&gt; free-threaded CPython!&lt;/p&gt;
&lt;p&gt;There are three ways to add it to your test matrix:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;actions/setup-python: &lt;code&gt;t&lt;/code&gt; suffix&lt;/li&gt;
&lt;li&gt;actions/setup-uv: &lt;code&gt;t&lt;/code&gt; suffix&lt;/li&gt;
&lt;li&gt;actions/setup-python: &lt;code&gt;freethreaded&lt;/code&gt; variable&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="actionssetup-python-t-suffix" class="relative group"&gt;actions/setup-python: &lt;code&gt;t&lt;/code&gt; suffix &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#actionssetup-python-t-suffix" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Using &lt;a href="https://github.com/actions/setup-python#basic-usage"&gt;actions/setup-python&lt;/a&gt;, you
can add the &lt;code&gt;t&lt;/code&gt; suffix for Python versions 3.13 and higher: &lt;code&gt;3.13t&lt;/code&gt; and &lt;code&gt;3.14t&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is my preferred method, we can clearly see which versions are free-threaded and
it&amp;rsquo;s straightforward to test both regular and free-threaded builds.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&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="l"&gt;push, pull_request, workflow_dispatch]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.os }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.13&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.13t&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# add this!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.14&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.14t&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# add this!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;os&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="s2"&gt;&amp;#34;windows-latest&amp;#34;&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;#34;macos-latest&amp;#34;&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;#34;ubuntu-latest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allow-prereleases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# needed for 3.14&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python --version --version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -c &amp;#34;import sys; print(&amp;#39;sys._is_gil_enabled:&amp;#39;, sys._is_gil_enabled())&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -c &amp;#34;import sysconfig; print(&amp;#39;Py_GIL_DISABLED:&amp;#39;, sysconfig.get_config_var(&amp;#39;Py_GIL_DISABLED&amp;#39;))&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Regular builds will output something like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Python 3.14.0a6 (main, Mar 17 2025, 02:44:29) [GCC 13.3.0]
sys._is_gil_enabled: True
Py_GIL_DISABLED: 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And free-threaded builds will output something like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Python 3.14.0a6 experimental free-threading build (main, Mar 17 2025, 02:44:30) [GCC 13.3.0]
sys._is_gil_enabled: False
Py_GIL_DISABLED: 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For example:
&lt;a href="https://github.com/hugovk/test/actions/runs/14057185035"&gt;hugovk/test/actions/runs/14057185035&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="actionssetup-uv-t-suffix" class="relative group"&gt;actions/setup-uv: &lt;code&gt;t&lt;/code&gt; suffix &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#actionssetup-uv-t-suffix" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Similarly, you can install uv with
&lt;a href="https://github.com/astral-sh/setup-uv#python-version"&gt;astral/setup-uv&lt;/a&gt; and use that to
set up free-threaded Python using the &lt;code&gt;t&lt;/code&gt; suffix.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&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="l"&gt;push, pull_request, workflow_dispatch]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.os }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.13&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.13t&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# add this!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.14&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3.14t&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# add this!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;os&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="s2"&gt;&amp;#34;windows-latest&amp;#34;&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;#34;macos-latest&amp;#34;&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;#34;ubuntu-latest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;astral-sh/setup-uv@v5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# change this!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enable-cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# only needed for this example with no dependencies&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python --version --version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -c &amp;#34;import sys; print(&amp;#39;sys._is_gil_enabled:&amp;#39;, sys._is_gil_enabled())&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -c &amp;#34;import sysconfig; print(&amp;#39;Py_GIL_DISABLED:&amp;#39;, sysconfig.get_config_var(&amp;#39;Py_GIL_DISABLED&amp;#39;))&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For example:
&lt;a href="https://github.com/hugovk/test/actions/runs/13967959519"&gt;hugovk/test/actions/runs/13967959519&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="actionssetup-python-freethreaded-variable" class="relative group"&gt;actions/setup-python: &lt;code&gt;freethreaded&lt;/code&gt; variable &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#actionssetup-python-freethreaded-variable" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Back to actions/setup-python, you can also set the &lt;code&gt;freethreaded&lt;/code&gt; variable for &lt;code&gt;3.13&lt;/code&gt;
and higher.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&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="l"&gt;push, pull_request, workflow_dispatch]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.os }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.13&amp;#34;&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;#34;3.14&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;os&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="s2"&gt;&amp;#34;windows-latest&amp;#34;&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;#34;macos-latest&amp;#34;&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;#34;ubuntu-latest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allow-prereleases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# needed for 3.14&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;freethreaded&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# add this!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python --version --version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -c &amp;#34;import sys; print(&amp;#39;sys._is_gil_enabled:&amp;#39;, sys._is_gil_enabled())&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -c &amp;#34;import sysconfig; print(&amp;#39;Py_GIL_DISABLED:&amp;#39;, sysconfig.get_config_var(&amp;#39;Py_GIL_DISABLED&amp;#39;))&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For example:
&lt;a href="https://github.com/hugovk/test/actions/runs/39359291708"&gt;hugovk/test/actions/runs/39359291708&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="python_gil0" class="relative group"&gt;&lt;code&gt;PYTHON_GIL=0&lt;/code&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#python_gil0" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;And you may want to set &lt;code&gt;PYTHON_GIL=0&lt;/code&gt; to force Python to keep the GIL disabled, even
after importing a module that doesn&amp;rsquo;t support running without it.&lt;/p&gt;
&lt;p&gt;See
&lt;a href="https://py-free-threading.github.io/running-gil-disabled/"&gt;Running Python with the GIL Disabled&lt;/a&gt;
for more info.&lt;/p&gt;
&lt;p&gt;With the &lt;code&gt;t&lt;/code&gt; suffix:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set PYTHON_GIL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;endsWith(matrix.python-version, &amp;#39;t&amp;#39;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;PYTHON_GIL=0&amp;#34; &amp;gt;&amp;gt; &amp;#34;$GITHUB_ENV&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With the &lt;code&gt;freethreaded&lt;/code&gt; variable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set PYTHON_GIL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&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;#34;${{ matrix.freethreaded }}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;PYTHON_GIL=0&amp;#34; &amp;gt;&amp;gt; &amp;#34;$GITHUB_ENV&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="please-test" class="relative group"&gt;Please test! &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#please-test" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;For free-threaded Python to succeed and become the default, it&amp;rsquo;s essential there is
ecosystem and community support. Library maintainers: please test it and where needed,
adapt your code, and publish
&lt;a href="https://hugovk.github.io/free-threaded-wheels/"&gt;free-threaded wheels&lt;/a&gt; so others can
test &lt;em&gt;their&lt;/em&gt; code that depends on &lt;em&gt;yours&lt;/em&gt;. Everyone else: please test your code too!&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../../2023/help-us-test-free-threaded-python-without-the-gil/"&gt;Help us test free-threaded Python without the GIL&lt;/a&gt;
for other ways to test and how to check your build&lt;/li&gt;
&lt;li&gt;&lt;a href="https://py-free-threading.github.io/"&gt;Python free-threading guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/setup-python/pull/973"&gt;actions/setup-python#973&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/setup-python/releases/tag/v5.5.0"&gt;actions/setup-python@v5.5.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/library-company-of-philadelphia/9354604392/"&gt;Spinning
Room, Winding Bobbins with Woolen Yarn for Weaving, Philadelphia, PA&lt;/a&gt;&amp;rdquo; by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/library-company-of-philadelphia/"&gt;Library
Company of Philadelphia&lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Improving licence metadata</title><link>https://hugovk.dev/blog/2025/improving-licence-metadata/</link><pubDate>Fri, 14 Feb 2025 15:11:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/improving-licence-metadata/</guid><description>&lt;h2 id="what" class="relative group"&gt;What? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#what" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://peps.python.org/pep-0639/"&gt;PEP 639&lt;/a&gt; defines a spec on how to document licences
used in Python projects.&lt;/p&gt;
&lt;p&gt;Instead of using a &lt;a href="https://pypi.org/classifiers/"&gt;Trove classifier&lt;/a&gt; such as &amp;ldquo;License ::
OSI Approved :: BSD License&amp;rdquo;, which is imprecise (for example, which
&lt;a href="https://en.wikipedia.org/wiki/BSD_licenses"&gt;BSD licence&lt;/a&gt;?), the
&lt;a href="https://spdx.github.io/spdx-spec/v2.2.2/SPDX-license-expressions/"&gt;SPDX licence expression syntax&lt;/a&gt;
is used.&lt;/p&gt;
&lt;h2 id="how" class="relative group"&gt;How? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;h3 id="pyprojecttoml" class="relative group"&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pyprojecttoml" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Change &lt;code&gt;pyproject.toml&lt;/code&gt; as follows.&lt;/p&gt;
&lt;p&gt;I usually use Hatchling as a build backend, and support was added in 1.27:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [build-system]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; build-backend = &amp;#34;hatchling.build&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; requires = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;hatch-vcs&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- &amp;#34;hatchling&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ &amp;#34;hatchling&amp;gt;=1.27&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ]
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Replace the freeform &lt;code&gt;license&lt;/code&gt; field with a valid SPDX license expression, and add
&lt;code&gt;license-files&lt;/code&gt; which points to the licence files in the repo. There&amp;rsquo;s often only one,
but if you have more than one, list them all:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [project]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-license = { text = &amp;#34;MIT&amp;#34; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+license = &amp;#34;MIT&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+license-files = [ &amp;#34;LICENSE&amp;#34; ]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Optionally delete the deprecated licence classifier:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; classifiers = [
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;Development Status :: 5 - Production/Stable&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;Intended Audience :: Developers&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- &amp;#34;License :: OSI Approved :: MIT License&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;Operating System :: OS Independent&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For example, see &lt;a href="https://github.com/python-humanize/humanize/pull/236"&gt;humanize#236&lt;/a&gt;
and &lt;a href="https://github.com/prettytable/prettytable/pull/350"&gt;prettytable#350&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="upload" class="relative group"&gt;Upload &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#upload" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Then make sure to use a PyPI uploader that supports this.&lt;/p&gt;
&lt;p&gt;I recommend using &lt;a href="https://docs.pypi.org/trusted-publishers/"&gt;Trusted Publishing&lt;/a&gt; which
I use with &lt;a href="https://github.com/pypa/gh-action-pypi-publish"&gt;pypa/gh-action-pypi-publish&lt;/a&gt;
to deploy from GitHub Actions. I didn&amp;rsquo;t need to make any changes here, just make a
release as usual.&lt;/p&gt;
&lt;h2 id="result" class="relative group"&gt;Result &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#result" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;h3 id="pypi" class="relative group"&gt;PyPI &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pypi" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;PyPI &lt;a href="https://pypi.org/project/prettytable/3.13.0/"&gt;shows the new metadata&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 
 srcset="https://hugovk.dev/blog/2025/improving-licence-metadata/pypi-licence_hu_90fd56d8bbd079d1.webp"
 
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="586"
 height="372"
 class="mx-auto my-0 rounded-md"
 alt="Screenshot of PyPI showing licence expression: BSD-3-Clause"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/improving-licence-metadata/pypi-licence.png"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pip" class="relative group"&gt;pip &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pip" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;pip can also show you the metadata:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip install &lt;span class="nv"&gt;prettytable&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;3.13.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pip show prettytable
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Name: prettytable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Version: 3.13.0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;License-Expression: BSD-3-Clause
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Location: /Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/site-packages
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Requires: wcwidth
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Required-by: norwegianblue, pypistats
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="thank-you" class="relative group"&gt;Thank you! &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thank-you" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;A lot of work went into this. Thank you to PEP authors
&lt;a href="https://github.com/pombredanne"&gt;Philippe Ombredanne&lt;/a&gt; for creating the first draft in
2019, to &lt;a href="https://github.com/cam-gerlach"&gt;C.A.M. Gerlach&lt;/a&gt; for the second draft in 2021,
and especially to &lt;a href="https://karolinasurma.eu/"&gt;Karolina Surma&lt;/a&gt; for getting the third
draft over the finish line and helping with the implementation.&lt;/p&gt;
&lt;p&gt;And many projects were updated to support this, thanks to the maintainers and
contributors of at least:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pypi/warehouse/issues/16620"&gt;PyPI/Warehouse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://packaging.pypa.io/en/stable/changelog.html"&gt;packaging 24.2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hatch.pypa.io/dev/history/hatchling/"&gt;Hatchling 1.27&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twine.readthedocs.io/en/stable/changelog.html"&gt;Twine 6.1.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pypa/gh-action-pypi-publish/releases/tag/v1.12.4"&gt;PyPI publish GitHub Action v1.12.4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hynek/build-and-inspect-python-package/releases/tag/v2.12.0"&gt;build-and-inspect-python-package v2.12.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ichard26.github.io/blog/2025/01/whats-new-in-pip-25.0/"&gt;pip 25.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/sdasmarchives/49801923846/"&gt;Amelia
Earhart&amp;rsquo;s 1932 pilot licence&lt;/a&gt; in the
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/sdasmarchives/"&gt;San
Diego Air and Space Museum Archive&lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>I'm excited to join the Sovereign Tech Fellowship</title><link>https://hugovk.dev/blog/2025/im-excited-to-join-the-sovereign-tech-fellowship/</link><pubDate>Thu, 06 Feb 2025 11:40:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/im-excited-to-join-the-sovereign-tech-fellowship/</guid><description>&lt;p&gt;For the duration of 2025, I&amp;rsquo;m thrilled to join the
&lt;a href="https://www.sovereign.tech/programs/fellowship"&gt;Sovereign Tech Fellowship for Maintainers&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;This is a pilot programme from the &lt;a href="https://www.sovereign.tech/"&gt;Sovereign Tech Agency&lt;/a&gt;
to pay maintainers of critical open source technologies in the public interest.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m one of six participants in this first cohort and I&amp;rsquo;ll be working on Python, with a
focus on &lt;a href="https://github.com/python/cpython"&gt;CPython&lt;/a&gt;, including as release manager for
&lt;a href="https://peps.python.org/pep-0745/"&gt;Python 3.14&lt;/a&gt; (under development with full release
due in October 2025, supported until 2030) and the next one (development starts in May
2025 for release in October 2026, supported until 2031).&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m delighted to work with the Sovereign Tech Agency as they really get open source.
Supported by Germany&amp;rsquo;s Ministry for Economic Affairs and Climate Action, they recognise
the importance of open source and its place as critical digital infrastructure in the
public interest. In their &lt;a href="https://www.sovereign.tech/mission"&gt;own words&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Sovereign Tech Agency supports the development, improvement, and maintenance of
open digital infrastructure. Our goal is to sustainably strengthen the open source
ecosystem. We focus on security, resilience, technological diversity, and the people
behind the code.&lt;/p&gt;
&lt;p&gt;We often don’t notice how much our lives depend on digital infrastructure until it
stops working. But making it available, accessible and secure is key for
digitalization in the public interest. The Sovereign Tech Agency invests in open
digital infrastructure. It is critical for innovation and economic growth, and forms
the foundation for digitalization across sectors.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;In addition to CPython core development and release management, I&amp;rsquo;ll continue to
maintain other Python projects such as &lt;a href="https://pillow.readthedocs.io/"&gt;Pillow&lt;/a&gt;,
&lt;a href="https://github.com/python-humanize/humanize"&gt;humanize&lt;/a&gt; and
&lt;a href="https://github.com/termcolor/termcolor"&gt;termcolor&lt;/a&gt;, and CLI utilities such as
&lt;a href="https://github.com/hugovk/norwegianblue"&gt;norwegianblue&lt;/a&gt;,
&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt; and
&lt;a href="https://github.com/hugovk/pepotron"&gt;pepotron&lt;/a&gt;, and help out more widely in the Python
ecosystem.&lt;/p&gt;
&lt;p&gt;Tasks include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bug fixing, issue triaging, PR review and security improvements&lt;/li&gt;
&lt;li&gt;Mentoring new contributors to foster community growth and diversity&lt;/li&gt;
&lt;li&gt;Communicating updates to the wider community via &lt;a href="https://hugovk.dev/"&gt;blog posts&lt;/a&gt; and
&lt;a href="https://mastodon.social/@hugovk"&gt;social&lt;/a&gt; &lt;a href="https://bsky.app/profile/hugovk.dev"&gt;media&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/python/core-workflow"&gt;Workflow&lt;/a&gt;,
&lt;a href="https://github.com/python/cpython/actions/"&gt;CI&lt;/a&gt;, and infrastructure optimisations to
streamline core development tasks&lt;/li&gt;
&lt;li&gt;Maintenance and improvement of &lt;a href="http://peps.python.org/"&gt;PEP&lt;/a&gt; editing infrastructure
and related documentation tools&lt;/li&gt;
&lt;li&gt;Automation and &lt;a href="https://hugovk.github.io/dashboard/"&gt;dashboard&lt;/a&gt; creation for triage
scripts to assist other contributors&lt;/li&gt;
&lt;li&gt;Process and workflow improvements for CI and core contributor environments&lt;/li&gt;
&lt;li&gt;Ongoing documentation enhancements, including hosting monthly
&lt;a href="https://docs-community.readthedocs.io/en/latest/"&gt;Documentation Working Group&lt;/a&gt;
meetings, improving build processes, and focusing on accessibility&lt;/li&gt;
&lt;li&gt;Updating and modernising projects for compatibility with the latest Python versions&lt;/li&gt;
&lt;li&gt;Implementing stronger security measures and using
&lt;a href="https://docs.pypi.org/trusted-publishers/"&gt;Trusted Publishing&lt;/a&gt; and
&lt;a href="https://docs.pypi.org/attestations/"&gt;digital attestations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Adding and maintaining Python support for widely used PyPI packages&lt;/li&gt;
&lt;li&gt;Improving community-wide tooling, documentation, and accessibility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Meet my
&lt;a href="https://www.sovereign.tech/news/meet-the-sovereign-tech-fellows"&gt;fellow Sovereign Tech Fellows&lt;/a&gt;,
and by the way, the Sovereign Tech Agency is currently hiring, check out their
&lt;a href="https://www.sovereign.tech/jobs"&gt;open positions&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>How to delay a Python release</title><link>https://hugovk.dev/blog/2025/how-to-delay-a-python-release/</link><pubDate>Tue, 04 Feb 2025 18:55:00 +0000</pubDate><guid>https://hugovk.dev/blog/2025/how-to-delay-a-python-release/</guid><description>&lt;h2 id="prologue" class="relative group"&gt;Prologue &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#prologue" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;This was a &lt;a href="https://www.wikihow.com/Delete-a-Twitter-Account"&gt;Twitter&lt;/a&gt;
&lt;a href="https://web.archive.org/web/20220115150826/https://twitter.com/hugovk/status/1482367773577822208"&gt;thread from 15th January 2022&lt;/a&gt;
about my first CPython bug. Eight days from report to fix to merge, not bad!&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="delay" class="relative group"&gt;Delay &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#delay" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I helped
&lt;a href="https://web.archive.org/web/20220114231910/https://twitter.com/pyblogsal/status/1482128909286227972"&gt;delay the release of Python 3.11.0a4&lt;/a&gt;!
But in a good way! 😇&lt;/p&gt;
&lt;p&gt;Python 3.11 is due out in October, but they make early alpha, beta and release
candidates available for people to help test Python itself and their own code before the
big release.&lt;/p&gt;
&lt;p&gt;So I tested &lt;a href="https://fosstodon.org/@pillow"&gt;Pillow&lt;/a&gt;&amp;hellip;&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_c71a8d5d36df6dea.webp 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_d3ffbe8eca2126f3.webp 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_a1d09cfbe34a4759.webp 1024w
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_81c5022f200cf688.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1510"
 height="1236"
 class="mx-auto my-0 rounded-md"
 alt="Release manager Pablo tweeting: Yeah, no kidding! 😵‍💫 3.11.0a4 had a ridiculous amount of release blockers 🧐 we needed to solve and while everything was on fire we discovered something that made us make an early 3.10.2 release 🤯(you should upgrade: read the release notes for more info⚠️)! What a week 😅"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_2e31bbceab84da82.png" srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_5878dfa92949d8df.png 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_2e31bbceab84da82.png 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_87eaf8f0fc13934.png 1024w
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_9feca2ddccdcd4a.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="tests" class="relative group"&gt;Tests &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#tests" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The Pillow test suite passed with 3.11 ✅&lt;/p&gt;
&lt;p&gt;Next I tried building the documentation with 3.11 ❌&lt;/p&gt;
&lt;p&gt;The docs program, Sphinx, emitted a couple of warnings. Warnings are often missed
because they don&amp;rsquo;t error. But luckily we use the &amp;ldquo;-W&amp;rdquo; option to turn warnings into hard
errors.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_64373a4a09b61b60.webp 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_d123b6abb90873dc.webp 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_369b70cea750330.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_c8cf603c8b458b54.webp 1200w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1200"
 height="885"
 class="mx-auto my-0 rounded-md"
 alt="Sphinx output showing &amp;ldquo;WARNING: image file not readable&amp;rdquo;"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_f71d9b786b6d60c.jpg" srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_acf54886bd5ef20f.jpg 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_f71d9b786b6d60c.jpg 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_bfd7c0371f39ff5f.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1.jpg 1200w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;







&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_124963749b02ac9e.webp 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_d9295cecac675757.webp 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_fa1ca5bd5d2b1c31.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_fec4723464028e6f.webp 1082w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1082"
 height="1136"
 class="mx-auto my-0 rounded-md"
 alt="Sphinx output turning the warning into an error"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_8e75217336a5e63b.jpg" srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_f8f8f648222ac536.jpg 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_8e75217336a5e63b.jpg 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_cf46a0ad9ed92dcb.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2.jpg 1082w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="sphinx" class="relative group"&gt;Sphinx &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#sphinx" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Maybe Sphinx isn&amp;rsquo;t ready for Python 3.11?&lt;/p&gt;
&lt;p&gt;Rather than submitting a report with the full Pillow documentation (lots of files) I
made a
&lt;a href="https://github.com/hugovk/test/tree/3.11-sphinx"&gt;new, &amp;ldquo;minimal&amp;rdquo; example with just enough stuff to reproduce it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This makes it easier to investigate what&amp;rsquo;s up.&lt;/p&gt;
&lt;h2 id="report-1" class="relative group"&gt;Report 1 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#report-1" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I &lt;a href="https://github.com/sphinx-doc/sphinx/issues/10030"&gt;reported this to Sphinx&lt;/a&gt;. The
problem was that a page in a subdirectory could load an image from one directory, but
not from another, further away directory.&lt;/p&gt;
&lt;p&gt;It occurs for the Python 3.11.0a3 alpha, but not 3.7-3.10.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_9e9815c09e43dd3d.webp 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_c930d3745ebf2387.webp 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_db234ca665a369ad.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_5ffef8a2fd350dc.webp 1044w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1044"
 height="682"
 class="mx-auto my-0 rounded-md"
 alt="A screenshot of a GitHub bug report"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_f547bcb017dccb90.jpg" srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_145d2fae5cded557.jpg 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_f547bcb017dccb90.jpg 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_6eb54c1ec9b797.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3.jpg 1044w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="cpython" class="relative group"&gt;CPython &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#cpython" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;A few hours later the Sphinx maintainer Takeshi said it looks like a change to a part of
Python itself &amp;ndash; &lt;code&gt;os.path.normpath()&lt;/code&gt; &amp;ndash; since 3.11.0a3, and as it wasn&amp;rsquo;t mentioned on
the &lt;a href="https://docs.python.org/3/whatsnew/3.11.html"&gt;&amp;ldquo;What&amp;rsquo;s New in Python 3.11&amp;rdquo;&lt;/a&gt; page it
could be a bug in Python.&lt;/p&gt;
&lt;p&gt;He asked me to report it to Python.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_77bbc7abf73c2a7.webp 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_5d3fdac3badeec70.webp 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_43d7374d0f7b78fc.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_cdc5089b58afc047.webp 1200w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1200"
 height="824"
 class="mx-auto my-0 rounded-md"
 alt="Screenshot of Takeshi&amp;rsquo;s reply"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_34ab524685998fa3.jpg" srcset="https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_b029bb435170c2a2.jpg 330w,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_34ab524685998fa3.jpg 660w
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_6c3cdab9893df6c.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4.jpg 1200w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="report-2" class="relative group"&gt;Report 2 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#report-2" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I &lt;a href="https://bugs.python.org/issue46208"&gt;reported it to Python&lt;/a&gt; with Takeshi&amp;rsquo;s even more
minimal example.&lt;/p&gt;
&lt;p&gt;Half an hour later Christian pointed out a change which may have caused this.&lt;/p&gt;
&lt;p&gt;I tested and confirmed.&lt;/p&gt;
&lt;p&gt;The next day Steve confirmed it was a bug and set it as a &amp;ldquo;release blocker&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="fix" class="relative group"&gt;Fix &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#fix" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Steve also said it will needs tests, because this bug slipped out due to a gap in
testing.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t know how to fix the bug, but I could write some test cases!&lt;/p&gt;
&lt;p&gt;neonene then took the tests and
&lt;a href="https://github.com/python/cpython/pull/30362"&gt;fixed the bug&lt;/a&gt;! In doing so they found
even more bugs!&lt;/p&gt;
&lt;h2 id="merge" class="relative group"&gt;Merge &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#merge" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;These extra bugs also existed in earlier versions.&lt;/p&gt;
&lt;p&gt;But it turns out path handling can get pretty complicated in places, so Steve decided to
only fix my bug now to get it released and the others can be sorted later.&lt;/p&gt;
&lt;p&gt;The fix was merged and I confirmed it also worked with Sphinx ✅&lt;/p&gt;
&lt;h2 id="conclusion" class="relative group"&gt;Conclusion &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#conclusion" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;And that&amp;rsquo;s about it!&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s now fixed in 3.11.0a4; much better to find these before 3.11.0 final is released to
the world in October. Along the way we found more issues to address.&lt;/p&gt;
&lt;p&gt;Short version: test your code with 3.11 now, you may find issues in your code or in
Python itself 🚀&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="epilogue" class="relative group"&gt;Epilogue &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#epilogue" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Back to 2025: Please test and delay
&lt;a href="https://discuss.python.org/tag/release"&gt;Python 3.14 alpha&lt;/a&gt; &amp;ndash; but in a good way! 😇&lt;/p&gt;</description></item><item><title>A surprising thing about PyPI's BigQuery data</title><link>https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/</link><pubDate>Sun, 24 Nov 2024 20:45:31 +0000</pubDate><guid>https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/</guid><description>&lt;p&gt;You can get download numbers for PyPI packages (or projects) from a
&lt;a href="https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/"&gt;Google BigQuery dataset&lt;/a&gt;.
You need a Google account and credentials, and Google gives 1 TiB of free quota per
month.&lt;/p&gt;
&lt;p&gt;Each month, I have automation to fetch the download numbers for the 8,000 most popular
packages over the past 30 days, and make it available as more accessible JSON and CSV
files at &lt;a href="https://hugovk.github.io/top-pypi-packages/"&gt;Top PyPI Packages&lt;/a&gt;. This data is
&lt;a href="https://hugovk.github.io/top-pypi-packages/#users"&gt;widely used&lt;/a&gt; for
&lt;a href="https://github.com/hugovk/top-pypi-packages/issues/23"&gt;research in academia and industry&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However, as more packages and releases are uploaded to PyPI, and there are more and more
downloads logged, the amount of billed data increases too.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_4ab0d41d150e25eb.webp 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_2e9aeff679c5823a.webp 660w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_b5e55ba342907433.webp 828w
 
 
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_b5e55ba342907433.webp 828w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="828"
 height="502"
 class="mx-auto my-0 rounded-md"
 alt="BigQuery TB billed"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_9e622079538413b7.png" srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_70473e0aca729d1a.png 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_9e622079538413b7.png 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee.png 828w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee.png 828w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;This chart shows the amount of data billed per month.&lt;/p&gt;
&lt;p&gt;At first, I was only collecting downloads data for 4,000 packages, and it was fetched
for two queries: downloads over 365 days and over 30 days. But as time passed, it
started using up too much quota to download data for 365 days.&lt;/p&gt;
&lt;p&gt;So I ditched the 365-day data, and increased the 30-day data from 4,000 to 5,000
packages. Later, I checked how much quota was being used and increased from 5,000
packages to 8,000 packages.&lt;/p&gt;
&lt;p&gt;But then I exceeded the BigQuery monthly quota of 1 TiB fetching data for July 2024.&lt;/p&gt;
&lt;p&gt;To fetch the missing data and investigate what&amp;rsquo;s going in, I started Google Cloud&amp;rsquo;s
90-day, $300 (€277.46) free-trial 💸&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I found!&lt;/p&gt;
&lt;h2 id="finding-it-costs-more-to-get-data-for-downloads-from-only-pip-than-from-all-installers" class="relative group"&gt;Finding: it costs more to get data for downloads from only pip than from all installers &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#finding-it-costs-more-to-get-data-for-downloads-from-only-pip-than-from-all-installers" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I use the &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; client to help query BigQuery. By
default, it only fetches downloads for pip.&lt;/p&gt;
&lt;h3 id="only-pip" class="relative group"&gt;Only pip &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#only-pip" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;This command gets one day&amp;rsquo;s download data for the top 10 packages, &lt;em&gt;for pip only&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pypinfo --limit &lt;span class="m"&gt;10&lt;/span&gt; --days &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; project
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Served from cache: False
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Data processed: 58.21 GiB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Data billed: 58.21 GiB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Estimated cost: $0.29
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Results:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;project&lt;/th&gt;
 &lt;th style="text-align: right"&gt;download count&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;boto3&lt;/td&gt;
 &lt;td style="text-align: right"&gt;37,251,744&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;aiobotocore&lt;/td&gt;
 &lt;td style="text-align: right"&gt;16,252,824&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;urllib3&lt;/td&gt;
 &lt;td style="text-align: right"&gt;16,243,278&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;botocore&lt;/td&gt;
 &lt;td style="text-align: right"&gt;15,687,125&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;requests&lt;/td&gt;
 &lt;td style="text-align: right"&gt;13,271,314&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;s3fs&lt;/td&gt;
 &lt;td style="text-align: right"&gt;12,865,055&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;s3transfer&lt;/td&gt;
 &lt;td style="text-align: right"&gt;12,014,278&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;fsspec&lt;/td&gt;
 &lt;td style="text-align: right"&gt;11,982,305&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;charset-normalizer&lt;/td&gt;
 &lt;td style="text-align: right"&gt;11,684,740&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;certifi&lt;/td&gt;
 &lt;td style="text-align: right"&gt;11,639,584&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;strong&gt;158,892,247&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="all-installers" class="relative group"&gt;All installers &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#all-installers" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Adding the &lt;code&gt;--all&lt;/code&gt; flag gets one day&amp;rsquo;s download data for the top 10 packages, &lt;em&gt;for all
installers&lt;/em&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pypinfo --all --limit &lt;span class="m"&gt;10&lt;/span&gt; --days &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; project
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Served from cache: False
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Data processed: 46.63 GiB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Data billed: 46.63 GiB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Estimated cost: $0.23
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;project&lt;/th&gt;
 &lt;th style="text-align: right"&gt;download count&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;boto3&lt;/td&gt;
 &lt;td style="text-align: right"&gt;39,495,624&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;botocore&lt;/td&gt;
 &lt;td style="text-align: right"&gt;17,281,187&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;urllib3&lt;/td&gt;
 &lt;td style="text-align: right"&gt;17,225,121&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;aiobotocore&lt;/td&gt;
 &lt;td style="text-align: right"&gt;16,430,826&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;requests&lt;/td&gt;
 &lt;td style="text-align: right"&gt;14,287,965&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;s3fs&lt;/td&gt;
 &lt;td style="text-align: right"&gt;12,958,516&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;charset-normalizer&lt;/td&gt;
 &lt;td style="text-align: right"&gt;12,781,405&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;certifi&lt;/td&gt;
 &lt;td style="text-align: right"&gt;12,647,098&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;setuptools&lt;/td&gt;
 &lt;td style="text-align: right"&gt;12,608,120&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;idna&lt;/td&gt;
 &lt;td style="text-align: right"&gt;12,510,335&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;strong&gt;168,226,197&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;So we can see the default pip-only costs an extra 25% data processed and data billed,
and costs an extra 25% in dollars.&lt;/p&gt;
&lt;p&gt;Unsurprisingly, the actual download counts are higher for all installers. The ranking
has changed a bit, but I expect we&amp;rsquo;re still getting more-or-less the same packages in
the top thousands of results.&lt;/p&gt;
&lt;h3 id="queries" class="relative group"&gt;Queries &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#queries" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;It sends a query like this to BigQuery for only pip:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="n"&gt;bigquery&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pypi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_downloads&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TIMESTAMP_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;TIMESTAMP_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="n"&gt;details&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;installer&lt;/span&gt;&lt;span class="p"&gt;.&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;#34;pip&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And for all installers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="n"&gt;bigquery&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pypi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_downloads&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TIMESTAMP_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;TIMESTAMP_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These queries are the same, except the default has an extra
&lt;code&gt;AND details.installer.name = &amp;quot;pip&amp;quot;&lt;/code&gt; condition. It seems reasonable it would cost more
to do extra filtering work.&lt;/p&gt;
&lt;h3 id="installers" class="relative group"&gt;Installers &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#installers" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Let&amp;rsquo;s look at the installers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; pypinfo --all --limit &lt;span class="m"&gt;100&lt;/span&gt; --days &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; installer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Served from cache: False
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Data processed: 29.49 GiB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Data billed: 29.49 GiB
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Estimated cost: $0.15
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;installer name&lt;/th&gt;
 &lt;th style="text-align: right"&gt;download count&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;pip&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1,121,198,711&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;uv&lt;/td&gt;
 &lt;td style="text-align: right"&gt;117,194,833&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;requests&lt;/td&gt;
 &lt;td style="text-align: right"&gt;29,828,272&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;poetry&lt;/td&gt;
 &lt;td style="text-align: right"&gt;23,009,454&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;td style="text-align: right"&gt;8,916,745&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;bandersnatch&lt;/td&gt;
 &lt;td style="text-align: right"&gt;6,171,555&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;setuptools&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1,362,797&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Bazel&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1,280,271&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Browser&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1,096,328&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Nexus&lt;/td&gt;
 &lt;td style="text-align: right"&gt;593,230&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Homebrew&lt;/td&gt;
 &lt;td style="text-align: right"&gt;510,247&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Artifactory&lt;/td&gt;
 &lt;td style="text-align: right"&gt;69,063&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;pdm&lt;/td&gt;
 &lt;td style="text-align: right"&gt;62,904&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;OS&lt;/td&gt;
 &lt;td style="text-align: right"&gt;13,108&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;devpi&lt;/td&gt;
 &lt;td style="text-align: right"&gt;9,530&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;conda&lt;/td&gt;
 &lt;td style="text-align: right"&gt;2,272&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;pex&lt;/td&gt;
 &lt;td style="text-align: right"&gt;194&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;strong&gt;1,311,319,514&lt;/strong&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;pip still by far the most popular, and unsurprising uv is up there too, with about 10%
of pip&amp;rsquo;s downloads.&lt;/p&gt;
&lt;p&gt;The others are about 25% or less of uv. A lot of them are mirroring services that we
wanted to exclude before.&lt;/p&gt;
&lt;p&gt;I think given uv&amp;rsquo;s importance, and my expectation that it will continue to take a bigger
share of the pie, plus especially the extra cost for filtering by just pip, means that
we should switch to fetching data for all downloaders. Plus the others don&amp;rsquo;t account for
that much of the pie.&lt;/p&gt;
&lt;h2 id="finding-the-number-of-packages-doesnt-affect-the-cost" class="relative group"&gt;Finding: the number of packages doesn&amp;rsquo;t affect the cost &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#finding-the-number-of-packages-doesnt-affect-the-cost" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;This was the biggest surprise. Earlier I&amp;rsquo;d been increasing or decreasing the number to
try and remain under quota. But it turns out it makes no difference how many packages
you query!&lt;/p&gt;
&lt;p&gt;I fetched data for just one day and all installers for different package limits: 1000,
2000, 3000, 4000, 5000, 6000, 7000, 8000. Sample query:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;COUNT&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="n"&gt;bigquery&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pypi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file_downloads&lt;/span&gt;&lt;span class="o"&gt;`&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BETWEEN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;TIMESTAMP_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&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="n"&gt;TIMESTAMP_ADD&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;CURRENT_TIMESTAMP&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;INTERVAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;LIMIT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_2a2a63e549db45d5.webp 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_7ca090941301c4a6.webp 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_2e901099213ea8fa.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_8752a6663c015321.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1440"
 height="870"
 class="mx-auto my-0 rounded-md"
 alt="Cost and bytes for 1 day with different package limits are the same"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_3f539365e34d393d.png" srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_9d5fdbbd8a572f56.png 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_3f539365e34d393d.png 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_9897b458b229b.png 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_33e9d3f4013f1b99.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Interestingly, the cost is the same for all limits (1000-8000): $0.31.&lt;/p&gt;
&lt;p&gt;Repeating with one day but filtering for pip only:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_b397b72181457d9d.webp 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_ed5bc2aeeb677c0f.webp 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_b5d35fcd7c1b84fe.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_f70325019a5c34a5.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1444"
 height="866"
 class="mx-auto my-0 rounded-md"
 alt="Cost and bytes for 1 day still the same for pip"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_1ec97c57058e8a0c.png" srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_945320b5966272b2.png 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_1ec97c57058e8a0c.png 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_c4041c02334b303.png 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_cada408b11e7346e.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Cost increased to $0.39 but again the same for all limits.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s repeat with all installers, but for 30 days, and this time query in decreasing
limits, in case we were only paying for incremental changes: 8000, 7000, 6000, 5000,
4000, 3000, 2000, 1000:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_ade13aaf38beb79e.webp 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_aec596a50feeb9c5.webp 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_8df0048fa369488.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_953e8a4b5dbc6363.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1436"
 height="878"
 class="mx-auto my-0 rounded-md"
 alt="Cost and bytes for 30 day still the same no matter how many packages"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_98f267283c5874cf.png" srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_c91d52f917924c59.png 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_98f267283c5874cf.png 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_d23efe3409ba50bc.png 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_bd5d7e9013aaefad.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Again, the cost is the same regardless of package limit: $4.89 per query.&lt;/p&gt;
&lt;p&gt;Well then, let&amp;rsquo;s repeat with the limit increasing by powers of ten, up to 1,000,000!
This last one fetches data for all 531,022 packages on PyPI:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;limit&lt;/th&gt;
 &lt;th&gt;projects count&lt;/th&gt;
 &lt;th&gt;estimated cost&lt;/th&gt;
 &lt;th&gt;bytes billed&lt;/th&gt;
 &lt;th&gt;bytes processed&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;0.20&lt;/td&gt;
 &lt;td&gt;43,447,746,560&lt;/td&gt;
 &lt;td&gt;43,447,720,943&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;0.20&lt;/td&gt;
 &lt;td&gt;43,447,746,560&lt;/td&gt;
 &lt;td&gt;43,447,720,943&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;100&lt;/td&gt;
 &lt;td&gt;100&lt;/td&gt;
 &lt;td&gt;0.20&lt;/td&gt;
 &lt;td&gt;43,447,746,560&lt;/td&gt;
 &lt;td&gt;43,447,720,943&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1000&lt;/td&gt;
 &lt;td&gt;1,000&lt;/td&gt;
 &lt;td&gt;0.20&lt;/td&gt;
 &lt;td&gt;43,447,746,560&lt;/td&gt;
 &lt;td&gt;43,447,720,943&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;8000&lt;/td&gt;
 &lt;td&gt;8,000&lt;/td&gt;
 &lt;td&gt;0.20&lt;/td&gt;
 &lt;td&gt;43,447,746,560&lt;/td&gt;
 &lt;td&gt;43,447,720,943&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;10000&lt;/td&gt;
 &lt;td&gt;10,000&lt;/td&gt;
 &lt;td&gt;0.20&lt;/td&gt;
 &lt;td&gt;43,447,746,560&lt;/td&gt;
 &lt;td&gt;43,447,720,943&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;100000&lt;/td&gt;
 &lt;td&gt;100,000&lt;/td&gt;
 &lt;td&gt;0.20&lt;/td&gt;
 &lt;td&gt;43,447,746,560&lt;/td&gt;
 &lt;td&gt;43,447,720,943&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;1000000&lt;/td&gt;
 &lt;td&gt;531,022&lt;/td&gt;
 &lt;td&gt;0.20&lt;/td&gt;
 &lt;td&gt;43,447,746,560&lt;/td&gt;
 &lt;td&gt;43,447,720,943&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_737458bb7c81cba8.webp 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_83e6c71596df13d3.webp 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_616a9f6eb933c6af.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_2449ecc67ce7a3af.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1434"
 height="868"
 class="mx-auto my-0 rounded-md"
 alt="Still same flat cost and bytes for 1 or 10 or 1,000 or 1,000,000 packages"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_af29992fe0241de5.png" srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_6ca843176331eda4.png 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_af29992fe0241de5.png 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_2c1872e0d1284c97.png 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_e7b36c06a82419e.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Again, same cost, whether for 1 package or 531,022 packages!&lt;/p&gt;
&lt;h2 id="finding-the-number-of-days-affects-the-cost" class="relative group"&gt;Finding: the number of days affects the cost &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#finding-the-number-of-days-affects-the-cost" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;No surprise. I&amp;rsquo;d earlier noticed 365 days too took much quota, and I could continue with
30 days.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the estimated cost and bytes billed (for one package, all installers) between one
and 30 days (&lt;code&gt;f&amp;quot;pypinfo --all --json --indent 0 --days {days} --limit 1 '' project&amp;quot;&lt;/code&gt;),
showing a roughly linear increase:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_145248c7b32fcdf7.webp 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_5a1f43b16a5af3b9.webp 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_22806785ded6e284.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_766570250c718306.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1672"
 height="1028"
 class="mx-auto my-0 rounded-md"
 alt="Cost and bytes increase as the number of days increase"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_97ce72cf552dd812.png" srcset="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_d2aed121124600f3.png 330w,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_97ce72cf552dd812.png 660w
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_262e7c9ff66b5779.png 1024w
 
 
 ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_e4eac705f42fa756.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="conclusion" class="relative group"&gt;Conclusion &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#conclusion" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It doesn&amp;rsquo;t matter how many packages I fetch data for, I might as well fetch all and
make it available to everyone, depending on the size of the data file. It will make
sense to still offer a smaller file with 8,000 or so packages: often you just need a
large-ish yet manageable number.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It costs more to filter for only downloads from pip, so I&amp;rsquo;ve switched to fetching data
for all installers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The number of days affects the cost, so I will need to decrease this in the future to
stay within quota. For example, at some point I may need to switch from 30 to 25 days,
and later from 25 to 20 days.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More details from the investigation, the scripts and data files can be found at
&lt;a href="https://github.com/hugovk/top-pypi-packages/issues/36"&gt;hugovk/top-pypi-packages#36&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And let me know if you know any tricks to reduce costs!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/royalaustralianhistoricalsociety/44366270300/"&gt;The
Balancing Rock, Stonehenge, Near Glen Innes, NSW&lt;/a&gt;&amp;rdquo; by the
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/royalaustralianhistoricalsociety/"&gt;Royal
Australian Historical Society&lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Speed up CI with uv ⚡</title><link>https://hugovk.dev/blog/2024/speed-up-ci-with-uv/</link><pubDate>Sat, 02 Nov 2024 13:11:22 +0000</pubDate><guid>https://hugovk.dev/blog/2024/speed-up-ci-with-uv/</guid><description>&lt;p&gt;We can use &lt;a href="https://github.com/astral-sh/uv"&gt;uv&lt;/a&gt; to make linting and testing on GitHub
Actions around 1.5 times as fast.&lt;/p&gt;
&lt;h2 id="linting" class="relative group"&gt;Linting &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#linting" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;When using &lt;a href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt; for linting:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Lint&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&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="l"&gt;push, pull_request, workflow_dispatch]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FORCE_COLOR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persist-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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;#34;3.x&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pre-commit/action@v3.0.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can replace &lt;a href="https://github.com/pre-commit/action"&gt;pre-commit/action&lt;/a&gt; with
&lt;a href="https://github.com/tox-dev/action-pre-commit-uv"&gt;tox-dev/action-pre-commit-uv&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - uses: actions/setup-python@v5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; python-version: &amp;#34;3.x&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- cache: pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- - uses: pre-commit/action@v3.0.1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ - uses: tox-dev/action-pre-commit-uv@v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Lint&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&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="l"&gt;push, pull_request, workflow_dispatch]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FORCE_COLOR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persist-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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;#34;3.x&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tox-dev/action-pre-commit-uv@v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This means uv will create virtual environments and install packages for pre-commit,
which is faster for the initial seed operation when there&amp;rsquo;s no cache.&lt;/p&gt;
&lt;h3 id="lint-comparison" class="relative group"&gt;Lint comparison &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#lint-comparison" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;For example: &lt;a href="https://github.com/python/blurb/pull/32"&gt;python/blurb#32&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Before&lt;/th&gt;
 &lt;th&gt;After&lt;/th&gt;
 &lt;th&gt;Times faster&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;No cache&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635610765/job/32405339441"&gt;60s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635612034/job/32405343101"&gt;37s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;1.62&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;With cache&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635702058/job/32405618322"&gt;11s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635703339/job/32405622146"&gt;11s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;1.00&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="testing" class="relative group"&gt;Testing &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#testing" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;When testing with tox:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&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="l"&gt;push, pull_request, workflow_dispatch]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FORCE_COLOR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.9&amp;#34;&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;#34;3.10&amp;#34;&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;#34;3.11&amp;#34;&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;#34;3.12&amp;#34;&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;#34;3.13&amp;#34;&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;#34;3.14&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persist-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allow-prereleases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pip&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install dependencies&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python --version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -m pip install -U pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -m pip install -U tox&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Tox tests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; tox -e py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can replace &lt;a href="https://github.com/tox-dev/tox"&gt;tox&lt;/a&gt; with
&lt;a href="https://github.com/tox-dev/tox-uv"&gt;tox-uv&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - name: Set up Python ${{ matrix.python-version }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uses: actions/setup-python@v5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; python-version: ${{ matrix.python-version }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; allow-prereleases: true
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- cache: pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- - name: Install dependencies
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- run: |
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- python --version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- python -m pip install -U pip
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- python -m pip install -U tox
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ - name: Install uv
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ uses: hynek/setup-cached-uv@v2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - name: Tox tests
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; run: |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- tox -e py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ uvx --with tox-uv tox -e py
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&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="l"&gt;push, pull_request, workflow_dispatch]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;permissions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;read&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FORCE_COLOR&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.9&amp;#34;&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;#34;3.10&amp;#34;&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;#34;3.11&amp;#34;&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;#34;3.12&amp;#34;&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;#34;3.13&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;persist-credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allow-prereleases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Install uv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;hynek/setup-cached-uv@v2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Tox tests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; uvx --with tox-uv tox -e py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;tox-uv is tox plugin to replace virtualenv and pip with uv in your tox environments. We
only need to install uv, and use &lt;code&gt;uvx&lt;/code&gt; to both install tox-uv and run tox, for faster
installs of tox, the virtual environment, and the dependencies within it.&lt;/p&gt;
&lt;h3 id="test-comparison" class="relative group"&gt;Test comparison &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#test-comparison" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;For example: &lt;a href="https://github.com/python/blurb/pull/32"&gt;python/blurb#32&lt;/a&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;&lt;/th&gt;
 &lt;th&gt;Before&lt;/th&gt;
 &lt;th&gt;After&lt;/th&gt;
 &lt;th&gt;Times faster&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;No cache&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635722298/usage"&gt;2m 0s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635723255/usage"&gt;1m 26s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;1.40&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;strong&gt;With cache&lt;/strong&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635745236/usage"&gt;1m 58s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635746618/usage"&gt;1m 22s&lt;/a&gt;&lt;/td&gt;
 &lt;td&gt;1.44&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="no-pip-with-tox-uv" class="relative group"&gt;No pip with tox-uv &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#no-pip-with-tox-uv" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;One difference with uv environments compared to regular venv/virtualenv ones is that
they do not have pip. This means calls to pip need replacing, for example in &lt;code&gt;tox.ini&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [testenv]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-commands_pre =
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- {envpython} -m pip install -U -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+deps =
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pass_env =
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; FORCE_COLOR
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; commands =
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {envpython} -m pytest {posargs}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you still need pip (or setuptools or wheel), add
&lt;a href="https://github.com/tox-dev/tox-uv#environment-creation"&gt;&lt;code&gt;uv_seed = True&lt;/code&gt;&lt;/a&gt; to your
&lt;code&gt;[testenv]&lt;/code&gt; to inject them.&lt;/p&gt;
&lt;h2 id="bonus-tip" class="relative group"&gt;Bonus tip &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#bonus-tip" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Run the new tool &lt;a href="https://github.com/woodruffw/zizmor"&gt;zizmor&lt;/a&gt; to find security issues
in GitHub Actions.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://finna.fi/Record/hkm.BB327C7B-90B8-45DF-90A1-ABC7B74F6BAA?imgid=1"&gt;Road
cycling at the 1952 Helsinki Olympics&lt;/a&gt;&amp;rdquo; by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/people/nlmhmd/"&gt;Olympia-Kuva
Oy &amp;amp; Helsinki City Museum&lt;/a&gt;,
&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/publicdomain/mark/1.0/deed.en"&gt;Public
Domain&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Python Core Sprint 2024</title><link>https://hugovk.dev/blog/2024/python-core-sprint/</link><pubDate>Sat, 05 Oct 2024 19:39:48 +0000</pubDate><guid>https://hugovk.dev/blog/2024/python-core-sprint/</guid><description>&lt;p&gt;🐍🏃The week before last was the annual Python Core Dev Sprint, graciously hosted by
Meta in Bellevue, WA!&lt;/p&gt;
&lt;p&gt;The idea: bring a bunch of Python core team members, triagers, and special guests to the
same room for a week. It&amp;rsquo;s hugely beneficial and productive, we held many in-depth
discussions that just don&amp;rsquo;t happen when we&amp;rsquo;re all remote and async, and got to work on
many different things together.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_5989d945ae342db0.webp 330w,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_a76d6fdc4c06e442.webp 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_c6e4bafd78f30a28.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_21c2782493cd289b.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4096"
 height="1305"
 class="mx-auto my-0 rounded-md"
 alt="A panoramic shot of a room of people, some sitting at desks, some standing, some working on laptops, some in discussion."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_92ae87e84564bc6f.jpg" srcset="https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_68294675c546fd4d.jpg 330w,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_92ae87e84564bc6f.jpg 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_cc8f7a11aa9c14dc.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_27eeee51c9e761a6.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;The sprint room&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;During the week, I reviewed 39 PRs, created 15, merged 10, updated 4, and closed 2
issues.&lt;/p&gt;
&lt;h2 id="monday-highlights" class="relative group"&gt;Monday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#monday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;As release manager for &lt;a href="https://peps.python.org/pep-0745/"&gt;Python 3.14&lt;/a&gt;, I discussed
with &lt;a href="https://fosstodon.org/@brettcannon"&gt;Brett Cannon&lt;/a&gt; one of his project ideas which
will come after lock files, and after the next big one.&lt;/p&gt;
&lt;p&gt;Also as RM, discussed with &lt;a href="https://cloudisland.nz/@freakboy3742"&gt;Russell Keith-Magee&lt;/a&gt;,
Ned Deily, &lt;a href="https://mastodon.social/@ambv"&gt;Łukasz Langa&lt;/a&gt; and
&lt;a href="https://social.coop/@Yhg1s"&gt;Thomas Wouters&lt;/a&gt; about
&lt;a href="https://beeware.org/news/buzz/2024q4-roadmap/"&gt;including official binaries for iOS and Android&lt;/a&gt;,
which wandered into ideas about security releases.&lt;/p&gt;
&lt;p&gt;I did some maintenance of our PyPI projects, adding
&lt;a href="https://peps.python.org/pep-0740/"&gt;PEP 740 attestations&lt;/a&gt;, support for the
&lt;a href="https://peps.python.org/pep-0719/"&gt;new Python 3.13&lt;/a&gt; and dropping support for the
&lt;a href="https://peps.python.org/pep-0569/"&gt;very-nearly-EOL 3.8&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="tuesday-highlights" class="relative group"&gt;Tuesday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#tuesday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Started investigating slow doctest on 3.13+ with
&lt;a href="https://fosstodon.org/@AlexWaygood"&gt;Alex Waygood&lt;/a&gt;, who on Wednesday narrowed it down to
a problem with the
&lt;a href="https://github.com/python/cpython/issues/124567"&gt;new incremental garbage collector&lt;/a&gt;,
which would go on to be reverted by Friday and result in
&lt;a href="https://discuss.python.org/t/python-3-12-7-and-3-13-0rc3-released/66306?u=hugovk"&gt;Python 3.13&amp;rsquo;s Monday release to be postponed and replaced with an extra release candidate&lt;/a&gt;.
Not ideal, but much better to discover these things before the big release.&lt;/p&gt;
&lt;p&gt;We had a Q&amp;amp;A session with the Steering Council:
&lt;a href="https://mastodon.social/@pumpichank"&gt;Barry Warsaw&lt;/a&gt;, Emily Morehouse,
&lt;a href="https://infosec.exchange/@gpshead"&gt;Gregory P. Smith&lt;/a&gt;, Pablo Galindo Salgado and Thomas.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_48947b4be2516de9.webp 330w,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_84baa120669cda31.webp 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_a238f7da663fc1de.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_4a0421d43ff5fc00.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="The steering council: Barry, Emily, Greg with a microphone, Pablo and Thomas sitting on high stools."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_325b7ae976d18cfb.jpg" srcset="https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_840c9ee517a26c72.jpg 330w,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_325b7ae976d18cfb.jpg 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_2d5fddaecae1b09c.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_16a66ffa3eebb7cb.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;The Python Steering Council&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;Proofread Guido van Rossum&amp;rsquo;s
&lt;a href="https://discuss.python.org/t/changing-pep-13-to-adopt-bloc-star-voting/64971?u=hugovk"&gt;STAR voting proposal&lt;/a&gt;
for electing future steering councils.&lt;/p&gt;
&lt;p&gt;Discussed with Eric Snow his novel method for displaying many code samples in a table,
using &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; disclosures to prevent the table being too wide. Looks like a good
solution!&lt;/p&gt;
&lt;h2 id="wednesday-highlights" class="relative group"&gt;Wednesday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#wednesday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I applied the &lt;a href="https://github.com/python/peps/pull/3995"&gt;finishing touches&lt;/a&gt; to
&lt;a href="https://peps.python.org/pep-2026/"&gt;PEP 2026&lt;/a&gt; (Calendar versioning for Python) and Barry
gave it a final review. Ready for submission!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fosstodon.org/@sethmlarson"&gt;Seth Larson&lt;/a&gt;, the PSF
&lt;a href="https://sethmlarson.dev/security-developer-in-residence"&gt;Security Developer-in-Residence&lt;/a&gt;,
wasn&amp;rsquo;t at the sprint but we discussed our plan to stop providing GPG signatures for
CPython and rely on SigStore instead. Expect a PEP soon!&lt;/p&gt;
&lt;p&gt;Also not at the sprint, I recommended PSF
&lt;a href="https://pyfound.blogspot.com/2024/07/announcing-our-new-infrastructure.html"&gt;Infrastructure Engineer&lt;/a&gt;
&lt;a href="https://fosstodon.org/@Monorepo"&gt;Jacob Coffee&lt;/a&gt; as a &lt;a href="CPython"&gt;CPython triager&lt;/a&gt;. Welcome
aboard!&lt;/p&gt;
&lt;p&gt;The whole room discussed including
&lt;a href="https://discuss.python.org/t/static-type-annotations-in-cpython/65068?u=hugovk"&gt;static type annotations in CPython&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We had a Q&amp;amp;A session with two of the three Developers-in-Residence, Łukasz and
&lt;a href="https://mastodon.social/@encukou"&gt;Petr Viktorin&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_f25ba851e9c73163.webp 330w,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_4661a7920146453a.webp 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_5755e30f547f4ea2.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_1f27c95c34e04a13.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="Łukasz (with a microphone) and Petr sitting on high stools."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_96521f8f754cd9d9.jpg" srcset="https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_6960b0a14cc4f1c0.jpg 330w,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_96521f8f754cd9d9.jpg 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_52ac56b1a576ab76.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_53952b3d19b37af4.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Q&amp;A with Łukasz and Petr&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;Discussed
&lt;a href="https://discuss.python.org/t/collecting-feedback-about-expanding-the-voter-pool-for-sc-elections?u=hugovk"&gt;expanding the voter pool for Steering Council elections&lt;/a&gt;
with &lt;a href="https://fosstodon.org/@mariatta"&gt;Mariatta&lt;/a&gt;, Greg and Thomas.&lt;/p&gt;
&lt;p&gt;Larry Hastings handed out, in return for &lt;em&gt;oohs&lt;/em&gt; and &lt;em&gt;aahs&lt;/em&gt;, some nice P.C.D.S. 2024
stickers he generously designed and printed up for us. Thanks!&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_d577297e67790147.webp 330w,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_2b196df1d20c703a.webp 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_9d5765cf975d3705.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_95b68a05a5853e9e.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="Two stickers. One yellow snake with its body looping and spelling out the letters PCDS (for Python Core Dev Sprint), one blue snake spelling 2024."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_c6c02d44910213c9.jpg" srcset="https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_1f35a3dc83d8d8.jpg 330w,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_c6c02d44910213c9.jpg 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_ad3b4c4f90380f59.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_20f93f96423f3c8d.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;PCDS 2024 stickers by Larry&lt;/small&gt;&lt;/center&gt;
&lt;h2 id="thursday-highlights" class="relative group"&gt;Thursday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thursday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;On the 26th September, at 10:26 Bellevue time (20:26 Helsinki time), I
&lt;a href="https://github.com/python/steering-council/issues/255"&gt;submitted PEP 2026 to the Steering Council!&lt;/a&gt;🤞&lt;/p&gt;
&lt;p&gt;Brett discussed whether we should update
&lt;a href="https://discuss.python.org/t/updating-pep-387-to-prefer-5-year-deprecations-instead-of-2-years/65166?u=hugovk"&gt;PEP 387 to prefer 5 year deprecations instead of 2 years&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Brandt Bucher gave us all an update on the progress of the Just-in-Time (JIT) compiler
(&amp;ldquo;we went from 0% slower to 0% faster!&amp;rdquo;) and we discussed plans for Python 3.14.&lt;/p&gt;
&lt;p&gt;Because I couldn&amp;rsquo;t attend Thursday&amp;rsquo;s
&lt;a href="https://helsinki-python.github.io/"&gt;Helsinki Python meetup&lt;/a&gt; due to being at another
kind of Python meetup on the other side of the world, I gave the famous HelPy quiz to
the assembled core devs. Unsurprisingly they did pretty well, but the most incorrect
answer was a pleasant surprise: we&amp;rsquo;ve had ~400 not ~80 new contributors to Python 3.13!&lt;/p&gt;
&lt;p&gt;Pablo performed card tricks!&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_2f13ed116425f297.webp 330w,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_d85a54d5c9cb29f1.webp 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_82de655e177e8097.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_c7bcb7cab8c9b252.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="Pablo splaying open a deck of PyCon US playing cards and Greg picking one."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_239e77767d364bf1.jpg" srcset="https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_5388aa5c48ca15c0.jpg 330w,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_239e77767d364bf1.jpg 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_d2759b7d766d1253.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_77f874c8fdc729d1.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Magic from Pablo&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;Meta took us out for a delicious dinner at a local fish restaurant. Thank you!&lt;/p&gt;
&lt;h2 id="friday-highlights" class="relative group"&gt;Friday highlights &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#friday-highlights" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Mariatta presented ideas to Jelle Zijlstra, Petr, Russell and me about to use modern
tools to create a modern, interactive tutorial.&lt;/p&gt;
&lt;p&gt;Also during the week, continued work with Adam Turner on improving the
&lt;a href="https://docs.python.org/3/"&gt;docs.python.org&lt;/a&gt; build. Adam wasn&amp;rsquo;t at the sprint, so
tag-teamed PR reviews overnight. After
&lt;a href="https://github.com/python/docsbuild-scripts/issues/169#issuecomment-2389743956"&gt;much work straddling many teams, projects and repos&lt;/a&gt;,
we&amp;rsquo;ve got the full HTML build loop for 13 languages × 3 versions down from over 40 hours
to just under 9 hours, with more improvements coming.&lt;/p&gt;
&lt;p&gt;Made a &lt;a href="https://hugovk-cpython.readthedocs.io/en/pydata-sphinx-theme/"&gt;demo&lt;/a&gt; of the
CPython docs using the
&lt;a href="https://pydata-sphinx-theme.readthedocs.io/en/stable/"&gt;PyData Sphinx Theme&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Along with around 25 others, I was on Łukasz and Pablo&amp;rsquo;s
&lt;a href="https://podcasters.spotify.com/pod/show/corepy/episodes/Episode-15-Core-sprint-at-Meta-e2p64tc"&gt;core.py podcast&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_ea4b0fc432ee2d59.webp 330w,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_2faba5065b93b933.webp 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_5148b041d531e001.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_a62537ed55085d50.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="4000"
 height="3000"
 class="mx-auto my-0 rounded-md"
 alt="Łukasz and Pablo in their ad-hoc podcast studio in a Meta meeting room."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_d84536419ff2e7a5.jpg" srcset="https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_363088ba11260aa6.jpg 330w,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_d84536419ff2e7a5.jpg 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_b2988225b852219c.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_757224b2a6d15927.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Łukasz and Pablo in their ad-hoc podcast studio in a Meta meeting room&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;Itamar gave us cake for the podcast&amp;rsquo;s first birthday!&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_a42fdd3d5bab3ab0.webp 330w,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_4134792200921db0.webp 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_b01ace4d19c3b8fc.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_e97ea3d6d206c0bd.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="2439"
 height="1747"
 class="mx-auto my-0 rounded-md"
 alt="A big white cake, with decorations of Łukasz and Pablo&amp;rsquo;s faces, along with the Core.py logo, a big digit 1, headphones, microphone, bananas, and &amp;ldquo;emosido engañado&amp;rdquo; graffiti."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_10bb0a296531e659.jpg" srcset="https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_1568c2e0eec4fcdd.jpg 330w,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_10bb0a296531e659.jpg 660w
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_99bc277cff288229.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_6f4c9c58311527ca.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;cake.py. Photo by Itamar Oren.&lt;/small&gt;&lt;/center&gt;
&lt;h2 id="thank-you" class="relative group"&gt;Thank you &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thank-you" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;It was a hugely productive week, big thanks to Itamar Oren and Meta for organising and
hosting!&lt;/p&gt;
&lt;p&gt;See also Mariatta&amp;rsquo;s excellent &lt;a href="https://mariatta.ca/tags/sprint/"&gt;blog posts&lt;/a&gt;, and I
recommend the
&lt;a href="https://podcasters.spotify.com/pod/show/corepy/episodes/Episode-15-Core-sprint-at-Meta-e2p64tc"&gt;core.py podcast&lt;/a&gt;
with short interviews with some 25 attendees! Łukasz and Pablo were also guests on the
&lt;a href="https://changelog.com/podcast/611"&gt;Changelog podcast&lt;/a&gt; during the sprint.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo by Itamar Oren&lt;/small&gt;&lt;/p&gt;</description></item><item><title>PyCon US 2024: A roundup of writeups</title><link>https://hugovk.dev/blog/2024/pycon-us-2024-a-roundup-of-writeups/</link><pubDate>Thu, 13 Jun 2024 14:41:55 +0000</pubDate><guid>https://hugovk.dev/blog/2024/pycon-us-2024-a-roundup-of-writeups/</guid><description>&lt;p&gt;If you read just one, check
&lt;a href="https://katherinemichel.github.io/portfolio/pycon-us-2024-recap.html"&gt;Kati&amp;rsquo;s thorough recap&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;22nd May 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://nedbatchelder.com/blog/202405/echos_of_the_people_api_user_guide.html"&gt;Echos of the People API user guide&lt;/a&gt;
by &lt;a href="https://mastodon.social/@nedbat@hachyderm.io/112479219635272740"&gt;Ned Batchelder&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;24th May 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://wagtail.org/blog/pycon-2024/"&gt;Wagtailers spread their wings at PyCon 2024&lt;/a&gt; by
&lt;a href="https://fosstodon.org/@vossisboss"&gt;Meagen Voss&lt;/a&gt; (@vossisboss)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://flet.dev/blog/flet-at-pycon-us-2024/"&gt;Flet at PyCon US 2024&lt;/a&gt; by Feodor
Fitsner&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;27th May 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.tomy.me/en/posts/pycon-us-2024/"&gt;PyCon US 2024: My First PyCon in US 🫶🏻&lt;/a&gt;
by &lt;a href="https://mastodon.social/@tomyhsieh"&gt;Tomy Hsieh&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;28th May 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://treyhunner.com/2024/05/pycon-2024-reflection/"&gt;PyCon 2024 Reflection&lt;/a&gt; by
&lt;a href="https://mastodon.social/@treyhunner/112520554890667118"&gt;Trey Hunner&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://simonwillison.net/2024/May/28/weeknotes/"&gt;Weeknotes: PyCon US 2024&lt;/a&gt; by
&lt;a href="https://fedi.simonwillison.net/@simon/112520529116346191"&gt;Simon Willison&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://stacklok.com/blog/3-key-takeaways-from-pycon-us-2024"&gt;3 key takeaways from PyCon US 2024&lt;/a&gt;
by Luis Juncal &amp;amp; Yolanda Robla&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;30th May 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.pyopensci.org/blog/recap-pyos-pyconus-2024.html"&gt;pyOpenSci at PyCon US 2024 - Python packaging and community &lt;/a&gt;
by &lt;a href="https://fosstodon.org/@leahawasser/112554205196871020"&gt;Leah Wasser&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://kafkai.com/en/blog/our-experience-at-pycon-us-2024-in-pittsburgh/"&gt;Our Experience at PyCon US 2024 in Pittsburgh&lt;/a&gt;
by &lt;a href="https://hachyderm.io/@muheuenga/112530136789502549"&gt;Ngazetungue Muheue&lt;/a&gt;
(@ngazetungue)&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1st June 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://katherinemichel.github.io/portfolio/pycon-us-2024-recap.html"&gt;PyCon US 2024 Recap&lt;/a&gt;
by &lt;a href="https://fosstodon.org/@kati/112542145378019538"&gt;Kati Michel&lt;/a&gt; (@katherinemichel)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;4th June 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mangoumbrella.com/post/pycon-us-2024"&gt;PyCon US 2024&lt;/a&gt; by
&lt;a href="https://mastodon.social/@y2mango/112556835094650562"&gt;Yilei Yang&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;13th June 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://sethmlarson.dev/security-developer-in-residence-report-37"&gt;PyCon US 2024 as Security Developer-in-Residence&lt;/a&gt;
by &lt;a href="https://fosstodon.org/@sethmlarson"&gt;Seth Michael Larson&lt;/a&gt; (@sethmlarson)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;14th June 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pyfound.blogspot.com/2024/06/python-language-summit-2024.html"&gt;The Python Language Summit 2024&lt;/a&gt;
by &lt;a href="https://fosstodon.org/@sethmlarson"&gt;Seth Michael Larson&lt;/a&gt; (@sethmlarson)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;16th June 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dafoster.net/articles/2024/06/16/pycon-us-2024-highlights/"&gt;PyCon US 2024 Highlights&lt;/a&gt;
by &lt;a href="https://mastodon.world/@davidfstr"&gt;David Foster&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;19th June 2024&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/@monicaoyugi/from-pittsburgh-to-new-york-a-pycon-us-2024-adventure-1727c952509c"&gt;From Pittsburgh to New York: A PyCon US 2024 Adventure&lt;/a&gt;
by &lt;a href="https://mastodon.social/@monics"&gt;Monica Oyugi&lt;/a&gt; (@monicaoyugi)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo: Downtown Pittsburgh seen between the Andy Warhol Bridge and Roberto
Clemente Bridge&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Help test Python 3.13!</title><link>https://hugovk.dev/blog/2024/help-test-python-313/</link><pubDate>Tue, 04 Jun 2024 13:04:03 +0000</pubDate><guid>https://hugovk.dev/blog/2024/help-test-python-313/</guid><description>&lt;p&gt;Calling all Python library maintainers! 🐍&lt;/p&gt;
&lt;p&gt;The Python 3.13 beta is out! 🎉&lt;/p&gt;
&lt;p&gt;&lt;a href="https://peps.python.org/pep-0719/#release-schedule"&gt;PEP 719&lt;/a&gt; defines the release
schedule for Python 3.13.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first beta candidate came out on 8th May 2024&lt;/li&gt;
&lt;li&gt;The first release candidate is set for 30th July 2024&lt;/li&gt;
&lt;li&gt;And the full release is set for 1st October 2024&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In his
&lt;a href="https://discuss.python.org/t/python-3-13-0b1-now-available/52891?u=hugovk"&gt;announcement&lt;/a&gt;,
Thomas Wouters, release manager for Python 3.12 and 3.13, said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We &lt;strong&gt;strongly encourage&lt;/strong&gt; maintainers of third-party Python projects to test with 3.13
during the beta phase and report issues found to the
&lt;a href="https://github.com/python/cpython/issues"&gt;Python bug tracker&lt;/a&gt; as soon as possible.
While the release is planned to be feature complete entering the beta phase, it is
possible that features may be modified or, in rare cases, deleted up until the start
of the release candidate phase (Tuesday 2024-07-30). Our goal is to have no ABI
changes after beta 4 and as few code changes as possible after 3.13.0rc1, the first
release candidate. To achieve that, it will be &lt;strong&gt;extremely important&lt;/strong&gt; to get as much
exposure for 3.13 as possible during the beta phase.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="test-with-313" class="relative group"&gt;Test with 3.13 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#test-with-313" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;It&amp;rsquo;s now time for us library maintainers to start testing our projects with 3.13.
There&amp;rsquo;s two big benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;There have been
&lt;a href="https://docs.python.org/3.13/whatsnew/3.13.html#removed"&gt;removals and changes&lt;/a&gt; in
Python 3.13. Testing now helps us make our code compatible and avoid any big
surprises (for us and our users) at the big launch in October.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We might find bugs in Python itself! Reporting those will help get them fixed and
help everyone.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;h3 id="github-actions-setup-python" class="relative group"&gt;GitHub Actions: setup-python &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#github-actions-setup-python" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;To test the latest alpha, beta or release candidate with
&lt;a href="https://github.com/actions/setup-python#supported-version-syntax"&gt;actions/setup-python&lt;/a&gt;,
add &lt;code&gt;3.13&lt;/code&gt; and &lt;code&gt;allow-prereleases: true&lt;/code&gt; to your workflow matrix.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.10&amp;#34;&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;#34;3.11&amp;#34;&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;#34;3.12&amp;#34;&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;#34;3.13&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allow-prereleases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(We can instead use &lt;code&gt;3.13-dev&lt;/code&gt; and omit &lt;code&gt;allow-prereleases: true&lt;/code&gt;, but I find the above
a bit neater, and when 3.13.0 final is released in October, it will continue testing
with full release versions.)&lt;/p&gt;
&lt;h3 id="github-actions-deadsnakes" class="relative group"&gt;GitHub Actions: deadsnakes &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#github-actions-deadsnakes" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;For the bleeding edge, we can use
&lt;a href="https://github.com/deadsnakes/action"&gt;deadsnakes/action&lt;/a&gt; to test the latest nightly
build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.10&amp;#34;&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;#34;3.11&amp;#34;&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;#34;3.12&amp;#34;&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;#34;3.13-dev&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&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;#34;!endsWith(matrix.python-version, &amp;#39;-dev&amp;#39;)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deadsnakes/action@v3.1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }} (deadsnakes)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;endsWith(matrix.python-version, &amp;#39;-dev&amp;#39;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="when-to-support-313" class="relative group"&gt;When to support 3.13? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#when-to-support-313" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;When should you declare support and add the &lt;code&gt;Programming Language :: Python :: 3.13&lt;/code&gt;
&lt;a href="https://pypi.org/classifiers/"&gt;Trove classifier&lt;/a&gt;? Some
&lt;a href="https://pyreadiness.org/3.13/"&gt;projects already have&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;If you have a pure Python project, you can release now.&lt;/p&gt;
&lt;p&gt;If you have C extensions and other projects depend on yours, a preview release with
wheels will help them test and prepare. I&amp;rsquo;ve already started releasing these.&lt;/p&gt;
&lt;h3 id="abi-breaks" class="relative group"&gt;ABI breaks? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#abi-breaks" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;ABI breaks during the beta are infrequent and unintentional. If they happen, you can
rebuild your wheels and upload them to an existing PyPI release by adding an optional
&lt;a href="https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-format"&gt;&lt;em&gt;build tag&lt;/em&gt;&lt;/a&gt;
to the filename:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The wheel filename is
&lt;code&gt;{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;build tag: [&amp;hellip;] Acts as a tie-breaker if two wheel file names are the same in all
other respects (i.e. name, version, and other tags).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For example, this updates the filename and metadata with build number 1, and removes the
original file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install &lt;span class="s2"&gt;&amp;#34;wheel&amp;gt;=0.4.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wheel tags --build&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; --remove sampleproject-5.0.0-cp313-cp313-macosx_10_10_x86_64.whl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# this creates a file named sampleproject-5.0.0-1-cp313-cp313-macosx_10_10_x86_64.whl&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Upload it, and the new file will be used instead of the old one. See also
&lt;a href="https://snarky.ca/what-to-do-when-you-botch-a-release-on-pypi/#a-wheel-file-wasnt-compiled-properly"&gt;Brett Cannon&amp;rsquo;s advice on making new wheels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In any case, let&amp;rsquo;s start testing 3.13 now! 🚀&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../../2023/help-us-test-free-threaded-python-without-the-gil/"&gt;Help us test free-threaded Python without the GIL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/whatsnew/3.13.html"&gt;What’s New In Python 3.13&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo: Lot 044 from the &lt;a href="https://us.pycon.org/2024/"&gt;PyCon 2024&lt;/a&gt;
&lt;a href="https://mastodon.social/@Lorenanicole/112468770670490719"&gt;PyLadies Auction&lt;/a&gt;: &amp;ldquo;A pair of
hand-woven snakes (&lt;a href="https://www.pylatam.org/"&gt;PyCon Latam&lt;/a&gt; 2023 edition), donated by the
PyCon Latam Organizers. This is a souvenir from PyCon Latam held in Mexica 2023 that
represents the snakes of the PyLatam community logo. They are made completely by
hand.&amp;quot;&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Printable PyCon 2024 schedule</title><link>https://hugovk.dev/blog/2024/printable-pycon-2024-schedule/</link><pubDate>Sat, 11 May 2024 11:01:33 +0000</pubDate><guid>https://hugovk.dev/blog/2024/printable-pycon-2024-schedule/</guid><description>&lt;p&gt;Want to print out the PyCon US schedule? Paper doesn&amp;rsquo;t run out of batteries, is easy to
scribble on, and stuff into a pocket (technical term: &lt;em&gt;the affordances of paper&lt;/em&gt;).&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s some custom CSS and JavaScript to make it nicely printable.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the
&lt;a href="https://chrome.google.com/webstore/detail/styler/bogdgcfoocbajfkjjolkmcdcnnellpkb?hl=en"&gt;Styler browser extension&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;View a PyCon schedule page such as &lt;a href="https://us.pycon.org/2024/schedule/talks/"&gt;https://us.pycon.org/2024/schedule/talks/&lt;/a&gt; and
click the Styler extension&amp;rsquo;s S icon&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paste this CSS into the upper box:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-CSS" data-lang="CSS"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;internal-page-header&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;panel-heading&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;badges&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;display&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fit-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;calendar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;slot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="kt"&gt;px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt; &lt;span class="nt"&gt;print&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;href&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;Paste this JavaScript into the lower box:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2024/schedule/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2024/schedule/presentation/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;body&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pycon-schedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;Print!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&amp;rsquo;ll run on any of the &lt;code&gt;https://us.pycon.org/2024/schedule/*&lt;/code&gt; pages, but not the
individual presentation pages such as
&lt;code&gt;https://us.pycon.org/2024/schedule/presentation/61/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not perfect, the right edge is slightly cut off, but it&amp;rsquo;s more printable than the
original.&lt;/p&gt;
&lt;p&gt;Once printed, you can click the Styler icon and the &lt;code&gt;x&lt;/code&gt; button to disable Styler for the
site so you can browse the original version.&lt;/p&gt;
&lt;p&gt;See also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="../2023/printable-pycon-2023-schedule"&gt;Printable PyCon 2023 schedule&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Help us test free-threaded Python without the GIL</title><link>https://hugovk.dev/blog/2023/help-us-test-free-threaded-python-without-the-gil/</link><pubDate>Fri, 10 May 2024 12:18:27 +0000</pubDate><guid>https://hugovk.dev/blog/2023/help-us-test-free-threaded-python-without-the-gil/</guid><description>&lt;p&gt;Python 3.13 is &lt;a href="https://peps.python.org/pep-0719/"&gt;due out in October 2024&lt;/a&gt; and work is
underway to implement &lt;em&gt;experimental support&lt;/em&gt; for
&lt;a href="https://peps.python.org/pep-0703/"&gt;PEP 703 - Making the Global Interpreter Lock Optional in CPython&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;See also
&lt;a href="https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython"&gt;Free-threaded CPython&lt;/a&gt;
in &amp;ldquo;What&amp;rsquo;s New in Python 3.13?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;As the Steering Council noted in their
&lt;a href="https://discuss.python.org/t/pep-703-making-the-global-interpreter-lock-optional-in-cpython-acceptance/37075?u=hugovk"&gt;acceptance of the PEP&lt;/a&gt;,
to succeed it&amp;rsquo;s important to have community support.&lt;/p&gt;
&lt;p&gt;Projects will need to test their code with free-threaded (aka &amp;ldquo;nogil&amp;rdquo; but
&lt;a href="https://discuss.python.org/t/pep-703-making-the-global-interpreter-lock-optional-in-cpython-acceptance/37075?u=hugovk"&gt;don&amp;rsquo;t call it that!&lt;/a&gt;)
Python builds to help us find bugs in CPython, and to check their code is compatible.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s some ways to test.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Official installers&lt;/li&gt;
&lt;li&gt;Build it yourself&lt;/li&gt;
&lt;li&gt;deadsnakes&lt;/li&gt;
&lt;li&gt;GitHub Actions?&lt;/li&gt;
&lt;li&gt;Fedora&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="official-installers" class="relative group"&gt;Official installers &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#official-installers" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The official macOS (starting in beta 2) and Windows installers has an option to install
the free-threading binaries, which also installs &lt;code&gt;python3.13t&lt;/code&gt; alongside the regular
&lt;code&gt;python3.13&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/python/cpython/issues/120098"&gt;python/cpython#120098&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/using/windows.html#installing-free-threaded-binaries"&gt;https://docs.python.org/3.13/using/windows.html#installing-free-threaded-binaries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="build-it-yourself" class="relative group"&gt;Build it yourself &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#build-it-yourself" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Check out the CPython Git repo and &lt;a href="https://devguide.python.org/"&gt;build yourself&lt;/a&gt; using
the
&lt;a href="https://docs.python.org/3.13/using/configure.html#cmdoption-disable-gil"&gt;&lt;code&gt;--disable-gil&lt;/code&gt; configuration flag&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For example, on macOS I run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nv"&gt;GDBM_CFLAGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-I&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;brew --prefix gdbm&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/include&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; GDBM_LIBS=&amp;#34;-L$(brew --prefix gdbm)/lib -lgdbm&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; ./configure --config-cache \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; --with-system-libmpdec \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; --with-openssl=&amp;#34;$(brew --prefix openssl@3.0)&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; --disable-gil &amp;amp;&amp;amp; make -s -j10
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./python.exe -c &lt;span class="s2"&gt;&amp;#34;import sysconfig; print(sysconfig.get_config_var(&amp;#39;Py_GIL_DISABLED&amp;#39;))&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; ./python.exe -c &lt;span class="s2"&gt;&amp;#34;import sys; print(sys._is_gil_enabled())&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;False
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;More details in the devguide:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devguide.python.org/getting-started/setup-building/"&gt;https://devguide.python.org/getting-started/setup-building/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="github-actions-deadsnakesaction" class="relative group"&gt;GitHub Actions: deadsnakes/action &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#github-actions-deadsnakesaction" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;We can use the &lt;a href="https://github.com/deadsnakes/action"&gt;deadsnakes/action&lt;/a&gt; to test Ubuntu.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&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="l"&gt;push, pull_request, workflow_dispatch]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="m"&gt;3.12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3.13&lt;/span&gt;-&lt;span class="l"&gt;dev]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;main&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&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;#34;!endsWith(matrix.python-version, &amp;#39;-dev&amp;#39;)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deadsnakes/action@v3.1.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;endsWith(matrix.python-version, &amp;#39;-dev&amp;#39;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nogil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;run&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python --version --version
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; which python
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -c &amp;#34;import sysconfig; print(sysconfig.get_config_var(&amp;#39;Py_GIL_DISABLED&amp;#39;))&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; python -c &amp;#34;import sys; print(sys._is_gil_enabled())&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="github-actions-actionssetup-python" class="relative group"&gt;GitHub Actions: actions/setup-python &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#github-actions-actionssetup-python" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I&amp;rsquo;ve asked for support at
&lt;a href="https://github.com/actions/setup-python/issues/771"&gt;actions/setup-python#771&lt;/a&gt;, they&amp;rsquo;re
looking into it 🤞&lt;/p&gt;
&lt;p&gt;In the meantime, give it an upvote, and use deadsnakes/action ⤴️&lt;/p&gt;
&lt;h2 id="deadsnakes-ppa" class="relative group"&gt;deadsnakes: PPA &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#deadsnakes-ppa" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The deadsnakes project provides Personal Package Archives of Python packaged for Ubuntu,
included free-threaded builds.&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;python3.13-nogil&lt;/code&gt; under &lt;code&gt;python3.13 - 3.13.0~a2-1+focal1&lt;/code&gt; and
&lt;code&gt;python3.13 - 3.13.0~a2-1+jammy1&lt;/code&gt; at
&lt;a href="https://launchpad.net/~deadsnakes/&amp;#43;archive/ubuntu/ppa/&amp;#43;packages"&gt;https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa/+packages&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="fedora" class="relative group"&gt;Fedora &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#fedora" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Fedora uses the &lt;code&gt;python3.13t&lt;/code&gt; executable name
&lt;a href="https://github.com/python/steering-council/issues/221#issuecomment-1841593283"&gt;as decided by the Steering Council&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Install&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; sudo dnf install python3.13-freethreading
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Run&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; /usr/bin/python3.13t -c &lt;span class="s2"&gt;&amp;#34;import sysconfig; print(sysconfig.get_config_var(&amp;#39;Py_GIL_DISABLED&amp;#39;))&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; /usr/bin/python3.13t -c &lt;span class="s2"&gt;&amp;#34;import sys; print(sys._is_gil_enabled())&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;False
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="how-to-check-your-build" class="relative group"&gt;How to check your build &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-to-check-your-build" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To confirm if you&amp;rsquo;re using a free-threaded build, check the double &lt;code&gt;--version&lt;/code&gt; option
(starting in beta 2):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Regular build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.13 --version --version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Python 3.13.0b2 (v3.13.0b2:3a83b172af, Jun 5 2024, 12:50:24) [Clang 15.0.0 (clang-1500.3.9.4)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Free-threaded build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.13t --version --version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Python 3.13.0b2 experimental free-threading build (v3.13.0b2:3a83b172af, Jun 5 2024, 12:57:31) [Clang 15.0.0 (clang-1500.3.9.4)]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or the &lt;code&gt;Py_GIL_DISABLED&lt;/code&gt; macro:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Regular build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.13 -c &lt;span class="s2"&gt;&amp;#34;import sysconfig; print(sysconfig.get_config_var(&amp;#39;Py_GIL_DISABLED&amp;#39;))&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Free-threaded build&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.13t -c &lt;span class="s2"&gt;&amp;#34;import sysconfig; print(sysconfig.get_config_var(&amp;#39;Py_GIL_DISABLED&amp;#39;))&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;By default, the GIL is disabled for free-threaded builds, and can be re-enabled by
setting the &lt;code&gt;PYTHON_GIL&lt;/code&gt; environment variable to &lt;code&gt;1&lt;/code&gt; or running Python with &lt;code&gt;-X gil 1&lt;/code&gt;.
You can check with &lt;code&gt;sys._is_gil_enabled()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Regular build: GIL is always enabled&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.13 -c &lt;span class="s2"&gt;&amp;#34;import sys; print(sys._is_gil_enabled())&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;True
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Free-threaded build: GIL is disabled by default&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.13t -c &lt;span class="s2"&gt;&amp;#34;import sys; print(sys._is_gil_enabled())&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;False
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Free-threaded build: re-enable with -X&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3.13t -X &lt;span class="nv"&gt;gil&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; -c &lt;span class="s2"&gt;&amp;#34;import sys; print(sys._is_gil_enabled())&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;True
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="c1"&gt;# Free-threaded build: re-enable with env var&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nv"&gt;PYTHON_GIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; python3.13t -c &lt;span class="s2"&gt;&amp;#34;import sys; print(sys._is_gil_enabled())&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;True
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;References:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/using/configure.html#cmdoption-disable-gil"&gt;https://docs.python.org/3.13/using/configure.html#cmdoption-disable-gil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/using/cmdline.html#envvar-PYTHON_GIL"&gt;https://docs.python.org/3.13/using/cmdline.html#envvar-PYTHON_GIL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/using/cmdline.html#cmdoption-X"&gt;https://docs.python.org/3.13/using/cmdline.html#cmdoption-X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../../2024/help-test-python-313/"&gt;Help test Python 3.13!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/howto/free-threading-extensions.html"&gt;C API Extension Support for Free Threading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labs.quansight.org/blog/free-threaded-python-rollout"&gt;Free-threaded CPython is ready to experiment with!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://py-free-threading.github.io/"&gt;py-free-threading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Quansight-Labs/free-threaded-compatibility"&gt;Improving Ecosystem Compatibility with Free-Threaded Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/nlmhmd/8616809942/"&gt;George
Mayerle test chart&lt;/a&gt;&amp;rdquo; by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/people/nlmhmd/"&gt;US
National Library of Medicine&lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Sphinx docs: How to activate tabs for your OS</title><link>https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/</link><pubDate>Tue, 02 Apr 2024 18:41:30 +0000</pubDate><guid>https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/</guid><description>&lt;p&gt;On the &lt;a href="https://devguide.python.org/"&gt;Python Developer&amp;rsquo;s Guide&lt;/a&gt; and
&lt;a href="https://pillow.readthedocs.io/en/latest/installation/basic-installation.html"&gt;Pillow documentation&lt;/a&gt;
we have some pages with tabs for different operating systems:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_8ea0342733ffc1da.webp 330w,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_198a2e0e0484a6ee.webp 660w
 
 ,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_1d2f9524750f9848.webp 1024w
 
 
 ,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_adf4b8fd531c59d2.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1550"
 height="776"
 class="mx-auto my-0 rounded-md"
 alt="Screenshot showing instructions how to build Python. Some parts have Unix, macOS and Windows tabs. The macOS tab is active, showing a configure command to run in your terminal. Underneath is a second step with its own tabs and a macOS command. There&amp;rsquo;s a macOS-specific note underneath."
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_8d3812abfd1d09af.png" srcset="https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_e13b169ef468e614.png 330w,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_8d3812abfd1d09af.png 660w
 
 ,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_208bd901fbf9c61a.png 1024w
 
 
 ,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_380bd23e7cb8554f.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible to add some JavaScript so that the matching tab is activated based on the
visitor&amp;rsquo;s operating system.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how!&lt;/p&gt;
&lt;h2 id="sphinx-inline-tabs" class="relative group"&gt;Sphinx Inline Tabs &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#sphinx-inline-tabs" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;First add the &lt;a href="https://github.com/pradyunsg/sphinx-inline-tabs"&gt;Sphinx Inline Tabs&lt;/a&gt;
extension to your docs'
&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/requirements.txt#L4"&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-txt" data-lang="txt"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sphinx-inline-tabs&amp;gt;=2023.4.21
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="javascript" class="relative group"&gt;JavaScript &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#javascript" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;Next, add
&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/_static/activate_tab.js"&gt;&lt;code&gt;activate_tab.js&lt;/code&gt;&lt;/a&gt;
to your &lt;code&gt;_static/&lt;/code&gt; directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// activate_tab.js
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Based on https://stackoverflow.com/a/38241481/724176
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getOS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userAgent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgentData&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;macosPlatforms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;macOS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Macintosh&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;MacIntel&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;MacPPC&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Mac68K&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;windowsPlatforms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Win32&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Win64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Windows&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;WinCE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;iosPlatforms&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;iPhone&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;iPad&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;iPod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;macosPlatforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;macOS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iosPlatforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;iOS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;windowsPlatforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Windows&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Android/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Android&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/Linux/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Unix&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;unknown&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;activateTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Find all label elements containing the specified tab name
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.tab-label&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Find the associated input element using the &amp;#39;for&amp;#39; attribute
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tabInputId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;label&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;for&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tabInput&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabInputId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Check if the input element exists before attempting to set the &amp;#34;checked&amp;#34; attribute
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Activate the tab by setting its &amp;#34;checked&amp;#34; attribute to true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tabInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h1 id="confpy" class="relative group"&gt;&lt;code&gt;conf.py&lt;/code&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#confpy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h1&gt;&lt;p&gt;Add the
&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/conf.py#L15"&gt;extension&lt;/a&gt;
and
&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/conf.py#L49-L51"&gt;JavaScript&lt;/a&gt;
to your &lt;code&gt;conf.py&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# conf.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;extensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;sphinx_inline_tabs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;html_js_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;activate_tab.js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="restructuredtext" class="relative group"&gt;reStructuredText &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#restructuredtext" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Almost there!&lt;/p&gt;
&lt;p&gt;Add tabs to the reStructuredText files.&lt;/p&gt;
&lt;p&gt;For
&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/testing/run-write-tests.rst#L58-L74"&gt;example&lt;/a&gt;,
here we have three different commands; one for Unix, one for macOS, and one for Windows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-reStructuredText" data-lang="reStructuredText"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; Unix&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt; ..&lt;/span&gt; &lt;span class="ow"&gt;code-block&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="k"&gt;shell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ./python -m &lt;span class="nb"&gt;test&lt;/span&gt; -h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; macOS&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt; ..&lt;/span&gt; &lt;span class="ow"&gt;code-block&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; &lt;span class="k"&gt;shell&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ./python.exe -m &lt;span class="nb"&gt;test&lt;/span&gt; -h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;tab&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; Windows&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt; ..&lt;/span&gt; &lt;span class="ow"&gt;code-block&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; dosbatch&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; .\python.bat -m test -h&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Finally, add the JavaScript
&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/testing/run-write-tests.rst#L8-L14"&gt;to the same reST page&lt;/a&gt;
to activate the correct tab:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-rst" data-lang="rst"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;..&lt;/span&gt; &lt;span class="ow"&gt;raw&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt; html&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;script&amp;gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; document.addEventListener(&amp;#39;DOMContentLoaded&amp;#39;, function() {&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; activateTab(getOS());&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; });&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;/script&amp;gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can see the results
&lt;a href="https://devguide.python.org/testing/run-write-tests/#running"&gt;here&lt;/a&gt;. When the page
loads, the browser finds the browser name, and activates the tabs with a matching name.&lt;/p&gt;
&lt;p&gt;If you have many sets of tabs on a page, the corresponding OS tab will be activated for
all. And if you click on another OS tab, all the others with the same name are
activated.&lt;/p&gt;
&lt;p&gt;Happy tabbing!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/nationalgalleries/3110282571/"&gt;The
Great Pyramid and the Sphinx&lt;/a&gt;&amp;rdquo; by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/nationalgalleries/"&gt;National
Galleries of Scotland&lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Tech style guides</title><link>https://hugovk.dev/blog/2024/tech-style-guides/</link><pubDate>Sat, 02 Mar 2024 15:25:54 +0000</pubDate><guid>https://hugovk.dev/blog/2024/tech-style-guides/</guid><description>&lt;p&gt;Here&amp;rsquo;s some tech style guides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/style"&gt;Google developer documentation style guide&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/style/word-list"&gt;Word list&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/style-guide/welcome/"&gt;Microsoft Writing Style Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stylepedia.net/"&gt;Red Hat Technical Writing Style Guide&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://redhat-documentation.github.io/supplementary-style-guide/"&gt;Red Hat supplementary style guide for product documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://bishopfox.com/cybersecurity-style-guide"&gt;Bishop Fox Cybersecurity Style Guide&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://images.bishopfox.com/prod-1437/Documents/Guides/Bishop-Fox-Cybersecurity-Style-Guide-V2.pdf"&gt;PDF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.apple.com/en-gb/guide/applestyleguide/welcome/web"&gt;Apple Style Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://devguide.python.org/documentation/style-guide/"&gt;Python docs style guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ubuntu.com/styleguide/en/"&gt;Canonical Documentation Style Guide&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://canonical-documentation-with-sphinx-and-readthedocscom.readthedocs-hosted.com/style-guide/"&gt;Canonical reStructuredText style guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And some other style guides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.theguardian.com/guardian-observer-style-guide-a"&gt;Guardian and Observer style guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style"&gt;Wikipedia Manual of Style&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://styleguide.mailchimp.com/"&gt;Mailchimp Content Style Guide&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://styleguide.mailchimp.com/writing-for-accessibility/"&gt;Writing for Accessibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://english.meta.stackexchange.com/a/2579/9001"&gt;and more&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;More on style guides:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.writethedocs.org/guide/writing/style-guides/"&gt;Write the Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And a technical writing course:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://asrfs.github.io/Tech-Writing-for-Techies/output/html/00-00-course-overview.html"&gt;Tech Writing for Techies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/statelibraryofnsw/48910246623"&gt;Cootamundra
Cycle Club, Cootamundra, New South Wales, c. 1900&lt;/a&gt;&amp;rdquo; by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/24029425@N06"&gt;Mitchell
Library, State Library of New South Wales&lt;/a&gt;, with
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no
known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>TIL: exclude_also with coverage.py</title><link>https://hugovk.dev/blog/2023/til-excludealso-with-coveragepy/</link><pubDate>Mon, 06 Nov 2023 20:05:55 +0000</pubDate><guid>https://hugovk.dev/blog/2023/til-excludealso-with-coveragepy/</guid><description>&lt;p&gt;Sometimes you have code you want to exclude from the test coverage report, because it
doesn&amp;rsquo;t really make sense to test it.&lt;/p&gt;
&lt;p&gt;For example, maybe you want to exclude:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The &lt;em&gt;old&lt;/em&gt; advice was to add something like this to &lt;code&gt;.coveragerc&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[report]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Regexes for lines to exclude from consideration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;exclude_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # Have to re-enable the standard pragma:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; pragma: no cover&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Don&amp;#39;t complain if non-runnable code isn&amp;#39;t run:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="na"&gt;if __name__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;= .__main__.:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But since
&lt;a href="https://coverage.readthedocs.io/en/7.3.2/changes.html#version-7-2-0-2023-02-22"&gt;coverage.py 7.2.0 (2023-02-22)&lt;/a&gt;
you can use &lt;code&gt;exclude_also&lt;/code&gt; instead and skip that pragma:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;[report]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Regexes for lines to exclude from consideration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;exclude_also&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; # Don&amp;#39;t complain if non-runnable code isn&amp;#39;t run:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; if __name__ == .__main__.:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; [report]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Regexes for lines to exclude from consideration
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-exclude_lines =
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- # Have to re-enable the standard pragma:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- pragma: no cover
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+exclude_also =
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Don&amp;#39;t complain if non-runnable code isn&amp;#39;t run:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if __name__ == .__main__.:
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="thanks" class="relative group"&gt;Thanks &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thanks" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To &lt;a href="https://mastodon.social/@brianokken@fosstodon.org/111360201593749157"&gt;Brian Okken&lt;/a&gt;
for the tip.&lt;/p&gt;
&lt;p&gt;To &lt;a href="https://nedbatchelder.com/"&gt;Ned Batchelder&lt;/a&gt; for maintaining
&lt;a href="https://coverage.readthedocs.io"&gt;Coverage.py&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To the Library of Congress and Flickr Commons for the photo of a
&lt;a href="https://www.flickr.com/photos/library_of_congress/52303625278/"&gt;covered wagon&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Why are there still so many downloads for EOL Python 3.7?</title><link>https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/</link><pubDate>Tue, 22 Aug 2023 19:25:29 +0000</pubDate><guid>https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/</guid><description>&lt;p&gt;Python 3.7 was first released on 2018-06-27 and recently reached end-of-life on
2023-06-27 (&lt;a href="https://peps.python.org/pep-0537/"&gt;PEP 537&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;This means it is no longer receiving security updates and you should upgrade to a newer
version (at least 3.8, but preferably 3.11):&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_6e077b29b5cabd0e.webp 330w,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_ad90e402eecb4a65.webp 660w
 
 ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_cdb22eb89583d3f9.webp 1024w
 
 
 ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_d2c4c505ac08b52e.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1530"
 height="950"
 class="mx-auto my-0 rounded-md"
 alt="Chart showing when different Python versions reached end-of-life"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_ef47086c70966279.png" srcset="https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_cc3c164520be1899.png 330w,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_ef47086c70966279.png 660w
 
 ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_270602c327f8d52d.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_b3d448dc9018fdc5.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Source: &lt;a href="https://devguide.python.org/versions/"&gt;Python Developer's Guide&lt;/a&gt;&lt;/small&gt;&lt;/center&gt;
&lt;P&gt;However, if you look at download numbers from PyPI, 3.7 still accounts for a large share. 3.7 accounted for 25% of all downloads from PyPI in July 2023, compared with 27% for 3.8:
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_12624ee3dab0650c.webp 330w,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_91d260fdf0c39eb.webp 660w
 
 ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_6aca2eb3b6793d60.webp 1024w
 
 
 ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_6dd46e23f5d80008.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="Python download share over time"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_33bab2e6f650d99d.png" srcset="https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_cefd1628eb1f75b9.png 330w,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_33bab2e6f650d99d.png 660w
 
 ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_db24a20e68729e05.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_e6135fcf842691a6.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Source: &lt;a href="https://hugovk.github.io/pypi-tools/charts.html"&gt;pypi-tools&lt;/a&gt;&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;But why does such an old Python version have so many downloads?&lt;/p&gt;
&lt;h2 id="all-downloads" class="relative group"&gt;All downloads &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#all-downloads" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Let&amp;rsquo;s dig into the numbers using a handy tool called
&lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt;, which helps us analyse the
&lt;a href="https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/"&gt;PyPI data from Google BigQuery&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This command fetches all of yesterday&amp;rsquo;s downloads per Python version:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pypinfo --days 1 --percent --markdown &amp;quot;&amp;quot; pyversion&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Python version&lt;/th&gt;
 &lt;th style="text-align: right"&gt;percent&lt;/th&gt;
 &lt;th style="text-align: right"&gt;download count&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;3.8&lt;/td&gt;
 &lt;td style="text-align: right"&gt;25.00%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;189,678,872&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.7&lt;/td&gt;
 &lt;td style="text-align: right"&gt;23.35%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;177,150,010&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.9&lt;/td&gt;
 &lt;td style="text-align: right"&gt;20.25%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;153,663,903&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.10&lt;/td&gt;
 &lt;td style="text-align: right"&gt;15.52%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;117,751,108&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.11&lt;/td&gt;
 &lt;td style="text-align: right"&gt;6.90%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;52,381,884&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.6&lt;/td&gt;
 &lt;td style="text-align: right"&gt;6.29%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;47,749,879&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2.7&lt;/td&gt;
 &lt;td style="text-align: right"&gt;2.32%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;17,602,650&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.5&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.23%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1,778,388&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.4&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.10%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;770,135&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.12&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.03%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;224,223&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.13&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;3,920&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.3&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1,165&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2.8&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;57&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3.2&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;35&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;3&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Total&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;758,756,232&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="all-downloads-by-os" class="relative group"&gt;All downloads by OS &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#all-downloads-by-os" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;But what happens if we check which distros are responsible for those downloads?&lt;/p&gt;
&lt;p&gt;This command gives us the top 20:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pypinfo --days 1 --limit 20 --percent --markdown &amp;quot;&amp;quot; system distro pyversion&lt;/code&gt;&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;system name&lt;/th&gt;
 &lt;th&gt;distro name&lt;/th&gt;
 &lt;th&gt;Python version&lt;/th&gt;
 &lt;th style="text-align: right"&gt;percent&lt;/th&gt;
 &lt;th style="text-align: right"&gt;download count&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Ubuntu&lt;/td&gt;
 &lt;td&gt;3.8&lt;/td&gt;
 &lt;td style="text-align: right"&gt;18.87%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;128,991,062&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Amazon Linux&lt;/td&gt;
 &lt;td&gt;3.7&lt;/td&gt;
 &lt;td style="text-align: right"&gt;14.44%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;98,738,952&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Ubuntu&lt;/td&gt;
 &lt;td&gt;3.9&lt;/td&gt;
 &lt;td style="text-align: right"&gt;12.14%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;83,019,828&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Ubuntu&lt;/td&gt;
 &lt;td&gt;3.10&lt;/td&gt;
 &lt;td style="text-align: right"&gt;9.66%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;66,019,309&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Ubuntu&lt;/td&gt;
 &lt;td&gt;3.7&lt;/td&gt;
 &lt;td style="text-align: right"&gt;5.89%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;40,257,060&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Debian GNU/Linux&lt;/td&gt;
 &lt;td&gt;3.8&lt;/td&gt;
 &lt;td style="text-align: right"&gt;5.70%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;38,958,367&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Debian GNU/Linux&lt;/td&gt;
 &lt;td&gt;3.9&lt;/td&gt;
 &lt;td style="text-align: right"&gt;5.63%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;38,482,436&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Debian GNU/Linux&lt;/td&gt;
 &lt;td&gt;3.7&lt;/td&gt;
 &lt;td style="text-align: right"&gt;4.25%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;29,035,532&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Debian GNU/Linux&lt;/td&gt;
 &lt;td&gt;3.10&lt;/td&gt;
 &lt;td style="text-align: right"&gt;4.15%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;28,348,346&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Debian GNU/Linux&lt;/td&gt;
 &lt;td&gt;3.6&lt;/td&gt;
 &lt;td style="text-align: right"&gt;3.14%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;21,441,883&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Ubuntu&lt;/td&gt;
 &lt;td&gt;3.11&lt;/td&gt;
 &lt;td style="text-align: right"&gt;3.01%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;20,570,619&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Debian GNU/Linux&lt;/td&gt;
 &lt;td&gt;3.11&lt;/td&gt;
 &lt;td style="text-align: right"&gt;2.72%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;18,588,584&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Amazon Linux&lt;/td&gt;
 &lt;td&gt;3.9&lt;/td&gt;
 &lt;td style="text-align: right"&gt;2.43%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;16,593,595&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;CentOS Linux&lt;/td&gt;
 &lt;td&gt;3.6&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1.70%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;11,605,142&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Amazon Linux&lt;/td&gt;
 &lt;td&gt;3.8&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1.47%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;10,035,087&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Amazon Linux&lt;/td&gt;
 &lt;td&gt;3.10&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1.46%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;9,969,514&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Ubuntu&lt;/td&gt;
 &lt;td&gt;2.7&lt;/td&gt;
 &lt;td style="text-align: right"&gt;1.02%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;6,960,697&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Amazon Linux AMI&lt;/td&gt;
 &lt;td&gt;3.6&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.79%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;5,390,823&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Linux&lt;/td&gt;
 &lt;td&gt;Ubuntu&lt;/td&gt;
 &lt;td&gt;3.6&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.79%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;5,388,370&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Windows&lt;/td&gt;
 &lt;td&gt;None&lt;/td&gt;
 &lt;td&gt;3.10&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0.76%&lt;/td&gt;
 &lt;td style="text-align: right"&gt;5,227,519&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Total&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;&lt;/td&gt;
 &lt;td style="text-align: right"&gt;683,622,725&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;We can see Ubuntu with 3.8 is responsible for the largest share of 17%. (That&amp;rsquo;s fine,
3.8 is supported until 2024-10-14.)&lt;/p&gt;
&lt;p&gt;The next is Amazon Linux with 3.7, responsible for a whopping 15% of all downloads!&lt;/p&gt;
&lt;p&gt;The others responsible for 3.7 have a much lower share: Ubuntu (6%) and Debian (4%).&lt;/p&gt;
&lt;p&gt;Tip: replace &lt;code&gt;&amp;quot;&amp;quot;&lt;/code&gt; in the commands above with a package name to get data for just that
package, for example:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pypinfo --days 1 --limit 20 --percent --markdown requests system distro pyversion&lt;/code&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo: Space shuttle Discovery landing at Edwards Air Force Base,
California, 9th December, 1992
(&lt;a href="https://www.flickr.com/photos/nasacommons/30498961044"&gt;source: NASA on The Commons&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Help test the Python 3.12 release candidate!</title><link>https://hugovk.dev/blog/2023/help-test-python-312-beta/</link><pubDate>Fri, 26 May 2023 12:30:43 +0000</pubDate><guid>https://hugovk.dev/blog/2023/help-test-python-312-beta/</guid><description>&lt;p&gt;Calling all Python library maintainers! 🐍&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;third and final&lt;/em&gt; Python 3.12 release candidate is out! 🎉&lt;/p&gt;
&lt;p&gt;&lt;a href="https://peps.python.org/pep-0693/#release-schedule"&gt;PEP 693&lt;/a&gt; defines the release
schedule for Python 3.12.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The first release candidate came out on 6th August 2023&lt;/li&gt;
&lt;li&gt;The second &lt;del&gt;and final&lt;/del&gt; release candidate came out on 6th September 2023&lt;/li&gt;
&lt;li&gt;The third and final release candidate came out on 19th September 2023&lt;/li&gt;
&lt;li&gt;And the full release is set for &lt;strong&gt;2nd October 2023&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In his
&lt;a href="https://discuss.python.org/t/python-3-12-0rc3-released-honestly-the-final-release-candidate-i-swear/34093?u=hugovk"&gt;announcement&lt;/a&gt;,
Thomas Wouters, release manager for Python 3.12 and 3.13, said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We strongly encourage maintainers of third-party Python projects to prepare their
projects for 3.12 compatibilities during this phase, and where necessary publish
Python 3.12 wheels on PyPI to be ready for the final release of 3.12.0. Any binary
wheels built against Python 3.12.0rc3 will work with future versions of Python 3.12.
As always, report any issues to
&lt;a href="https://github.com/python/cpython/issues"&gt;the Python bug tracker&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="test-with-312" class="relative group"&gt;Test with 3.12 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#test-with-312" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;It&amp;rsquo;s now time for us library maintainers to start testing our projects with 3.12.
There&amp;rsquo;s two big benefits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;There have been
&lt;a href="https://docs.python.org/3.12/whatsnew/3.12.html#removed"&gt;removals and changes&lt;/a&gt; in
Python 3.12. Testing now helps us make our code compatible and avoid any big
surprises (for us and our users) at the big launch in October.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We might find bugs in Python itself! Reporting those will help get them fixed and
help everyone.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;h3 id="github-actions-setup-python" class="relative group"&gt;GitHub Actions: setup-python &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#github-actions-setup-python" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;To test the latest alpha, beta or release candidate with
&lt;a href="https://github.com/actions/setup-python#supported-version-syntax"&gt;actions/setup-python&lt;/a&gt;,
add &lt;code&gt;3.12&lt;/code&gt; and &lt;code&gt;allow-prereleases: true&lt;/code&gt; to your workflow matrix.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.8&amp;#34;&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;#34;3.9&amp;#34;&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;#34;3.10&amp;#34;&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;#34;3.11&amp;#34;&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;#34;3.12&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allow-prereleases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(We can instead use &lt;code&gt;3.12-dev&lt;/code&gt; and omit &lt;code&gt;allow-prereleases: true&lt;/code&gt;, but I find the above
a bit neater, and when 3.12.0 final is released in October, it will continue testing
with full release versions.)&lt;/p&gt;
&lt;h3 id="github-actions-deadsnakes" class="relative group"&gt;GitHub Actions: deadsnakes &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#github-actions-deadsnakes" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;For the bleeding edge, we can use
&lt;a href="https://github.com/deadsnakes/action"&gt;deadsnakes/action&lt;/a&gt; to test the latest nightly
build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.8&amp;#34;&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;#34;3.9&amp;#34;&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;#34;3.10&amp;#34;&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;#34;3.11&amp;#34;&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;#34;3.12-dev&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&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;#34;!endsWith(matrix.python-version, &amp;#39;-dev&amp;#39;)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deadsnakes/action@v3.0.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }} (deadsnakes)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;endsWith(matrix.python-version, &amp;#39;-dev&amp;#39;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="travis-ci" class="relative group"&gt;Travis CI &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#travis-ci" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;I recommend moving to another CI.&lt;/p&gt;
&lt;p&gt;In the meantime, you can also add &lt;code&gt;3.12-dev&lt;/code&gt; to &lt;code&gt;.travis.yml&lt;/code&gt;, although at the time of
writing it&amp;rsquo;s pointing to 3.12.0a3+ from 2022-12-07, which is better than nothing.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;python&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.8&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.9&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.10&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.11&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.12-dev&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Again, I recommend moving to another CI.&lt;/p&gt;
&lt;h3 id="other-cis" class="relative group"&gt;Other CIs &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#other-cis" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Do you use other CIs? Please leave a comment if you know how to test 3.12!&lt;/p&gt;
&lt;h2 id="when-to-support-312" class="relative group"&gt;When to support 3.12? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#when-to-support-312" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Now is also a good time to declare support and add the
&lt;code&gt;Programming Language :: Python :: 3.12&lt;/code&gt;
&lt;a href="https://pypi.org/classifiers/"&gt;Trove classifier&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Especially if you have C extensions and other projects depend on yours, a release with
wheels will help them test and prepare.&lt;/p&gt;
&lt;h3 id="abi-breaks" class="relative group"&gt;ABI breaks? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#abi-breaks" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;From the announcement:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There will be &lt;strong&gt;no ABI changes&lt;/strong&gt; from this point forward in the 3.12 series. The
intent is for the final release of 3.12.0, scheduled for Monday, 2023-10-02, to be
identical to this release candidate. &lt;strong&gt;This really is the last chance to find critical
problems in Python 3.12.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let&amp;rsquo;s start testing and releasing for 3.12 now! 🚀&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo: The carpet of the Salt Palace Convention Center grand ballroom,
host of &lt;a href="https://us.pycon.org/2023/"&gt;PyCon 2023&lt;/a&gt;, with a couple of googly eyes added to
make them Pythony (&lt;a href="https://mastodon.social/@hugovk/110239504743679454"&gt;source&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;small&gt;2023-08-14: Updated for RC1&lt;/small&gt;&lt;br&gt;&lt;small&gt;2023-09-06: Updated for
RC2&lt;/small&gt;&lt;br&gt;&lt;small&gt;2023-09-19: Updated for RC3&lt;/small&gt;&lt;/p&gt;</description></item><item><title>TIL: how to disable cron for GitHub forks</title><link>https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/</link><pubDate>Sun, 07 May 2023 15:59:57 +0000</pubDate><guid>https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/</guid><description>&lt;p&gt;GitHub Actions has a useful feature to trigger workflows on a cron schedule. For
example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;push&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pull_request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;cron&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;#34;0 6 * * *&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# daily at 6am&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But if a contributor has enabled GitHub Actions on their fork (which I recommend: test
your contributions before opening a PR), it also runs the cron on their fork. This not
only uses up extra CI resources on the fork:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_9875ce5ab4843705.webp 330w,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_487d71124003ee03.webp 660w
 
 ,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_bb10023c5d319c6a.webp 1024w
 
 
 ,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_28842f1dfdbfd329.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="2170"
 height="1642"
 class="mx-auto my-0 rounded-md"
 alt="A list of daily GitHub Actions runs"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_573216fc38a117c5.png" srcset="https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_797700a7703122e0.png 330w,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_573216fc38a117c5.png 660w
 
 ,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_d9f004bc13afa319.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/tc2y55vsrfl3ulss4629_hu_74176591c608559f.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;It also sends a regular email to the contributor when the workflow fails:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_7268bb72fd766117.webp 330w,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_2eb2d27e9125408a.webp 660w
 
 ,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_75bc4a9de1cb3426.webp 1024w
 
 
 ,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_9c33e356f81f723.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1434"
 height="1320"
 class="mx-auto my-0 rounded-md"
 alt="A daily email from GitHub: &amp;ldquo;hugovk/packaging.python.org Run failed: Test - main&amp;rdquo;"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_488d923819b81ad4.png" srcset="https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_dabc2cec515489c4.png 330w,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_488d923819b81ad4.png 660w
 
 ,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_7b4cdc9ba9016d97.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/til-how-to-disable-cron-for-github-forks/eqdmv5m766yif5qgjols_hu_8d680fbf0c6babeb.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Instead, we only need to run the cron for the upstream. For example, add:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jobs:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; test:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ if: ${{ github.event.repository.fork == false || github.event_name != &amp;#39;schedule&amp;#39; }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; runs-on: ubuntu-latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This only runs the &lt;code&gt;schedule&lt;/code&gt; trigger for the (non-fork) upstream, and all other
triggers run for both upstream and forks.&lt;/p&gt;
&lt;h2 id="also" class="relative group"&gt;Also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;For simpler workflows that only trigger on a cron, such as stalebot or CodeQL runs, we
can add a simpler condition.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Close stale issues&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;cron&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;#34;10 0 * * *&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# daily at 12:10am&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&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;#34;Check issues&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/stale@v8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can completely disable it for forks:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; jobs:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; stale:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ if: github.event.repository.fork == false
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; runs-on: ubuntu-latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="ps" class="relative group"&gt;PS &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#ps" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Use single quotes in these lines, double quotes are invalid here (&lt;code&gt;Unexpected symbol&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="thanks" class="relative group"&gt;Thanks &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thanks" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To &lt;a href="https://fosstodon.org/@AlexWaygood"&gt;Alex Waygood&lt;/a&gt; for the tip.&lt;/p&gt;
&lt;p&gt;To the British Library and Flickr Commons for the illustration of a
&lt;a href="https://www.flickr.com/photos/britishlibrary/11097061804/"&gt;chronograph&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="update" class="relative group"&gt;Update &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#update" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;2025-04-28: Instead of hardcoding the upstream repo&amp;rsquo;s owner with
&lt;code&gt;github.repository_owner == 'octocat'&lt;/code&gt;, use &lt;code&gt;github.event.repository.fork == false&lt;/code&gt;.
This makes it easier to diff across projects in different organisations and consistent
config makes for easier maintenance.&lt;/p&gt;</description></item><item><title>Printable PyCon 2023 schedule</title><link>https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/</link><pubDate>Sat, 15 Apr 2023 10:10:02 +0000</pubDate><guid>https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/</guid><description>&lt;p&gt;Want to print out the PyCon US 2023 schedule? Paper doesn&amp;rsquo;t run out of batteries, is
easy to scribble on, and stuff into a pocket.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_e889a0cacc0cd7a.webp 330w,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_e5eacfe961ca000d.webp 660w
 
 ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_2803aef5d4471444.webp 1024w
 
 
 ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_c59fc197ea5e1348.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="3000"
 height="3753"
 class="mx-auto my-0 rounded-md"
 alt="Printed PyCons schedule"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_f8897bffd35b7dc9.jpg" srcset="https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_e385285daf7d5f52.jpg 330w,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_f8897bffd35b7dc9.jpg 660w
 
 ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_d93934ac97530c05.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_35ad344eaa706a14.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s some custom CSS and JavaScript to make it nicely printable.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the
&lt;a href="https://chrome.google.com/webstore/detail/styler/bogdgcfoocbajfkjjolkmcdcnnellpkb?hl=en"&gt;Styler browser extension&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;View a PyCon schedule page such as &lt;a href="https://us.pycon.org/2023/schedule/talks/"&gt;https://us.pycon.org/2023/schedule/talks/&lt;/a&gt; and
click the Styler extension&amp;rsquo;s S icon&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Paste this CSS into the upper box:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-CSS" data-lang="CSS"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;aside&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;sidebar&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;badges&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;menu-button&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;open-menu&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;button&lt;/span&gt;&lt;span class="p"&gt;#&lt;/span&gt;&lt;span class="nn"&gt;theme-switch&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;footer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;display&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;font-size&lt;/span&gt;&lt;span class="p"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;main&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;pycon-schedule&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;calendar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="k"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="kt"&gt;%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="k"&gt;media&lt;/span&gt; &lt;span class="nt"&gt;print&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;a&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;href&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nd"&gt;after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;Paste this JavaScript into the lower box:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2023/schedule/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nb"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2023/schedule/presentation/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;body&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pycon-schedule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;
&lt;li&gt;Print!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&amp;rsquo;ll run on any of the &lt;code&gt;https://us.pycon.org/2023/schedule/*&lt;/code&gt; pages, but not the
individual presentation pages such as
&lt;code&gt;https://us.pycon.org/2023/schedule/presentation/88/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Once printed, you can click the Styler icon and the &lt;code&gt;x&lt;/code&gt; button to disable Styler for the
site so you can browse the original version.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_ee148a7a5e9858e3.webp 330w,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_9feecbcabbf35d5d.webp 660w
 
 ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_4574c03e546b6440.webp 1024w
 
 
 ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_42e43d17039b4e92.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="3000"
 height="3725"
 class="mx-auto my-0 rounded-md"
 alt="Printed PyCons schedule"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_34047f844a6b790b.jpg" srcset="https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_5116d3b928b9f654.jpg 330w,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_34047f844a6b790b.jpg 660w
 
 ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_4153de02ea7c6223.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_67bbcd5fc715014c.jpg 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;</description></item><item><title>Bits of Pluto on Mastodon</title><link>https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/</link><pubDate>Sat, 18 Feb 2023 13:16:49 +0000</pubDate><guid>https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/</guid><description>&lt;h2 id="what-i-built" class="relative group"&gt;What I built &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#what-i-built" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;I built a Mastodon bot that posts a a different bit of Pluto every six hours.&lt;/p&gt;
&lt;h3 id="category-submission" class="relative group"&gt;Category Submission &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#category-submission" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Wacky Wildcard&lt;/p&gt;
&lt;h3 id="app-link" class="relative group"&gt;App Link &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#app-link" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href="https://botsin.space/@bitsofpluto"&gt;https://botsin.space/@bitsofpluto&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="screenshots" class="relative group"&gt;Screenshots &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#screenshots" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;center&gt;https://botsin.space/@bitsofpluto/109854013035140138&lt;/center&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds_hu_33a8d9d4fc65f9cd.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds_hu_dabf1d7b8a1d63d2.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds_hu_d37d86f255822d85.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds_hu_e3e2fd0963b8bfbf.webp 1142w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1142"
 height="1150"
 class="mx-auto my-0 rounded-md"
 alt="Dark shadows caused by mountains"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds_hu_c1b1789a7c32ae8a.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds_hu_47adadd5070cb428.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds_hu_c1b1789a7c32ae8a.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds_hu_7c2b6022a3895a12.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/r78gpvcyl5ai09z93cds.png 1142w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;center&gt;https://botsin.space/@bitsofpluto/109878078918934666&lt;/center&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p_hu_b7a734282c825370.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p_hu_2da6881fff9710f0.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p_hu_f4671520ee4ab4d9.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p_hu_b2a4308b71c614f4.webp 1146w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1146"
 height="1126"
 class="mx-auto my-0 rounded-md"
 alt="Red cratered area to the left giving way to creamy pale plain to the right"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p_hu_7c716336fd2295fc.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p_hu_86888ac9c675d516.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p_hu_7c716336fd2295fc.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p_hu_24646e919097665.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/7zlvfcqdh1nj4nlgpl9p.png 1146w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;center&gt;https://botsin.space/@bitsofpluto/109882325572738354&lt;/center&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj_hu_e3167248faec6f0a.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj_hu_c52d5abb869d7262.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj_hu_46ebe8db6d29ca55.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj_hu_29163d7f084c8710.webp 1148w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1148"
 height="1130"
 class="mx-auto my-0 rounded-md"
 alt="Long ridges causing shadows"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj_hu_2c0617d1fb6f843e.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj_hu_b4acfa24a5071338.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj_hu_2c0617d1fb6f843e.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj_hu_e34f3081a8634838.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/fr8oxtml9trqb7kg1ptj.png 1148w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;center&gt;https://botsin.space/@bitsofpluto/109872416513104327&lt;/center&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq_hu_bf2be804b43511df.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq_hu_b8840988b12976c2.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq_hu_6a0b60a65315f7bd.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq_hu_b2572388d8a57e99.webp 1142w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1142"
 height="1130"
 class="mx-auto my-0 rounded-md"
 alt="Reddish area with many craters, including a large crater with a central peak"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq_hu_4377929563f7edd5.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq_hu_9b9dac250811e8cb.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq_hu_4377929563f7edd5.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq_hu_3ed0628312f2a919.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/ys3xv8itck0jpjfttqjq.png 1142w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;center&gt;https://botsin.space/@bitsofpluto/109869584884331747&lt;/center&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847_hu_66b9360b12b7b94f.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847_hu_621fcdc6e041345.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847_hu_586f3b95d9a761b6.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847_hu_b50ea47a6c8632f4.webp 1156w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1156"
 height="1138"
 class="mx-auto my-0 rounded-md"
 alt="Mostly space with a sliver of surface in the corner"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847_hu_20cc32a67a307c30.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847_hu_f0c3aeee5b59e62c.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847_hu_20cc32a67a307c30.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847_hu_e5cae11c03ce09d7.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tevx5s0kxjsq1kwlm847.png 1156w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;center&gt;https://botsin.space/@bitsofpluto/109835610772369900&lt;/center&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7_hu_8546f54077f88093.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7_hu_357f3d5660e4e53a.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7_hu_d87823bda8b29aa9.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7_hu_1b4bcb033c19ced2.webp 1128w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1128"
 height="1138"
 class="mx-auto my-0 rounded-md"
 alt="Deep red with ridges, and the curve of Pluto against deep black space"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7_hu_fd19ede9951a31a7.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7_hu_963e90d250d87b68.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7_hu_fd19ede9951a31a7.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7_hu_47c5058b6ad519a3.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/wscpk9b699sexhly1es7.png 1128w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;center&gt;https://botsin.space/@bitsofpluto/109828532816033685&lt;/center&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr_hu_6724661e5847abb0.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr_hu_965db20c4842179a.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr_hu_70723fb853a925e4.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr_hu_7ae063af5c37c0a0.webp 1140w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1140"
 height="1128"
 class="mx-auto my-0 rounded-md"
 alt="Mostly pale surface with mountainous areas and craters"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr_hu_2445420c5fd667a2.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr_hu_8d2739bd8784a5c4.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr_hu_2445420c5fd667a2.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr_hu_2d52ad3360189990.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/144ix05imzd5dv3qvmpr.png 1140w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;hr&gt;
&lt;center&gt;https://botsin.space/@bitsofpluto/109845519798723221&lt;/center&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7_hu_aafd99d5f841867f.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7_hu_3f5a275aed1d1501.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7_hu_f9e3df87b3c5100f.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7_hu_18014e3d0ddfd599.webp 1134w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1134"
 height="1128"
 class="mx-auto my-0 rounded-md"
 alt="Mixed white and red surface pocked with many craters"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7_hu_1f9818ed4020e0e8.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7_hu_bd89bea516a30c9e.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7_hu_1f9818ed4020e0e8.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7_hu_b1b1d2cca003c080.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/68l14un3lt032llpcnl7.png 1134w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="description" class="relative group"&gt;Description &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#description" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Bits of Pluto posts a different bit of Pluto to Mastodon every six hours. Each is a crop
from an image by NASA&amp;rsquo;s New Horizons spacecraft.&lt;/p&gt;
&lt;h3 id="link-to-source-code" class="relative group"&gt;Link to Source Code &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#link-to-source-code" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href="https://github.com/hugovk/bitsofpluto"&gt;https://github.com/hugovk/bitsofpluto&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="permissive-license" class="relative group"&gt;Permissive License &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#permissive-license" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;MIT&lt;/p&gt;
&lt;h2 id="background" class="relative group"&gt;Background &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#background" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;For decades, Pluto had only been a speck of light until the NASA&amp;rsquo;s Hubble Space
Telescope captured it in
&lt;a href="https://hubblesite.org/contents/news-releases/2010/news-2010-06.html"&gt;never-before-seen detail&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j_hu_45a41b6a78c353f.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j_hu_4065a518d44d05d1.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j_hu_6c80b0de6ecb1562.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j_hu_a45415f29c66ba23.webp 1280w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1280"
 height="1280"
 class="mx-auto my-0 rounded-md"
 alt="NASA: &amp;ldquo;Hubble&amp;rsquo;s view isn&amp;rsquo;t sharp enough to see craters or mountains, if they exist on the surface, but Hubble reveals a complex-looking and variegated world with white, dark-orange, and charcoal-black terrain.&amp;rdquo;"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j_hu_ff6458b19fca246a.jpg" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j_hu_564b48c5e55b0667.jpg 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j_hu_ff6458b19fca246a.jpg 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j_hu_f09f1e11bbf39557.jpg 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/tvnfv986pgdognhevi3j.jpg 1280w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Just a few years later, I followed the journey of NASA&amp;rsquo;s New Horizons spacecraft in awe
as it flew past Pluto, in the far depths of our solar system.&lt;/p&gt;
&lt;p&gt;Like many others I was especially struck by the beautiful photographs it shot,
especially this large, detailed image:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y_hu_ac25c4976724007e.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y_hu_46985eba34c788da.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y_hu_354e50488c98dd5f.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y_hu_ece60ee3670d88bf.webp 1041w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1041"
 height="1041"
 class="mx-auto my-0 rounded-md"
 alt="Pluto in enhanced colour"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y_hu_9bf9d22dd721ec7a.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y_hu_ded6dad63b954a54.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y_hu_9bf9d22dd721ec7a.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y_hu_3cf662f57387cb6b.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/x3cu2253wz13viuoyg3y.png 1041w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;As NASA describe it:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;NASA’s New Horizons spacecraft captured this high-resolution enhanced color view of
Pluto on July 14, 2015. The image combines blue, red and infrared images taken by the
Ralph/Multispectral Visual Imaging Camera (MVIC). Pluto’s surface sports a remarkable
range of subtle colors, enhanced in this view to a rainbow of pale blues, yellows,
oranges, and deep reds. Many landforms have their own distinct colors, telling a
complex geological and climatological story that scientists have only just begun to
decode. The image resolves details and colors on scales as small as 0.8 miles (1.3
kilometers). The viewer is encouraged to zoom in on the
&lt;a href="http://www.nasa.gov/sites/default/files/thumbnails/image/crop_p_color2_enhanced_release.png"&gt;full resolution&lt;/a&gt;
image on a larger screen to fully appreciate the complexity of Pluto’s surface
features.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I second the recommendation to zoom in on the full resolution! The detail and is
astounding, especially compared to the Hubble image.&lt;/p&gt;
&lt;p&gt;I thought it would be fascinating to chop this up and put it in a social media feed to
enjoy different aspects of the detail, to punctuate the doomscrolling with a moment of
wonder.&lt;/p&gt;
&lt;h3 id="how-i-built-it" class="relative group"&gt;How I built it &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-i-built-it" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;I built the bot in Python. Originally it posted to Twitter, but as the future of bots on
Twitter is
&lt;a href="https://www.theverge.com/2023/2/2/23582982/twitter-api-free-access-cutoff-bot-developers-shutdown"&gt;less&lt;/a&gt;
&lt;a href="https://www.theverge.com/2023/2/2/23582982/twitter-api-free-access-cutoff-bot-developers-shutdown"&gt;than&lt;/a&gt;
&lt;a href="https://www.vice.com/en/article/4axzzd/twitters-latest-chaotic-move-will-kill-the-sites-best-bots-account-owners-say"&gt;certain&lt;/a&gt;,
the time was ready to port it over to
&lt;a href="https://botwiki.org/resources/fediverse-bots/"&gt;Mastodon&lt;/a&gt; where bots are welcome.&lt;/p&gt;
&lt;p&gt;On the way, I learned how to calculate brightness in images, how to use the Mastodon
API, and how to set up a bot on Linode and (the appropriately-named!) botsin.space.&lt;/p&gt;
&lt;p&gt;The bot does two things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Get a bit of Pluto&lt;/li&gt;
&lt;li&gt;Post to Mastodon&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="1-get-a-bit-of-pluto" class="relative group"&gt;1. Get a bit of Pluto &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#1-get-a-bit-of-pluto" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;The
&lt;a href="https://github.com/hugovk/bitsofpluto/blob/d98c8fe4749c31c4d142b89a06d0241773794f00/bitsofpluto.py#L92-L139"&gt;&lt;code&gt;bitsofpluto()&lt;/code&gt; function&lt;/a&gt;
takes a single parameter, the path to the full resolution Pluto image.&lt;/p&gt;
&lt;p&gt;The image is opened using &lt;a href="https://pillow.readthedocs.io/"&gt;Pillow&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We then choose a random width for the image, somewhere between 800 and 2000 pixels, with
height is set to ¾ the width. This is to give a different zoom level each time. We then
randomly select a window to crop. This is our potential bit of Pluto.&lt;/p&gt;
&lt;p&gt;But we don&amp;rsquo;t stop there. The image has quite a large background of empty black (or
nearly black) space. We don&amp;rsquo;t want to post that. So we sample 9 points from the image
(corners, centre point, and bisecting points) and measure the brightness where 0 is
black and 255 is white. If brightness is under 10, we count it as a dark point. If there
are 6 or less, we save the image to a temporary directory.&lt;/p&gt;
&lt;p&gt;Otherwise if there are too many dark points, we discard this crop, and start again from
the top. I came up with these figures of 10 and 7 through trial-and-error. It&amp;rsquo;s quite
nice to sometimes get an image with a
&lt;a href="https://botsin.space/@bitsofpluto/109880910091459752"&gt;corner of Pluto&amp;rsquo;s curvature against the darkness of space&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1_hu_a9a3a3847be60f4b.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1_hu_d88447e1186c6313.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1_hu_69b36d743e196dc.webp 1024w
 
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1_hu_88521e9b5d484612.webp 1158w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1158"
 height="960"
 class="mx-auto my-0 rounded-md"
 alt="Light surface of Pluto with a few cratered, the darkness of space behind"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1_hu_97fb0a1df1c74131.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1_hu_a2e70c4a7b128a12.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1_hu_97fb0a1df1c74131.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1_hu_cbae4e2e3d2221c5.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/sh69yjordzcx0q0p20o1.png 1158w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 id="2-post-to-mastodon" class="relative group"&gt;2. Post to Mastodon &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#2-post-to-mastodon" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h4&gt;&lt;p&gt;We use the &lt;a href="https://mastodonpy.readthedocs.io/"&gt;Mastodon.py library&lt;/a&gt; to via the Mastodon
API.&lt;/p&gt;
&lt;p&gt;The
&lt;a href="https://github.com/hugovk/bitsofpluto/blob/d98c8fe4749c31c4d142b89a06d0241773794f00/bitsofpluto.py#L46-L89"&gt;&lt;code&gt;toot_it() function&lt;/code&gt;&lt;/a&gt;
first reads in the credentials for posting. I generated them using a
&lt;a href="https://github.com/hugovk/mastodon-tools/blob/main/mastodon_create_app.py"&gt;helper script&lt;/a&gt;
I wrote based upon &lt;a href="https://www.decontextualize.com/"&gt;Allison Parrish&lt;/a&gt;&amp;rsquo;s
&lt;a href="https://gist.github.com/aparrish/661fca5ce7b4882a8c6823db12d42d26"&gt;instructions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Once authenticated, there&amp;rsquo;s a two-step process for posting:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Upload the image using &lt;code&gt;api.media_post()&lt;/code&gt;, which returns a reference to the media&lt;/li&gt;
&lt;li&gt;Create the Mastodon post, using the media reference&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="hosting" class="relative group"&gt;Hosting &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#hosting" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h4&gt;&lt;h5 id="linode" class="relative group"&gt;Linode &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#linode" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;&lt;p&gt;The bot code is hosted on Linode. The nice thing about Mastodon bots is that they rarely
need expensive or high-end hosting. I created a Linode using the cheapest option: for
$5/month the &amp;ldquo;Nanode 1 GB&amp;rdquo; plan gives 1 GB RAM, 1 CPU and 25 GB storage, more than
enough for our needs.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_a0a45703a4d1e58f.webp 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_f75965eb408735f5.webp 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_4bbefc9434282230.webp 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_5400822fb79a0476.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="2430"
 height="1574"
 class="mx-auto my-0 rounded-md"
 alt="Linode 24-hour usage stats: average 0.43% CPU usage, and about ~1 Kb/s average network both inbound and outbound"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_31fba2ab4a1b55e9.png" srcset="https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_e978ac8a36a24cb.png 330w,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_31fba2ab4a1b55e9.png 660w
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_f5ba982186909d2c.png 1024w
 
 
 ,https://hugovk.dev/blog/2023/bits-of-pluto-on-mastodon/6oiwss0gys3ndhlnsksj_hu_280e7c69d9528786.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;I chose a Ubuntu 22.04 LTS image: LTS means &amp;ldquo;long-term support&amp;rdquo;, it will be supported
for 5 years, until April 2027. And I chose the Frankfurt, DE region because it&amp;rsquo;s closest
to me.&lt;/p&gt;
&lt;p&gt;After creating a user, I logged in via SSH, and cloned the
&lt;a href="https://github.com/hugovk/bitsofpluto"&gt;bitsofpluto repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I ran &lt;code&gt;crontab -e&lt;/code&gt; to schedule it to run once every six hours:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-cron" data-lang="cron"&gt;0 */6 * * * /home/botuser/bin/scheduled/0000-06-repeat.sh &amp;gt; /tmp/logs/0000-06-repeat.log
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Where &lt;code&gt;0000-06-repeat.sh&lt;/code&gt; contains:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#set -e&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p /tmp/logs/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~/github/bitsofpluto/crontask.sh &amp;gt; /tmp/logs/bitsofpluto.log 2&amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which, when the cron triggers, switches to the repo, fetches any recent changes, and
then runs the bot to make a post:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 bitsofpluto.py --yaml ~/bin/data/bitsofpluto.yaml --no-web
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h5 id="botsinspace" class="relative group"&gt;botsin.space &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#botsinspace" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h5&gt;&lt;p&gt;The bot&amp;rsquo;s Mastodon account is on &lt;a href="https://muffinlabs.com/"&gt;Colin Mitchell&lt;/a&gt;&amp;rsquo;s
&lt;a href="https://botsin.space/about"&gt;botsin.space instance&lt;/a&gt;, home to many other
&lt;a href="https://botsin.space/public/local"&gt;excellent bots&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="additional-resourcesinfo" class="relative group"&gt;Additional Resources/Info &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#additional-resourcesinfo" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;I&amp;rsquo;ve made two other Mastodon bots:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://botsin.space/@tiny_bus_stop"&gt;@tiny_bus_stop&lt;/a&gt; -
&lt;a href="https://github.com/hugovk/cheapbotsdonequick/blob/main/tiny_bus_stop.json"&gt;uses Tracery&lt;/a&gt;
and &lt;a href="https://cheapbotstootsweet.com/"&gt;Cheap Bots, Toot Sweet!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://botsin.space/@FlagFacts"&gt;@FlagFacts&lt;/a&gt; -
&lt;a href="https://github.com/hugovk/randimgbot"&gt;uses Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Enjoy!&lt;/p&gt;</description></item><item><title>How to search 5,000 Python projects</title><link>https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/</link><pubDate>Fri, 09 Dec 2022 12:44:21 +0000</pubDate><guid>https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/</guid><description>&lt;h2 id="why" class="relative group"&gt;Why? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#why" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Often Python core developers think about deprecating and removing old bits of the
language. But first it&amp;rsquo;s a good idea to get an idea of how much the old bits are used.
Searching the
&lt;a href="https://hugovk.github.io/top-pypi-packages/"&gt;5,000 most-popular projects on PyPI&lt;/a&gt; is a
helpful proxy to gauge community use.&lt;/p&gt;
&lt;h2 id="how" class="relative group"&gt;How? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Core developer &lt;a href="https://vstinner.readthedocs.io/"&gt;Victor Stinner&lt;/a&gt; has written a couple
of useful scripts that live in his &lt;a href="https://github.com/vstinner/misc"&gt;misc&lt;/a&gt; repo.&lt;/p&gt;
&lt;h2 id="setup" class="relative group"&gt;Setup &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#setup" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;First, clone the repo somewhere, doesn&amp;rsquo;t matter where:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p ~/github
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/github/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/vstinner/misc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Optionally for some colour in the logs:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m pip install termcolor
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then download 5,000 sdists! Again, doesn&amp;rsquo;t matter where:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; mkdir -p ~/top-pypi/output
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/top-pypi/output/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3 ~/github/misc/cpython/download_pypi_top.py .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Download JSON from: https://hugovk.github.io/top-pypi-packages/top-pypi-packages-30-days.min.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Project#: 5000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[1/5000] Saving to ./boto3-1.26.25.tar.gz (101.7 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[2/5000] Saving to ./botocore-1.29.25.tar.gz (10426.8 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[3/5000] Saving to ./urllib3-1.26.13.tar.gz (293.4 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[4990/5000] Saving to ./meld3-2.0.1.tar.gz (35.3 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[4991/5000] Saving to ./browserstack-local-1.2.4.tar.gz (6.6 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Cannot find URL for project: allensdk
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Cannot find URL for project: dataframe-image
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[4994/5000] Saving to ./SQLAlchemy-Continuum-1.3.13.tar.gz (79.2 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[4995/5000] Saving to ./sarge-0.1.7.post1.tar.gz (25.1 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[4996/5000] Saving to ./confusable_homoglyphs-3.2.0.tar.gz (158.1 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[4997/5000] Saving to ./YTThumb-1.4.5.tar.gz (3.0 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[4998/5000] Saving to ./azure-ai-ml-1.2.0.zip (4486.3 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[4999/5000] Saving to ./pythena-1.6.0.tar.gz (5.2 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[5000/5000] Saving to ./interchange-2021.0.4.tar.gz (25.2 kB)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Downloaded 5000 projects in 1602.4 seconds
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With colour:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_fa3e3fb4eb825f40.webp 330w,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_85b6d66787eb0990.webp 660w
 
 ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_3f7e937dc37195a2.webp 1024w
 
 
 ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_8e23c93da540b596.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1938"
 height="1378"
 class="mx-auto my-0 rounded-md"
 alt="Tail end of downloading files: successful downloads in green, unsuccessful downloads in red"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_17b745f05407bad7.png" srcset="https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_ad96a36bd967371e.png 330w,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_17b745f05407bad7.png 660w
 
 ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_d7f9120696dbade3.png 1024w
 
 
 ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_532197e5b51519af.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;⏳ This will take a bit of time. Some projects don&amp;rsquo;t have sdists, nothing to worry
about, we will still end up with a good number. At the time of writing (2022-12-09), it
took me just under 27 minutes to download 4,748 files, taking up 5.37 GB of space.&lt;/p&gt;
&lt;p&gt;If you want to download fewer, specify how many:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 ~/github/misc/cpython/download_pypi_top.py . &lt;span class="m"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="search" class="relative group"&gt;Search &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#search" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Next, we can search all the sdists using another script. And we don&amp;rsquo;t need to extract
them!&lt;/p&gt;
&lt;p&gt;For example, &lt;code&gt;configparser&lt;/code&gt;&amp;rsquo;s &lt;code&gt;LegacyInterpolation&lt;/code&gt; was deprecated in
&lt;a href="https://peps.python.org/pep-0392/"&gt;Python 3.2&lt;/a&gt; (released February 2011), but only in
docs and without raising a
&lt;a href="https://docs.python.org/3/library/exceptions.html#DeprecationWarning"&gt;&lt;code&gt;DeprecationWarning&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;How much is it used in the top 5k?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; ~/top-pypi/output/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gp"&gt;$&lt;/span&gt; python3 ~/github/misc/cpython/search_pypi_top.py -q . &lt;span class="s2"&gt;&amp;#34;LegacyInterpolation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./hexbytes-0.3.0.tar.gz: hexbytes-0.3.0/.tox/lint/lib/python3.9/site-packages/mypy/typeshed/stdlib/configparser.pyi: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./hexbytes-0.3.0.tar.gz: hexbytes-0.3.0/.tox/lint/lib/python3.9/site-packages/mypy/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./hexbytes-0.3.0.tar.gz: hexbytes-0.3.0/.tox/py39-lint/lib/python3.9/site-packages/mypy/typeshed/stdlib/configparser.pyi: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./hexbytes-0.3.0.tar.gz: hexbytes-0.3.0/.tox/py39-lint/lib/python3.9/site-packages/mypy/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./jedi-0.18.2.tar.gz: jedi-0.18.2/jedi/third_party/typeshed/stdlib/3/configparser.pyi: class LegacyInterpolation(Interpolation): ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./mypy-0.991.tar.gz: mypy-0.991/mypy/typeshed/stdlib/configparser.pyi: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./mypy-0.991.tar.gz: mypy-0.991/mypy/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth-hash-0.5.1.tar.gz: eth-hash-0.5.1/.tox/lint/lib/python3.9/site-packages/mypy/typeshed/stdlib/configparser.pyi: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth-hash-0.5.1.tar.gz: eth-hash-0.5.1/.tox/lint/lib/python3.9/site-packages/mypy/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth-account-0.7.0.tar.gz: eth-account-0.7.0/.tox/lint/lib/python3.10/site-packages/mypy/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth-account-0.7.0.tar.gz: eth-account-0.7.0/.tox/py310-lint/lib/python3.10/site-packages/mypy/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth-utils-2.1.0.tar.gz: eth-utils-2.1.0/.tox/lint/lib/python3.9/site-packages/mypy/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./pytype-2022.11.29.tar.gz: pytype-2022.11.29/pytype/typeshed/stdlib/configparser.pyi: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./pytype-2022.11.29.tar.gz: pytype-2022.11.29/pytype/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./pylint-2.15.8.tar.gz: pylint-2.15.8/pylint/checkers/stdlib.py: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./pyre-check-0.9.17.tar.gz: pyre-check-0.9.17/typeshed/stdlib/configparser.pyi: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./pyre-check-0.9.17.tar.gz: pyre-check-0.9.17/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/backports/configparser/__init__.py: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/backports/configparser/__init__.py: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/backports/configparser/__init__.py: &amp;#34;LegacyInterpolation has been deprecated since Python 3.2 &amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/configparser.py: LegacyInterpolation,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/configparser.py: &amp;#34;LegacyInterpolation&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/test_configparser.py: elif isinstance(self.interpolation, configparser.LegacyInterpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/test_configparser.py: elif isinstance(self.interpolation, configparser.LegacyInterpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/test_configparser.py: elif isinstance(self.interpolation, configparser.LegacyInterpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/test_configparser.py: class ConfigParserTestCaseLegacyInterpolation(ConfigParserTestCase):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/test_configparser.py: interpolation = configparser.LegacyInterpolation()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./configparser-5.3.0.tar.gz: configparser-5.3.0/src/test_configparser.py: configparser.LegacyInterpolation()
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth-rlp-0.3.0.tar.gz: eth-rlp-0.3.0/.tox/lint/lib/python3.9/site-packages/mypy/typeshed/stdlib/3/configparser.pyi: class LegacyInterpolation(Interpolation): ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth-rlp-0.3.0.tar.gz: eth-rlp-0.3.0/venv-erlp/lib/python3.9/site-packages/jedi/third_party/typeshed/stdlib/3/configparser.pyi: class LegacyInterpolation(Interpolation): ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth-rlp-0.3.0.tar.gz: eth-rlp-0.3.0/venv-erlp/lib/python3.9/site-packages/mypy/typeshed/stdlib/3/configparser.pyi: class LegacyInterpolation(Interpolation): ...
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;./eth_abi-3.0.1.tar.gz: eth_abi-3.0.1/.tox/lint/lib/python3.10/site-packages/mypy/typeshed/stdlib/configparser.pyi: class LegacyInterpolation(Interpolation):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Time: 0:00:17.957695
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Found 32 matching lines in 12 projects
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With colour:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_2cf5a79d31726849.webp 330w,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_562715d450191877.webp 660w
 
 ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_1e0051929dbfa1d2.webp 1024w
 
 
 ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_808d6685147e5536.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1340"
 height="774"
 class="mx-auto my-0 rounded-md"
 alt="Same output as above but with the source filename in purple and LegacyInterpolation in orange"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_48692cec6d5401bb.png" srcset="https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_1b2c35e9704bdb6d.png 330w,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_48692cec6d5401bb.png 660w
 
 ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_5b7e785b73498331.png 1024w
 
 
 ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_a699325602be0384.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Answer: very little, mostly backports and type stubs. This told us it&amp;rsquo;s a good candidate
for removal, so a proper &lt;code&gt;DeprecationWarning&lt;/code&gt; was
&lt;a href="https://docs.python.org/3/whatsnew/3.11.html#standard-library"&gt;added in Python 3.11&lt;/a&gt;
(released October 2022) and it will be
&lt;a href="https://peps.python.org/pep-0387/"&gt;removed in Python 3.13&lt;/a&gt; (October 2024).&lt;/p&gt;
&lt;p&gt;The tool searches using a regex, so you can look for more complicated things like
&lt;code&gt;&amp;quot;\b(currentThread|activeCount|notifyAll|isSet|isDaemon|setDaemon)\b&amp;quot;&lt;/code&gt;, and it can also
log to file. See &lt;code&gt;--help&lt;/code&gt; for other options.&lt;/p&gt;
&lt;p&gt;Happy searching! 🔎&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/24029425@N06/11237637113"&gt;The
card index department&lt;/a&gt;&amp;rdquo; by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/24029425@N06"&gt;Boston
Public Library&lt;/a&gt; is licensed under
&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC
BY 2.0&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Help test Python 3.11 beta!</title><link>https://hugovk.dev/blog/2022/help-test-python-311/</link><pubDate>Tue, 31 May 2022 17:10:00 +0000</pubDate><guid>https://hugovk.dev/blog/2022/help-test-python-311/</guid><description>&lt;p&gt;Calling all Python library maintainers! 🐍&lt;/p&gt;
&lt;p&gt;Python 3.11 is in beta! 🎉&lt;/p&gt;
&lt;p&gt;&lt;a href="https://peps.python.org/pep-0664/#release-schedule"&gt;PEP 664&lt;/a&gt; defines the release
schedule for Python 3.11.0:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The second beta came out on 31st May 2022&lt;/li&gt;
&lt;li&gt;The first release candidate is set for 1st August 2022&lt;/li&gt;
&lt;li&gt;And the full release is set for 3rd October 2022&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In his
&lt;a href="https://discuss.python.org/t/python-3-11-0b2-is-now-available/16111?u=hugovk"&gt;announcement&lt;/a&gt;,
Pablo Galindo Salgado, release manager for Python 3.10 and 3.11, said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We &lt;strong&gt;strongly encourage&lt;/strong&gt; maintainers of third-party Python projects to &lt;strong&gt;test with
3.11&lt;/strong&gt; during the beta phase and report issues found to the
&lt;a href="https://github.com/python/cpython/issues"&gt;Python bug tracker&lt;/a&gt; as soon as possible.
While the release is planned to be feature complete entering the beta phase, it is
possible that features may be modified or, in rare cases, deleted up until the start
of the release candidate phase (Monday, 2021-08-02). Our goal is have no ABI changes
after beta 4 and as few code changes as possible after 3.11.0rc1, the first release
candidate. To achieve that, it will be &lt;strong&gt;extremely important&lt;/strong&gt; to get as much exposure
for 3.11 as possible during the beta phase.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="test-with-311" class="relative group"&gt;Test with 3.11 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#test-with-311" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;It&amp;rsquo;s now time for library maintainers to start testing 3.11 with your project. You don&amp;rsquo;t
need to declare support and release for 3.11 yet, but there&amp;rsquo;s two big benefits to
testing with 3.11 on CI:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;There have been removals and changes in Python 3.11. Testing now will help you make
your code compatible and avoid any big surprises (for you and your users) at the big
launch in October.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;You might find bugs in Python itself! Reporting those will help get them fixed and
help everyone.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;h3 id="github-actions-setup-python" class="relative group"&gt;GitHub Actions: setup-python &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#github-actions-setup-python" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;To test the latest alpha, beta or release candidate with
&lt;a href="https://github.com/actions/setup-python#available-versions-of-python"&gt;actions/setup-python&lt;/a&gt;,
add &lt;code&gt;3.11-dev&lt;/code&gt; to your workflow matrix.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.7&amp;#34;&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;#34;3.8&amp;#34;&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;#34;3.9&amp;#34;&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;#34;3.10&amp;#34;&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;#34;3.11-dev&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="github-actions-deadsnakes" class="relative group"&gt;GitHub Actions: deadsnakes &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#github-actions-deadsnakes" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;For the bleeding edge, we can use
&lt;a href="https://github.com/deadsnakes/action"&gt;deadsnakes/action&lt;/a&gt; to test the latest nightly
build:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runs-on&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ubuntu-latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fail-fast&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.7&amp;#34;&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;#34;3.8&amp;#34;&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;#34;3.9&amp;#34;&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;#34;3.10&amp;#34;&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;#34;3.11-dev&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;steps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/checkout@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&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;#34;!endsWith(matrix.python-version, &amp;#39;-dev&amp;#39;)&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;actions/setup-python@v3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;uses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deadsnakes/action@v2.1.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Set up Python ${{ matrix.python-version }} (deadsnakes)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;if&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;endsWith(matrix.python-version, &amp;#39;-dev&amp;#39;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;python-version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${{ matrix.python-version }}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="travis-ci" class="relative group"&gt;Travis CI &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#travis-ci" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;I recommend moving to another CI.&lt;/p&gt;
&lt;p&gt;In the meantime, you can also add &lt;code&gt;3.11-dev&lt;/code&gt; to &lt;code&gt;.travis.yml&lt;/code&gt;, although at the time of
writing it&amp;rsquo;s pointing to 3.11.0a3 from 2021-12-08, which is better than nothing.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;python&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;python&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.7&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.8&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.9&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.10&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;3.11-dev&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Again, I recommend moving to another CI.&lt;/p&gt;
&lt;h3 id="other-cis" class="relative group"&gt;Other CIs &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#other-cis" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Do you use other CIs? Please leave a comment if you know how to test 3.11!&lt;/p&gt;
&lt;h2 id="when-to-support-311" class="relative group"&gt;When to support 3.11? &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#when-to-support-311" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;When should you declare support and add the &lt;code&gt;Programming Language :: Python :: 3.11&lt;/code&gt;
&lt;a href="https://pypi.org/classifiers/"&gt;Trove classifier&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;First of all, make sure your tests pass on 3.11 beta. One option is waiting until 3.11.0
final is released.&lt;/p&gt;
&lt;p&gt;Or, as mentioned above:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Our goal is have no ABI changes after beta 4 and as few code changes as possible after
3.11.0rc1, the first release candidate.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you have a pure Python project, you could release now.&lt;/p&gt;
&lt;p&gt;If you have C extensions, you might want to wait until the release candidate phase,
although if other projects depend on yours, a preview release would help them test and
prepare.&lt;/p&gt;
&lt;p&gt;In any case, start testing 3.11 now!&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&lt;a href="https://en.wikipedia.org/wiki/Uppland_Runic_Inscription_53"&gt;Uppland Runic Inscription 53&lt;/a&gt;,
a 1,000 year old runestone in the old town of Stockholm
(&lt;a href="https://www.flickr.com/photos/hugovk/3490246425/"&gt;source&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Top PyPI Packages</title><link>https://hugovk.dev/blog/2022/top-pypi-packages/</link><pubDate>Sun, 29 May 2022 15:53:36 +0000</pubDate><guid>https://hugovk.dev/blog/2022/top-pypi-packages/</guid><description>&lt;p&gt;&lt;a href="https://hugovk.github.io/top-pypi-packages/"&gt;Top PyPI Packages&lt;/a&gt; is a website that
creates a monthly dump of the 5,000 most-downloaded packages from the
&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;. It provides a human-readable list and
a machine-readable JSON file for programmatic use.&lt;/p&gt;
&lt;h2 id="how-its-used" class="relative group"&gt;How it&amp;rsquo;s used &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-its-used" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The generated data is important for the Python community: it has been cited by many
academic papers, covering research on topics such as software supply-chain attacks,
genetic algorithms, neural type hints and technical debt.&lt;/p&gt;
&lt;p&gt;Websites also use the data for analysing things like community adoption of Python 3 and
of wheels package files, and for automated dependency updates.&lt;/p&gt;
&lt;p&gt;Some are &lt;a href="https://github.com/hugovk/top-pypi-packages/issues/23"&gt;listed here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Last but not least, CPython core developers often use the data for community analysis,
for example to check how widespread certain language features are when considering
deprecations and removals, or API changes of Python itself.&lt;/p&gt;
&lt;p&gt;Some examples:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://mail.python.org/archives/search?q=top-pypi-packages"&gt;https://mail.python.org/archives/search?q=top-pypi-packages&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="how-it-works" class="relative group"&gt;How it works &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how-it-works" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;It runs on the cheapest DigitalOcean Droplet to query a Google BigQuery dataset from the
Python Software Foundation, and builds the JSON file from that. The data is
automatically committed from the droplet back to the GitHub repository, and GitHub
Actions is used to automatically tag and create a release, which then creates a Digital
Object Identifier (DOI) at Zenodo to help make it citable for researchers.&lt;/p&gt;
&lt;h3 id="in-more-detail" class="relative group"&gt;In more detail &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#in-more-detail" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;A &amp;ldquo;Droplet&amp;rdquo; is the name DigitalOcean uses for virtual machines, which is basically a
Linux server. I&amp;rsquo;m using the cheapest $5/month running Ubuntu 20.04 (LTS) plus $1/month
for automated backups. (In July 2022, I&amp;rsquo;ll switch to the new
&lt;a href="https://www.digitalocean.com/try/new-pricing"&gt;$4/month Droplet&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;On the first of the month, it runs a cron job:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-crontab" data-lang="crontab"&gt;13 11 1 * * ( eval &amp;#34;$(ssh-agent -s)&amp;#34;; ssh-add ~/.ssh/id_rsa-top-pypi-packages; /home/botuser/github/top-pypi-packages/top-pypi-packages.sh ) &amp;gt; /tmp/top-pypi-packages.log 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This
&lt;a href="https://github.com/hugovk/top-pypi-packages/blob/c8101970cbfde3aee2eb133ae0d2aa8eb8846a58/top-pypi-packages.sh"&gt;script&lt;/a&gt;
runs
&lt;a href="https://github.com/hugovk/top-pypi-packages/blob/c8101970cbfde3aee2eb133ae0d2aa8eb8846a58/build.sh"&gt;some&lt;/a&gt;
&lt;a href="https://github.com/hugovk/top-pypi-packages/blob/c8101970cbfde3aee2eb133ae0d2aa8eb8846a58/generate.sh"&gt;others&lt;/a&gt;
to call &lt;a href="https://github.com/ofek/pypinfo/"&gt;pypinfo&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/home/botuser/.local/bin/pypinfo --json --indent &lt;span class="m"&gt;0&lt;/span&gt; --limit &lt;span class="m"&gt;5000&lt;/span&gt; --days &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt; project &amp;gt; top-pypi-packages-30-days.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;a href="https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/"&gt;PyPI streams data about downloads to Google BigQuery&lt;/a&gt;
which are accessible as a public dataset. Google provides a free amount of queries per
month, and I&amp;rsquo;ve been adjusting the amount of data fetched to stay within the free quota
(e.g. changing from the top 5k packages to 4k; it used to get data for top packages over
the past 30 days and 365 days, but now only for 30 days; and bumping back up to 5k
packages).&lt;/p&gt;
&lt;p&gt;pypinfo is a handy command-line interface (CLI) to access this BigQuery data and dump it
to a JSON file.&lt;/p&gt;
&lt;p&gt;Another handy CLI tool called &lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt; minifies the JSON
data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;jq -c . &amp;lt; top-pypi-packages-30-days.json &amp;gt; top-pypi-packages-30-days.min.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;These are then committed back to the repo, tagged using &lt;a href="https://calver.org/"&gt;CalVer&lt;/a&gt;
(e.g. &lt;a href="https://github.com/hugovk/top-pypi-packages/releases/tag/2022.05"&gt;2022-05&lt;/a&gt;) and
pushed.
&lt;a href="https://github.com/hugovk/top-pypi-packages/blob/c8101970cbfde3aee2eb133ae0d2aa8eb8846a58/.github/workflows/release.yml"&gt;GitHub Actions&lt;/a&gt;
creates &lt;a href="https://github.com/hugovk/top-pypi-packages/releases"&gt;a release&lt;/a&gt; from the tag.&lt;/p&gt;
&lt;p&gt;This then creates a
&lt;a href="https://zenodo.org/badge/latestdoi/116806538"&gt;Digital Object Identifier (DOI) at Zenodo&lt;/a&gt;
to help make it citable for researchers.&lt;/p&gt;
&lt;p&gt;The &lt;a href="https://hugovk.github.io/top-pypi-packages/"&gt;website&lt;/a&gt;, on GitHub Pages, reads in
the generated JSON file and shows the top 100 (or 1,000 or 5,000) packages in
human-readable form. It&amp;rsquo;s based on &lt;a href="https://pythonwheels.com"&gt;Python Wheels&lt;/a&gt;, which in
nice circular fashion, uses the JSON data from this project.&lt;/p&gt;
&lt;h2 id="thanks" class="relative group"&gt;Thanks &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#thanks" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Thanks to &lt;a href="https://pypi.org/"&gt;PyPI&lt;/a&gt; and
&lt;a href="https://cloud.google.com/bigquery/"&gt;Google BigQuery&lt;/a&gt; for the data;
&lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; and &lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt; for
the tools; &lt;a href="https://pythonwheels.com/"&gt;Python Wheels&lt;/a&gt; for making their code open source;
and &lt;a href="https://m.do.co/c/431978e0c3e9"&gt;DigitalOcean&lt;/a&gt; for sponsoring this project&amp;rsquo;s
hosting. Visit &lt;a href="https://do.co/oss-sponsorship"&gt;https://do.co/oss-sponsorship&lt;/a&gt; to see if your project is eligible.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/49889874@N05/4772680734"&gt;PACKAGES&lt;/a&gt;&amp;rdquo;
by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/49889874@N05"&gt;marc
falardeau&lt;/a&gt; is licensed under
&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC
BY 2.0&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>The Python 3.1 problem</title><link>https://hugovk.dev/blog/2021/the-python-3-1-problem/</link><pubDate>Wed, 19 May 2021 14:08:31 +0000</pubDate><guid>https://hugovk.dev/blog/2021/the-python-3-1-problem/</guid><description>&lt;h2 id="or-a-variation-on-the-norway-problem" class="relative group"&gt;Or, a variation on the Norway problem &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#or-a-variation-on-the-norway-problem" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Short version: put quotes around version numbers in YAML.&lt;/p&gt;
&lt;h3 id="the-norway-problem" class="relative group"&gt;The Norway problem &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-norway-problem" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;The &lt;a href="https://hitchdev.com/strictyaml/why/implicit-typing-removed/"&gt;Norway problem&lt;/a&gt; is
when you put this in YAML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;GB&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;IE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FR&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="kc"&gt;NO&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But get this out:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;countries.yml&amp;#34;&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countries&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GB&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;IE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;FR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;:scream:&lt;/p&gt;
&lt;h3 id="the-norway-fix" class="relative group"&gt;The Norway fix &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-norway-fix" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Use quotes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;countries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;GB&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;IE&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;FR&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;DE&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="s2"&gt;&amp;#34;NO&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;countries.yml&amp;#34;&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;countries&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;GB&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;IE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;FR&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;DE&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;NO&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🇳🇴 ✅&lt;/p&gt;
&lt;h3 id="the-python-31-problem" class="relative group"&gt;The Python 3.1 problem &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-python-31-problem" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;A similar problem will affect the Python community in October 2021, when
&lt;a href="https://peps.python.org/pep-0619/"&gt;Python 3.10 comes out&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;When &lt;code&gt;3.10&lt;/code&gt; is added to YAML, for example in CI test matrix config, it&amp;rsquo;s interpreted as
a float. This:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;python-version&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="m"&gt;3.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3.10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pypy3]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Turns into this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;versions.yml&amp;#34;&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python-version&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;3.6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;3.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pypy3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;CI failed! It&amp;rsquo;s not &lt;a href="https://peps.python.org/pep-0375/"&gt;2009&lt;/a&gt;! Python 3.1 not found!&lt;/p&gt;
&lt;p&gt;:scream:&lt;/p&gt;
&lt;p&gt;Relatedly, &lt;code&gt;3.10-dev&lt;/code&gt; without quotes works because it&amp;rsquo;s interpreted as a string. But
when deleting &lt;code&gt;-dev&lt;/code&gt;, &lt;code&gt;3.10&lt;/code&gt; is interpreted as a float.&lt;/p&gt;
&lt;h3 id="the-python-310-fix" class="relative group"&gt;The Python 3.10 fix &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#the-python-310-fix" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Version numbers are strings, not floats. Use quotes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;python-version&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="s2"&gt;&amp;#34;3.6&amp;#34;&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;#34;3.7&amp;#34;&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;#34;3.8&amp;#34;&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;#34;3.9&amp;#34;&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;#34;3.10&amp;#34;&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;#34;pypy3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;yaml&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;versions.yml&amp;#34;&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;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="n"&gt;yaml&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;safe_load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;python-version&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;3.6&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;3.7&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;3.8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;3.9&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;3.10&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pypy3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🐍 ✅&lt;/p&gt;
&lt;h3 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;&lt;a href="https://github.com/asottile/flake8-2020/"&gt;flake8-2020&lt;/a&gt; is a useful Flake8 plugin to
find Python 3.10 and other bugs caused by assumptions about the length of version
numbers when using &lt;code&gt;sys.version&lt;/code&gt; and &lt;code&gt;sys.version_info&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;small&gt;Header photo:
&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/49968232@N00/2115403318"&gt;zero&lt;/a&gt;&amp;rdquo;
by
&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/49968232@N00"&gt;Leo
Reynolds&lt;/a&gt; is licensed under
&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/?ref=openverse"&gt;CC
BY-NC-SA 2.0&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;</description></item><item><title>Python version share over time, 6</title><link>https://hugovk.dev/blog/2020/python-version-share-over-time-6/</link><pubDate>Wed, 01 Jan 2020 16:36:25 +0000</pubDate><guid>https://hugovk.dev/blog/2020/python-version-share-over-time-6/</guid><description>&lt;h2 id="january-2016--december-2019" class="relative group"&gt;January 2016 — December 2019 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#january-2016--december-2019" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To celebrate the end of life of &lt;a href="https://www.python.org/doc/sunset-python-2/"&gt;Python 2&lt;/a&gt;
on &lt;a href="https://peps.python.org/pep-0373/"&gt;1st January 2020&lt;/a&gt;, here’s some statistics showing
how much different Python versions have been used over four years.&lt;/p&gt;
&lt;p&gt;Here’s the pip installs for all packages from the
&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and December
2019:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_80678aee9a0b5827.webp 330w,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_2f52dd05bf3e32b8.webp 660w
 
 ,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_d7077bd84dd1430d.webp 1024w
 
 
 ,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_615e62dfa4e0a45c.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="all"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_c6615ad1117357bb.png" srcset="https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_2c98760a1172e81d.png 330w,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_c6615ad1117357bb.png 660w
 
 ,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_a6dc1e9f1d61e5e.png 1024w
 
 
 ,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_ecb1f03ebdc07096.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pip" class="relative group"&gt;&lt;a href="https://github.com/pypa/pip"&gt;pip&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pip" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;The package installer&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643625-af087280-2cc4-11ea-9997-10aae072ac1d.png" alt="pip" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="six" class="relative group"&gt;&lt;a href="https://github.com/benjaminp/six"&gt;six&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#six" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Python 2 and 3 compatibility library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643630-b62f8080-2cc4-11ea-9461-1204f4dd117e.png" alt="six" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="numpy" class="relative group"&gt;&lt;a href="https://github.com/numpy/numpy"&gt;NumPy&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#numpy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Scientific computing library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643635-bb8ccb00-2cc4-11ea-9c4f-7095b15210f6.png" alt="numpy" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pytest" class="relative group"&gt;&lt;a href="https://github.com/pytest-dev/pytest"&gt;pytest&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pytest" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Testing framework&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643639-c0ea1580-2cc4-11ea-9aef-2a0d9cf816ae.png" alt="pytest" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pandas" class="relative group"&gt;&lt;a href="https://github.com/pandas-dev/pandas"&gt;pandas&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pandas" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Data analysis toolkit&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643641-c8a9ba00-2cc4-11ea-8f58-9c244ff958aa.png" alt="pandas" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="coveragepy" class="relative group"&gt;&lt;a href="https://github.com/nedbat/coveragepy"&gt;Coverage.py&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#coveragepy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Code coverage testing&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643645-ce070480-2cc4-11ea-85b5-e408ea682e31.png" alt="coverage" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pillow" class="relative group"&gt;&lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pillow" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Imaging library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643648-d3644f00-2cc4-11ea-9b3e-7271788d1f5d.png" alt="pillow" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="django" class="relative group"&gt;&lt;a href="https://github.com/python-pillow/Pillow"&gt;Django&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#django" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Web framework&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643651-d95a3000-2cc4-11ea-891e-a218090c5db9.png" alt="django" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="matplotlib" class="relative group"&gt;&lt;a href="https://github.com/matplotlib/matplotlib"&gt;Matplotlib&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#matplotlib" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;2D plotting library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643656-e6771f00-2cc4-11ea-808a-464ee01f88b5.png" alt="matplotlib" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="flake8" class="relative group"&gt;&lt;a href="https://gitlab.com/pycqa/flake8"&gt;Flake8&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#flake8" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Linter&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643661-ec6d0000-2cc4-11ea-9ef8-f5da11826684.png" alt="flake8" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pylint" class="relative group"&gt;&lt;a href="https://github.com/PyCQA/pylint/"&gt;Pylint&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pylint" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Linter&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643663-f42ca480-2cc4-11ea-9588-7290df0fa003.png" alt="pylint" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="tensorflow" class="relative group"&gt;&lt;a href="https://github.com/tensorflow/tensorflow/"&gt;TensorFlow&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#tensorflow" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Machine learning library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71645996-1506f100-2ce9-11ea-80d8-43b499348a54.png" alt="tensorflow" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pylast" class="relative group"&gt;&lt;a href="https://github.com/pylast/pylast"&gt;pylast&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pylast" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Interface to Last.fm&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/71643665-f989ef00-2cc4-11ea-921e-1d705524151c.png" alt="pylast" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Statistics were collected using
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a
wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; and
&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt; to fetch all monthly downloads from the
PyPI database on Google BigQuery and save them as JSON files. Data was downloaded over
several days as getting all months uses up a lot of free BigQuery quota. Then
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots a
chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is in
the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://langui.sh/2016/12/09/data-driven-decisions/"&gt;Data Driven Decisions Using PyPI Download Statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,
1&lt;/a&gt; (January
2016 — June 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-2/"&gt;Python version share over time,
2&lt;/a&gt; (January
2016 — October 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-3/"&gt;Python version share over time, 3&lt;/a&gt;
(January 2016 — December 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-4/"&gt;Python version share over time, 4&lt;/a&gt;
(January 2016 — March 2019)&lt;/li&gt;
&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-5/"&gt;Python version share over time, 5&lt;/a&gt;
(January 2016 — October 2019)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,
without needing to sign up for BigQuery&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data
from PyPI Stats&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Python version share over time, 5</title><link>https://hugovk.dev/blog/2019/python-version-share-over-time-5/</link><pubDate>Mon, 04 Nov 2019 14:40:48 +0000</pubDate><guid>https://hugovk.dev/blog/2019/python-version-share-over-time-5/</guid><description>&lt;h2 id="january-2016--october-2019" class="relative group"&gt;January 2016 — October 2019 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#january-2016--october-2019" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To celebrate the release of
&lt;a href="https://discuss.python.org/t/python-3-8-0-is-now-available/2478?u=hugovk"&gt;Python 3.8.0&lt;/a&gt;
on &lt;a href="https://peps.python.org/pep-0569/"&gt;14th October 2019&lt;/a&gt;, and with
&lt;a href="https://python2woop.pw/"&gt;less than two months left for Python 2&lt;/a&gt;, here’s some
statistics showing how much different Python versions have been used over nearly four
years.&lt;/p&gt;
&lt;p&gt;Here’s the pip installs for all packages from the
&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and October 2019:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_19f6c73658d3ba05.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_7e64026d4ccdd715.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_a3ded4c1e5d347be.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_8a6822638a378a2a.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pypi"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_10697589168269ba.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_bd020fb7c0b8d435.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_10697589168269ba.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_11a21e899c6dab30.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_a7dacaa6e985ac37.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pip" class="relative group"&gt;&lt;a href="https://github.com/pypa/pip"&gt;pip&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pip" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;The package installer&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128596-ab54ab80-ff20-11e9-96fa-867b403efc89.png" alt="pip" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="six" class="relative group"&gt;&lt;a href="https://github.com/benjaminp/six"&gt;six&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#six" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Python 2 and 3 compatibility library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128614-b60f4080-ff20-11e9-92e3-892f29ecceea.png" alt="six" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="numpy" class="relative group"&gt;&lt;a href="https://github.com/numpy/numpy"&gt;NumPy&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#numpy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Scientific computing library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128663-d0491e80-ff20-11e9-9442-0546e1a9843c.png" alt="numpy" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pytest" class="relative group"&gt;&lt;a href="https://github.com/pytest-dev/pytest"&gt;pytest&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pytest" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Testing framework&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128691-e0f99480-ff20-11e9-9bd8-87e12318d149.png" alt="pytest" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pandas" class="relative group"&gt;&lt;a href="https://github.com/pandas-dev/pandas"&gt;pandas&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pandas" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Data analysis toolkit&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68142151-9cc5be80-ff37-11e9-93d9-72edd2e35e6b.png" alt="pandas" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="coveragepy" class="relative group"&gt;&lt;a href="https://github.com/nedbat/coveragepy"&gt;Coverage.py&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#coveragepy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Code coverage testing&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128715-ea82fc80-ff20-11e9-80a9-515c4b5265c2.png" alt="coverage" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pillow" class="relative group"&gt;&lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pillow" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Imaging library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128908-49e10c80-ff21-11e9-977e-f25dfa13b2d4.png" alt="pillow" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="django" class="relative group"&gt;&lt;a href="https://github.com/python-pillow/Pillow"&gt;Django&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#django" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Web framework&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128927-52394780-ff21-11e9-80d6-1dadf2525ebc.png" alt="django" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="matplotlib" class="relative group"&gt;&lt;a href="https://github.com/matplotlib/matplotlib"&gt;Matplotlib&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#matplotlib" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;2D plotting library&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128947-5b2a1900-ff21-11e9-9386-d366a030fe7c.png" alt="matplotlib" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="flake8" class="relative group"&gt;&lt;a href="https://gitlab.com/pycqa/flake8"&gt;Flake8&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#flake8" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Linter&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128961-62e9bd80-ff21-11e9-8a45-001c7ae06c1d.png" alt="flake8" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pylint" class="relative group"&gt;&lt;a href="https://github.com/PyCQA/pylint/"&gt;Pylint&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pylint" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Linter&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128981-6bda8f00-ff21-11e9-9a97-903892a47823.png" alt="pylint" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pylast" class="relative group"&gt;&lt;a href="https://github.com/pylast/pylast"&gt;pylast&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pylast" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Interface to Last.fm&lt;/p&gt;
&lt;p&gt;






 
 
&lt;figure&gt;&lt;img src="https://user-images.githubusercontent.com/1324225/68128999-77c65100-ff21-11e9-9ac0-7f9c6071b07a.png" alt="pylast" class="mx-auto my-0 rounded-md" /&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Statistics were collected using
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a
wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; and
&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt; to fetch all monthly downloads from the
PyPI database on Google BigQuery and save them as JSON files. Data was downloaded over
several days as getting all months uses up a lot of free BigQuery quota. Then
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots a
chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is in
the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://langui.sh/2016/12/09/data-driven-decisions/"&gt;Data Driven Decisions Using PyPI Download Statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,
1&lt;/a&gt; (January
2016 — June 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-2/"&gt;Python version share over time,
2&lt;/a&gt; (January
2016 — October 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-3/"&gt;Python version share over time, 3&lt;/a&gt;
(January 2016 — December 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-4/"&gt;Python version share over time, 4&lt;/a&gt;
(January 2016 — March 2019)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,
without needing to sign up for BigQuery&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data
from PyPI Stats&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Python version share over time, 4</title><link>https://hugovk.dev/blog/2019/python-version-share-over-time-4/</link><pubDate>Thu, 04 Apr 2019 14:58:18 +0000</pubDate><guid>https://hugovk.dev/blog/2019/python-version-share-over-time-4/</guid><description>&lt;h2 id="january-2016--march-2019" class="relative group"&gt;January 2016 — March 2019 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#january-2016--march-2019" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To celebrate the release of
&lt;a href="https://www.python.org/downloads/release/python-373/"&gt;Python 3.7.3&lt;/a&gt; on
&lt;a href="https://peps.python.org/pep-0537/"&gt;25th March 2019&lt;/a&gt;, and with under
&lt;a href="https://python2woop.pw/"&gt;nine months left for Python 2&lt;/a&gt;, here’s some statistics showing
how much different Python versions have been used over the past three years.&lt;/p&gt;
&lt;p&gt;Here’s the pip installs for all packages from the
&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and March 2019:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_5824262dc13b9039.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_25b8586b6c4f5d.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_93b42a2cf8454afb.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_bff5e2cae1fa6088.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pypi"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_ff3438d4632687d6.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_40cd28cd643a4f18.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_ff3438d4632687d6.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_915c5274993cbd19.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_4dc1a6155a879aff.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pip" class="relative group"&gt;&lt;a href="https://github.com/pypa/pip"&gt;pip&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pip" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;The package installer&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_b2cca0669282be0d.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_45b7efe475f26dbf.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_54bcb3a15c983066.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_c00555a486290249.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pip"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_c0b9a523223d0a44.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_1c4a68db3687731f.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_c0b9a523223d0a44.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_c005ffab43ee357c.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_5645317f5a162b70.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="six" class="relative group"&gt;&lt;a href="https://github.com/benjaminp/six"&gt;six&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#six" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Python 2 and 3 compatibility library&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_6d41f0d99db19563.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_e7df4ed742ae6490.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_cd1da94454a76852.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_8fc4e74c7c65c17f.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="six"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_83224d28bd3576a0.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_e418c2ddc17554e5.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_83224d28bd3576a0.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_58f5123a376ee557.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_36938c059f3de55d.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="numpy" class="relative group"&gt;&lt;a href="https://github.com/numpy/numpy"&gt;NumPy&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#numpy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Scientific computing library&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_c1750d5159d1addb.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_ca2bea01b9d7656d.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_20bca4707adfaa29.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_21a9de1ce1830c9b.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="NumPy"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_ffaad05c81a4d6f.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_5e4bddf7ecf92885.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_ffaad05c81a4d6f.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_bab126983d155b4.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_7bffe0984b470a4b.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pytest" class="relative group"&gt;&lt;a href="https://github.com/pytest-dev/pytest"&gt;pytest&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pytest" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Testing framework&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_929b5192a5acd4a2.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_90866faf95c649c4.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_1fed580acd727d27.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_743d19ab54d5a61e.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pytest"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_4287a4140c778d6a.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_3b01cd9a2ca44bf0.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_4287a4140c778d6a.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_d2ef5f5bfc31fe17.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_4cf9d32ed16b2f8c.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="coveragepy" class="relative group"&gt;&lt;a href="https://github.com/nedbat/coveragepy"&gt;Coverage.py&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#coveragepy" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Code coverage testing&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_6a2e3df6594ce7d7.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_72463128036ea961.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_45bb75e5185cdadc.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_4922bc9542c7b69f.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="coverage"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_25e5247769ec62e7.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_c22ccf5477012a5f.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_25e5247769ec62e7.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_7888bf93fa72fdb4.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_b28b1fd83c6cf354.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pillow" class="relative group"&gt;&lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pillow" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Imaging library&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_86a99b8ac96b7a50.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_1d81cd5ede1f17a9.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_701b9640f27a9e10.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_f8676fb04952ed49.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pillow"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_7781b57ec78c180d.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_fcd5fd37c2a76bb4.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_7781b57ec78c180d.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_8364ab1bb6bc925c.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_bab3111c3cfeb4f0.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="django" class="relative group"&gt;&lt;a href="https://github.com/python-pillow/Pillow"&gt;Django&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#django" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Web framework&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_711b22c7992fc742.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_bc58527ac15ff917.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_ae3b193c3e32f183.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_18b04cca1fbcaede.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="django"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_4a9889ab98d325a3.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_388fecf7d9c113a.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_4a9889ab98d325a3.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_3fe2e707c159b46a.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_fc48b09a0a300846.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="matplotlib" class="relative group"&gt;&lt;a href="https://github.com/matplotlib/matplotlib"&gt;Matplotlib&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#matplotlib" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;2D plotting library&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_b31dd045646e720e.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_a877ee678ccfdf64.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_a2a2d74bb68e617.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_baf9e8a27f3ccff8.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="matplotlib"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_df29721da689f6e2.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_9b8fe75e35501a88.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_df29721da689f6e2.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_10cb82b2ea564070.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_e8763cc54de70d1d.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="flake8" class="relative group"&gt;&lt;a href="https://gitlab.com/pycqa/flake8"&gt;Flake8&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#flake8" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Linter&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_5e6e46ed2e45edae.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_34b8eb29a62db514.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_7f7efeee8eacc5b7.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_b376839d06ac005e.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="flake8"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_170528d4ea9a78f.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_52dc063197464839.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_170528d4ea9a78f.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_c9477dbded216dfe.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_d2c32cf39471cfc9.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pylint" class="relative group"&gt;&lt;a href="https://github.com/PyCQA/pylint/"&gt;Pylint&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pylint" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Linter&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_ec7c6bff6a189d44.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_448ad8b7c7d25839.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_2515fea6b95310db.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_5605662e8972b3d2.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pylint"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_718065fe3b640d21.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_b16185630e11d995.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_718065fe3b640d21.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_578383a04751bc50.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_c2b0f54a3152dbd4.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id="pylast" class="relative group"&gt;&lt;a href="https://github.com/pylast/pylast"&gt;pylast&lt;/a&gt; &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#pylast" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;Interface to Last.fm&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_a5a3029e8a3a1edf.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_2161ffb146517792.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_e266d4541a8daa81.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_29987d98ea89c351.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pylast"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_2bcc59d45f65c8e.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_135759a2ca13a1aa.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_2bcc59d45f65c8e.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_1e1ebb8d8e089656.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_7c4b7ac32166644.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Statistics were collected using
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a
wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; to fetch all monthly downloads
from the PyPI database on Google BigQuery and save them as JSON files. Data was
downloaded over several days as getting all months uses up a lot of free BigQuery quota.
Then &lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots
a chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is
in the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://langui.sh/2016/12/09/data-driven-decisions/"&gt;Data Driven Decisions Using PyPI Download Statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,
1&lt;/a&gt; (January
2016 — June 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-2/"&gt;Python version share over time,
2&lt;/a&gt; (January
2016 — October 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-3/"&gt;Python version share over time, 3&lt;/a&gt;
(January 2016 — December 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,
without needing to sign up for BigQuery&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data
from PyPI Stats&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Python version share over time, 3</title><link>https://hugovk.dev/blog/2019/python-version-share-over-time-3/</link><pubDate>Tue, 01 Jan 2019 14:40:48 +0000</pubDate><guid>https://hugovk.dev/blog/2019/python-version-share-over-time-3/</guid><description>&lt;h2 id="january-2016--december-2018" class="relative group"&gt;January 2016 — December 2018 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#january-2016--december-2018" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To celebrate the release of
&lt;a href="https://www.python.org/downloads/release/python-372/"&gt;Python 3.7.2&lt;/a&gt; on
&lt;a href="https://peps.python.org/pep-0537/"&gt;Christmas Eve 2018&lt;/a&gt;, and with
u&lt;a href="https://hugovk.github.io/python2-progress-bar/"&gt;nder a year left for Python 2&lt;/a&gt;, here&amp;rsquo;s
some statistics showing how much different Python versions have been used over the past
three years.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the pip installs for all packages from the
&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and December
2018:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_8d45c971c554d5e0.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_87583ac1dd65f269.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_d1f8a7090ff16763.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_15082e628ac86f8b.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pypi"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_6e37e3955c86c3a5.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_f91c6a9130b72d6f.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_6e37e3955c86c3a5.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_9f0cd80934ca8368.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_132f9c73735d06cf.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://github.com/numpy/numpy"&gt;NumPy&lt;/a&gt; scientific computing library:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_c74213f506c99cb8.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_db6c684dd9e2c8a2.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_ffa4d215fdbf1213.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_a0946d70a8ed20d1.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="NumPy"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_540a89a92cf912d2.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_18605c97cbc0310b.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_540a89a92cf912d2.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_67deb915c4a7ec44.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_95f0fbde6f5c051c.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://github.com/pytest-dev/pytest"&gt;pytest&lt;/a&gt; testing framework:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_249fc31f23dabcaa.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_14e6347f0d7f119f.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_20cea6928172c311.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_6e3d279c1398a129.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pytest"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_497aff62586fa2d3.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_6dc54a07db932b08.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_497aff62586fa2d3.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_15f1fdd043bad908.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_3382a792c337845c.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt; imaging library:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_96f2a6ec2eb8721f.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_3307e25f824688e6.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_f2bf3af5060e46e3.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_1a9e6fd6ea8ef938.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pillow"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_cfe889869523b24d.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_634cefc619e792c5.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_cfe889869523b24d.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_7ca825b96cbeffd.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_e5fde998471ca438.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://github.com/django/django"&gt;Django&lt;/a&gt; web framework:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_ba42f994f2c8e287.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_6f6672ad85a3e760.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_66231d11724c2b1f.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_5be1cab6ce9a22d0.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="django"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_3972c4e9f2ed5fee.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_a34225e899179813.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_3972c4e9f2ed5fee.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_60178940828ebc4b.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_3e9095f56f719ddd.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt; 2D plotting library:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_bba7be4e4a951527.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_99b06ff1a278c7b2.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_c3b25dfdff89bdc4.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_6a298d4ab3c99953.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="matplotlib"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_a8a1f920a46dea42.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_2f2ef47af96a83cb.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_a8a1f920a46dea42.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_3042fad692d468a6.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_97bcbf6365b31263.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;For the &lt;a href="https://github.com/PyCQA/pylint"&gt;Pylint&lt;/a&gt; linter:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_3ee5b4fd96951f93.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_d7eff2e0fa22707e.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_4118aaeab1ce4ac8.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_ddbc510793fc30fb.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pylint"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_3ee95098e90da8a1.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_447458655f3bd8bd.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_3ee95098e90da8a1.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_c1aa841f95201908.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_f12f9e61a91bc675.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;And for the &lt;a href="https://github.com/pylast/pylast"&gt;pylast&lt;/a&gt; interface to Last.fm:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_a8aacdca4fb508d9.webp 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_8aba23ddc760a551.webp 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_20bd13b334f6f66e.webp 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_98038669afce3132.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pylast"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_58a04b6afaeddbda.png" srcset="https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_4a67acf5a1d02e4f.png 330w,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_58a04b6afaeddbda.png 660w
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_b2e391430b907729.png 1024w
 
 
 ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_7042c596e1cb51f1.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Statistics were collected using
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt; a
wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; to fetch all monthly downloads
from the PyPI database on Google BigQuery and save them as JSON files. Data was
downloaded over several days as getting all months uses up a lot of free BigQuery quota.
Then &lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots
a chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is
in the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://langui.sh/2016/12/09/data-driven-decisions/"&gt;Data Driven Decisions Using PyPI Download Statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,
1&lt;/a&gt; (January
2016 — June 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-2/"&gt;Python version share over time,
2&lt;/a&gt; (January
2016 — October 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,
without needing to sign up for BigQuery&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data
from PyPI Stats&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Words of the year from Twitter, 2018</title><link>https://hugovk.dev/blog/2018/twitter-woty-2018/</link><pubDate>Mon, 31 Dec 2018 14:03:57 +0000</pubDate><guid>https://hugovk.dev/blog/2018/twitter-woty-2018/</guid><description>&lt;h2&gt;Three bots have been collecting words from Twitter for the past year.&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Short version:&lt;/p&gt;
&lt;p&gt;In 2018, people on Twitter talked about the words stan, toxic, peoplekind, councel, beclowned, caucasity, kakistocracy, catastrofuck, shithole, bombogenesis, chucklefuck, trumpfuckery, clackwanker, wankpuffin, dipshittery, fucksicle, and fuckwangled.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Long version:&lt;/p&gt;
&lt;p&gt;They've looked for certain sentences and extracted the X.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt; -- "I love/hate the word X"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt; -- "X is not/isn't/ain't a word"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt; -- "X is my new favorite/favouriteword"&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Combining and comparing the logs of all three, let's see the top words of 2018. &lt;strong&gt;Bold&lt;/strong&gt; means a word wasn't in that chart in 2017.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Combined output&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Total in 2018: 81,695&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;justice (980)&lt;/li&gt;
&lt;li&gt;irregardless (876)&lt;/li&gt;
&lt;li&gt;soon (696)&lt;/li&gt;
&lt;li&gt;no (684)&lt;/li&gt;
&lt;li&gt;love (636)&lt;/li&gt;
&lt;li&gt;mines (571)&lt;/li&gt;
&lt;li&gt;homophobia (506)&lt;/li&gt;
&lt;li&gt;moist (491)&lt;/li&gt;
&lt;li&gt;bae (434)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;clout (365)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (364)&lt;/li&gt;
&lt;li&gt;alot (354)&lt;/li&gt;
&lt;li&gt;nut (333)&lt;/li&gt;
&lt;li&gt;ain't (329)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cock (324)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;forever (305)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hurted (291)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;yolo (280)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sexy (271)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;impossible (269)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;lovihatibot&lt;/th&gt;
&lt;th&gt;nixibot&lt;/th&gt;
&lt;th&gt;favibot&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2018 total: 41,092&lt;/td&gt;
&lt;td&gt;2018 total: 33,078&lt;/td&gt;
&lt;td&gt;2018 total: 7,525&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;soon (688)&lt;/li&gt;
&lt;li&gt;no (528)&lt;/li&gt;
&lt;li&gt;homophobia (494)&lt;/li&gt;
&lt;li&gt;moist (483)&lt;/li&gt;
&lt;li&gt;bae (420)&lt;/li&gt;
&lt;li&gt;clout (356)&lt;/li&gt;
&lt;li&gt;nut (333)&lt;/li&gt;
&lt;li&gt;cunt (332)&lt;/li&gt;
&lt;li&gt;cock (319)&lt;/li&gt;
&lt;li&gt;yolo (277)&lt;/li&gt;
&lt;li&gt;sexy (240)&lt;/li&gt;
&lt;li&gt;bitch (227)&lt;/li&gt;
&lt;li&gt;sorry (210)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;queer (205)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stan (197)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;coochie (190)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuck (186)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fate (182)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;diet (167)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;toxic (159)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;justice (975)&lt;/li&gt;
&lt;li&gt;irregardless (869)&lt;/li&gt;
&lt;li&gt;mines (565)&lt;/li&gt;
&lt;li&gt;love (501)&lt;/li&gt;
&lt;li&gt;alot (352)&lt;/li&gt;
&lt;li&gt;ain't (324)&lt;/li&gt;
&lt;li&gt;hurted (289)&lt;/li&gt;
&lt;li&gt;forever (259)&lt;/li&gt;
&lt;li&gt;impossible (249)&lt;/li&gt;
&lt;li&gt;conversate (230)&lt;/li&gt;
&lt;li&gt;loyalty (224)&lt;/li&gt;
&lt;li&gt;anyways (209)&lt;/li&gt;
&lt;li&gt;finna (154)&lt;/li&gt;
&lt;li&gt;stupider (153)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;t (149)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;usa (125)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;marriage (111)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;peoplekind (105)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;councel (105)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;funner (104)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;no (68)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;beclowned (59)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;caucasity (35)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;whore (32)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kakistocracy (30)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;feckless (25)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cockwomble (24)&lt;/li&gt;
&lt;li&gt;yikes (22)&lt;/li&gt;
&lt;li&gt;bitch (20)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shithole (19)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;catastrofuck (17)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kissy (16)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bombogenesis (15)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;oof (15)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (15)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;chucklefuck (14)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;trumpfuckery (14)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wankpuffin (12)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dipshittery (11)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuck (11)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here's top 10 charts for each phrase from each bot.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;@lovihatbot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;I love the word X&lt;/th&gt;
&lt;th&gt;I hate the word X&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;cunt (245)&lt;/li&gt;
&lt;li&gt;fuck (173)&lt;/li&gt;
&lt;li&gt;fate (167)&lt;/li&gt;
&lt;li&gt;bitch (147)&lt;/li&gt;
&lt;li&gt;play (145)&lt;/li&gt;
&lt;li&gt;moist (99)&lt;/li&gt;
&lt;li&gt;choice (68)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;whore (55)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cock (47)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;queer (47)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;soon (648)&lt;/li&gt;
&lt;li&gt;homophobia (492)&lt;/li&gt;
&lt;li&gt;no (489)&lt;/li&gt;
&lt;li&gt;bae (417)&lt;/li&gt;
&lt;li&gt;moist (384)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;clout (351)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;nut (330)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;yolo (277)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cock (272)&lt;/li&gt;
&lt;li&gt;sexy (234)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@nixibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is not a word&lt;/th&gt;
&lt;th&gt;X isn't a word&lt;/th&gt;
&lt;th&gt;X ain't a word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;irregardless (738)&lt;/li&gt;
&lt;li&gt;mines (535)&lt;/li&gt;
&lt;li&gt;love (437)&lt;/li&gt;
&lt;li&gt;forever (259)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hurted (255)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;impossible (241)&lt;/li&gt;
&lt;li&gt;alot (241)&lt;/li&gt;
&lt;li&gt;conversate (196)&lt;/li&gt;
&lt;li&gt;loyalty (162)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;anyways (156)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;justice (964)&lt;/li&gt;
&lt;li&gt;irregardless (125)&lt;/li&gt;
&lt;li&gt;ain't (112)&lt;/li&gt;
&lt;li&gt;alot (109)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stupider (59)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;anyways (51)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;just (51)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;happeh (49)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;love (49)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;usa (46)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;ul&gt;
&lt;li&gt;ain't (150)&lt;/li&gt;
&lt;li&gt;loyalty (23)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;funk (19)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;love (15)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;unlonely (15)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;is (13)&lt;/li&gt;
&lt;li&gt;ignorantest (8)&lt;/li&gt;
&lt;li&gt;finna (6)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;irregardless (6)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;shit (5)&lt;/li&gt;
&lt;/ul&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@favibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is my new favorite word&lt;/th&gt;
&lt;th&gt;X is my new favourite word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;no (59)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;beclowned (54)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;caucasity (27)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kakistocracy (26)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;whore (25)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;feckless (21)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;yikes (20)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shithole (18)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cockwomble (15)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bombogenesis (13)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;catastrofuck (14)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cockwomble (8)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;caucasity (8)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kissy (8)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;clackwanker (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;no (7)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wankpuffin (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fucksicle (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuckwangled (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bitch (6)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Previous years&lt;/h2&gt;
&lt;p&gt;See the words from &lt;a href="../../2017/twitter-woty-2017/"&gt;2017&lt;/a&gt;, &lt;a href="../../2017/twitter-woty-2016/"&gt;2016&lt;/a&gt;,&amp;nbsp;&lt;a href="../../2016/twitter-woty-2015/"&gt;2015&lt;/a&gt;,&amp;nbsp;&lt;a href="../../2015/twitter-woty-2014/"&gt;2014&amp;nbsp;&lt;/a&gt;and&amp;nbsp;&lt;a href="../../2013/twitters-new-favourite-words/"&gt;2013&lt;/a&gt;.&lt;a href="../../2016/twitter-woty-2015/"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;2019&lt;/h2&gt;
&lt;p&gt;For words causing a reaction in 2019, follow &lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt;,&amp;nbsp;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt;&amp;nbsp;on Twitter.&lt;/p&gt;</description></item><item><title>Python version share over time, 2</title><link>https://hugovk.dev/blog/2018/python-version-share-over-time-2/</link><pubDate>Sat, 03 Nov 2018 14:40:48 +0000</pubDate><guid>https://hugovk.dev/blog/2018/python-version-share-over-time-2/</guid><description>&lt;h2 id="january-2016--october-2018" class="relative group"&gt;January 2016 — October 2018 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#january-2016--october-2018" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To celebrate the release of
&lt;a href="https://www.python.org/downloads/release/python-371/"&gt;Python 3.7.1&lt;/a&gt; on
&lt;a href="https://peps.python.org/pep-0537/"&gt;20th October 2018&lt;/a&gt;, here’s some statistics showing
how much different Python versions have been used over the past two and five-sixths
years.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the pip installs for all packages from the
&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and October 2018:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_4f74649f3d923664.webp 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_833329fbbe4ef596.webp 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_5f7fdf46a8178145.webp 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_6795ab95c9f31d18.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pypi"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_6ca9472db9d08989.png" srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_9dcfcf51a3ba3a9a.png 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_6ca9472db9d08989.png 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_90c3599a10fb09db.png 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_a83fc75ddb557e1f.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;For the &lt;a href="https://github.com/numpy/numpy"&gt;NumPy&lt;/a&gt; scientific computing library:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_af73564d69f4db66.webp 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_932b7876d71ddcda.webp 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_67f70e6fbce25701.webp 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_2b9376db4171ab9.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="NumPy"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_18540e1feebad89c.png" srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_b2be8b31d9dc2099.png 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_18540e1feebad89c.png 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_60262fc17f9dee72.png 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_87b1196698f36b9f.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Numpy's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;For the &lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt; imaging library:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_5d4aec8ee8e6db42.webp 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_f0fe8bc12d4ec49d.webp 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_d33515c925f1989b.webp 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_9ea77602db39bd68.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pillow"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_795bd5b0af6ee951.png" srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_81d08f724916e419.png 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_795bd5b0af6ee951.png 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_7eb20f520ce2e96.png 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_fc69ec737f7aa8c3.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Pillow's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;For the &lt;a href="https://github.com/django/django"&gt;Django&lt;/a&gt; web framework:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_9a09a10363d49f86.webp 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_22d3213be693ea80.webp 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_9c6ae8859f05f0bf.webp 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_3995ce0f7610a6f6.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="django"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_d45389430242b15.png" srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_d12e1d2e9ea26613.png 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_d45389430242b15.png 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_b658b437a8aabcc0.png 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_944f29479ec21bf9.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;pylast's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;And for the &lt;a href="https://github.com/pylast/pylast"&gt;pylast&lt;/a&gt; interface to Last.fm:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_df6eda960fc1d5b7.webp 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_e2dc9fc698b6524d.webp 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_2d180a564169297e.webp 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_1625e575b418b340.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1536"
 height="1152"
 class="mx-auto my-0 rounded-md"
 alt="pylast"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_15aeb42313bf1edc.png" srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_29f18aaaa5eb2c2f.png 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_15aeb42313bf1edc.png 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_b5c90c65440f3130.png 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_dc669d7619106aed.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Pillow's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Statistics were collected using
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a
wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; to fetch all monthly downloads
from the PyPI database on Google BigQuery and save them as JSON files. Data was
downloaded over several days as getting all months uses up a lot of free BigQuery quota.
Then &lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots
a chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is
in the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="https://langui.sh/2016/12/09/data-driven-decisions/"&gt;Data Driven Decisions Using PyPI Download Statistics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,
1&lt;/a&gt; (January
2016 — June 2018)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,
without needing to sign up for BigQuery&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data
from PyPI Stats&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Python version share over time, 1</title><link>https://hugovk.dev/blog/2018/python-version-share-over-time-1/</link><pubDate>Thu, 05 Jul 2018 14:40:48 +0000</pubDate><guid>https://hugovk.dev/blog/2018/python-version-share-over-time-1/</guid><description>&lt;h2 id="january-2016--june-2018" class="relative group"&gt;January 2016 — June 2018 &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#january-2016--june-2018" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;To celebrate the release of &lt;a href="https://realpython.com/python37-new-features/"&gt;Python 3.7&lt;/a&gt;
on &lt;a href="https://peps.python.org/pep-0537/"&gt;27th June 2018&lt;/a&gt;, here’s some statistics showing
how much different Python versions have been used over the past two and a half years.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the pip installs for all packages from the
&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and June 2018:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_aa610d4298b3adae.webp 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_13f14a57e36ab1c.webp 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_4cfc9be767ea3dda.webp 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_ee012f2a76af287a.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1756"
 height="1093"
 class="mx-auto my-0 rounded-md"
 alt="pypi"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_d275114075d6efb2.png" srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_c798a0e2eb749fb7.png 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_d275114075d6efb2.png 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_57e72c760b5719a0.png 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_b784aa405a5bb4c6.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;
&lt;p&gt;And for the &lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt; imaging library:&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_1e6eb312cc23135b.webp 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_d5070c844fa94348.webp 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_60f56cd848c31475.webp 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_971b1e4ed0519d1e.webp 1320w
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="1495"
 height="1093"
 class="mx-auto my-0 rounded-md"
 alt="pillow"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_2aff3069f58cad5e.png" srcset="https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_69aec0dcadcea3c2.png 330w,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_2aff3069f58cad5e.png 660w
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_a4ae32d8f9f04b7e.png 1024w
 
 
 ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_ee3dcbcc6230ca0f.png 1320w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 


&lt;/figure&gt;
&lt;/p&gt;
&lt;center&gt;&lt;small&gt;Pillow's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;
&lt;h2 id="how" class="relative group"&gt;How &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#how" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Statistics were collected using
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a
wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; to fetch all monthly downloads
from the PyPI database on Google BigQuery and save them as JSON files. Data was
downloaded over three or four days as getting all months uses up a lot of free BigQuery
quota. Then
&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; converts
them into a single CSV file for chart-wrangling in Excel. Raw CSV and JSON data is in
the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="see-also" class="relative group"&gt;See also &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#see-also" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Paul Kehrer fetched similar stats from BigQuery in December 2016, writing in
&lt;a href="https://langui.sh/2016/12/09/data-driven-decisions/"&gt;Data Driven Decisions Using PyPI Download Statistics&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Data ingestion into the BigQuery data set was spotty prior to June 2016*, but you can
see a significant uptick in Python 3 based downloads over 2016.
&lt;a href="https://frinkiac.com/caption/S08E11/289555"&gt;If these trends continue…&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;* But it shouldn’t be biased, so these percentages are likely to be accurate.&lt;/p&gt;
&lt;/blockquote&gt;</description></item><item><title>Words of the year from Twitter, 2017</title><link>https://hugovk.dev/blog/2017/twitter-woty-2017/</link><pubDate>Sat, 30 Dec 2017 20:00:10 +0000</pubDate><guid>https://hugovk.dev/blog/2017/twitter-woty-2017/</guid><description>&lt;h2&gt;Three bots have been collecting words from Twitter for the past year.&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Short version:&lt;/p&gt;
&lt;p&gt;In 2017, people on Twitter talked about the words broflake, caucacity, cockwomble, covfefe, dotard, douchecanoe, dracarys, shitgibbon, shooketh, twatwaffle, and woke.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Long version:&lt;/p&gt;
&lt;p&gt;They've looked for certain sentences and extracted the X.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt; -- "I love/hate the word X"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt; -- "X is not/isn't/ain't a word"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt; -- "X is my new favorite/favouriteword"&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Combining and comparing the logs of all three, let's see the top words of 2017. &lt;strong&gt;Bold&lt;/strong&gt; means a&amp;nbsp;word wasn't in that chart in 2016.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Combined output&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Total in 2017: 120,412&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;forever (2,828)&lt;/li&gt;
&lt;li&gt;mines (1,687)&lt;/li&gt;
&lt;li&gt;no (1,161)&lt;/li&gt;
&lt;li&gt;love (1,092)&lt;/li&gt;
&lt;li&gt;justice (1,072)&lt;/li&gt;
&lt;li&gt;ain't (1,038)&lt;/li&gt;
&lt;li&gt;irregardless (1,027)&lt;/li&gt;
&lt;li&gt;bae (1,002)&lt;/li&gt;
&lt;li&gt;moist (891)&lt;/li&gt;
&lt;li&gt;homophobia (856)&lt;/li&gt;
&lt;li&gt;soon (811)&lt;/li&gt;
&lt;li&gt;loyalty (594)&lt;/li&gt;
&lt;li&gt;conversate (593)&lt;/li&gt;
&lt;li&gt;finna (566)&lt;/li&gt;
&lt;li&gt;bigly (551)&lt;/li&gt;
&lt;li&gt;impossible (526)&lt;/li&gt;
&lt;li&gt;cunt (500)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;alot&lt;/strong&gt; (495)&lt;/li&gt;
&lt;li&gt;sorry (449)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;nut&lt;/strong&gt; (441)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;lovihatibot&lt;/th&gt;
&lt;th&gt;nixibot&lt;/th&gt;
&lt;th&gt;favibot&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2017 total: 55,063&lt;/td&gt;
&lt;td&gt;2017 total: 50,050&lt;/td&gt;
&lt;td&gt;2017 total: 9,314&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;bae (950)&lt;/li&gt;
&lt;li&gt;no (941)&lt;/li&gt;
&lt;li&gt;moist (880)&lt;/li&gt;
&lt;li&gt;homophobia (854)&lt;/li&gt;
&lt;li&gt;soon (791)&lt;/li&gt;
&lt;li&gt;cunt (441)&lt;/li&gt;
&lt;li&gt;nut (435)&lt;/li&gt;
&lt;li&gt;lit (398)&lt;/li&gt;
&lt;li&gt;sexy (392)&lt;/li&gt;
&lt;li&gt;sorry (377)&lt;/li&gt;
&lt;li&gt;bitch (345)&lt;/li&gt;
&lt;li&gt;cock (339)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;yolo&lt;/strong&gt; (298)&lt;/li&gt;
&lt;li&gt;panties (292)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shook&lt;/strong&gt; (267)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;woke&lt;/strong&gt; (262)&lt;/li&gt;
&lt;li&gt;love (249)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;clout&lt;/strong&gt; (248)&lt;/li&gt;
&lt;li&gt;daddy (242)&lt;/li&gt;
&lt;li&gt;hate (239)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;forever (2,699)&lt;/li&gt;
&lt;li&gt;mines (1,666)&lt;/li&gt;
&lt;li&gt;justice (1,072)&lt;/li&gt;
&lt;li&gt;ain't (1,017)&lt;/li&gt;
&lt;li&gt;irregardless (1,014)&lt;/li&gt;
&lt;li&gt;love (841)&lt;/li&gt;
&lt;li&gt;loyalty (587)&lt;/li&gt;
&lt;li&gt;conversate (586)&lt;/li&gt;
&lt;li&gt;bigly (522)&lt;/li&gt;
&lt;li&gt;impossible (501)&lt;/li&gt;
&lt;li&gt;alot (493)&lt;/li&gt;
&lt;li&gt;finna (463)&lt;/li&gt;
&lt;li&gt;anyways (385)&lt;/li&gt;
&lt;li&gt;hurted (271)&lt;/li&gt;
&lt;li&gt;stupider (251)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;covfefe&lt;/strong&gt; (246)&lt;/li&gt;
&lt;li&gt;funner (245)&lt;/li&gt;
&lt;li&gt;marriage (203)&lt;/li&gt;
&lt;li&gt;worser (202)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;stupidest&lt;/strong&gt; (183)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;mania&lt;/strong&gt; (255)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dotard&lt;/strong&gt; (133)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;covfefe&lt;/strong&gt; (102)&lt;/li&gt;
&lt;li&gt;no (95)&lt;/li&gt;
&lt;li&gt;yikes (48)&lt;/li&gt;
&lt;li&gt;cockwomble (35)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shooketh&lt;/strong&gt; (34)&lt;/li&gt;
&lt;li&gt;cunt (33)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;serendipity&lt;/strong&gt; (30)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;caucacity&lt;/strong&gt; (25)&lt;/li&gt;
&lt;li&gt;bitch (24)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shitgibbon&lt;/strong&gt; (23)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dracarys&lt;/strong&gt; (22)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;broflake&lt;/strong&gt; (20)&lt;/li&gt;
&lt;li&gt;wow (19)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;twatwaffle&lt;/strong&gt; (19)&lt;/li&gt;
&lt;li&gt;totality (17)&lt;/li&gt;
&lt;li&gt;headass (17)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;thrussy&lt;/strong&gt; (16)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;douchecanoe&lt;/strong&gt; (16)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here's top 10 charts for each phrase from each bot.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;@lovihatbot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;I love the word X&lt;/th&gt;
&lt;th&gt;I hate the word X&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;cunt (293)&lt;/li&gt;
&lt;li&gt;fuck (218)&lt;/li&gt;
&lt;li&gt;fate (197)&lt;/li&gt;
&lt;li&gt;bitch (178)&lt;/li&gt;
&lt;li&gt;moist (143)&lt;/li&gt;
&lt;li&gt;play (128)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;spooky&lt;/strong&gt; (71)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;love&lt;/strong&gt; (66)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;choice&lt;/strong&gt; (62)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;free&lt;/strong&gt; (60)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;bae (932)&lt;/li&gt;
&lt;li&gt;no (893)&lt;/li&gt;
&lt;li&gt;homophobia (851)&lt;/li&gt;
&lt;li&gt;soon (770)&lt;/li&gt;
&lt;li&gt;moist (737)&lt;/li&gt;
&lt;li&gt;nut (431)&lt;/li&gt;
&lt;li&gt;sexy (380)&lt;/li&gt;
&lt;li&gt;lit (379)&lt;/li&gt;
&lt;li&gt;sorry (370)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cock&lt;/strong&gt; (305)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@nixibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is not a word&lt;/th&gt;
&lt;th&gt;X isn't a word&lt;/th&gt;
&lt;th&gt;X ain't a word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;forever (2,683)&lt;/li&gt;
&lt;li&gt;mines (1,153)&lt;/li&gt;
&lt;li&gt;irregardless (765)&lt;/li&gt;
&lt;li&gt;love (598)&lt;/li&gt;
&lt;li&gt;impossible (454)&lt;/li&gt;
&lt;li&gt;loyalty (398)&lt;/li&gt;
&lt;li&gt;conversate (392)&lt;/li&gt;
&lt;li&gt;bigly (347)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;alot&lt;/strong&gt; (307)&lt;/li&gt;
&lt;li&gt;finna (288)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;justice (1,057)&lt;/li&gt;
&lt;li&gt;mines (466)&lt;/li&gt;
&lt;li&gt;ain't (412)&lt;/li&gt;
&lt;li&gt;irregardless (242)&lt;/li&gt;
&lt;li&gt;love (223)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;alot&lt;/strong&gt; (185)&lt;/li&gt;
&lt;li&gt;conversate (184)&lt;/li&gt;
&lt;li&gt;anyways (176)&lt;/li&gt;
&lt;li&gt;bigly (171)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cant&lt;/strong&gt; (149)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;ain't (390)&lt;/li&gt;
&lt;li&gt;finna (58)&lt;/li&gt;
&lt;li&gt;loyalty (53)&lt;/li&gt;
&lt;li&gt;mines (47)&lt;/li&gt;
&lt;li&gt;ignorantest (26)&lt;/li&gt;
&lt;li&gt;is (22)&lt;/li&gt;
&lt;li&gt;love (20)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shit&lt;/strong&gt; (17)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;just&lt;/strong&gt; (15)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;worser&lt;/strong&gt; (11)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@favibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is my new favorite word&lt;/th&gt;
&lt;th&gt;X is my new favourite word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;mania&lt;/strong&gt; (255)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dotard&lt;/strong&gt; (110)&lt;/li&gt;
&lt;li&gt;no (87)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;covfefe&lt;/strong&gt; (80)&lt;/li&gt;
&lt;li&gt;yikes (40)&lt;/li&gt;
&lt;li&gt;cunt (25)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;serendipity&lt;/strong&gt; (23)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shooketh&lt;/strong&gt; (22)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;caucacity&lt;/strong&gt; (22)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cockwomble&lt;/strong&gt; (20)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;dotard&lt;/strong&gt; (20)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;covfefe&lt;/strong&gt; (18)&lt;/li&gt;
&lt;li&gt;cockwomble (15)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shooketh&lt;/strong&gt; (11)&lt;/li&gt;
&lt;li&gt;no (7)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;nonce&lt;/strong&gt; (7)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;muggy&lt;/strong&gt; (6)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wow&lt;/strong&gt; (6)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;serendipity&lt;/strong&gt; (6)&lt;/li&gt;
&lt;li&gt;yikes (5)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Previous years&lt;/h2&gt;
&lt;p&gt;See the words from&amp;nbsp;&lt;a href="../../2017/twitter-woty-2016/"&gt;2016&lt;/a&gt;,&amp;nbsp;&lt;a href="../../2016/twitter-woty-2015/"&gt;2015&lt;/a&gt;,&amp;nbsp;&lt;a href="../../2015/twitter-woty-2014/"&gt;2014&amp;nbsp;&lt;/a&gt;and&amp;nbsp;&lt;a href="../../2013/twitters-new-favourite-words/"&gt;2013,&lt;/a&gt;&lt;a href="../..//2016/twitter-woty-2015/"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;2018&lt;/h2&gt;
&lt;p&gt;For words causing a reaction in 2018, follow&amp;nbsp;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt;,&amp;nbsp;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt;&amp;nbsp;on Twitter.&lt;/p&gt;</description></item><item><title>Words of the year from Twitter, 2016</title><link>https://hugovk.dev/blog/2017/twitter-woty-2016/</link><pubDate>Thu, 05 Jan 2017 16:41:29 +0000</pubDate><guid>https://hugovk.dev/blog/2017/twitter-woty-2016/</guid><description>&lt;h2&gt;Three bots have been collecting words from Twitter for the past year.&lt;/h2&gt;
&lt;p&gt;They've looked for certain sentences and extracted the X.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt; -- "I love/hate the word X"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt; -- "X is not/isn't/ain't a word"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt; -- "X is my new favorite/favourite/fave word"&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Combining and comparing the logs of all three, let's see the top words of 2016. &lt;strong&gt;Bold&lt;/strong&gt; means a&amp;nbsp;word wasn't in that chart in 2015.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Combined output&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Total in 2016: 168,241&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;mines (4,060)&lt;/li&gt;
&lt;li&gt;bae (3,369)&lt;/li&gt;
&lt;li&gt;no (2,582)&lt;/li&gt;
&lt;li&gt;forever (2,143)&lt;/li&gt;
&lt;li&gt;lit (2,005)&lt;/li&gt;
&lt;li&gt;moist (1,805)&lt;/li&gt;
&lt;li&gt;love (1,790)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bigly (1,488)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;conversate (1,358)&lt;/li&gt;
&lt;li&gt;ain't (1,339)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;irregardless (1,204)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;homophobia (1,186)&lt;/li&gt;
&lt;li&gt;justice (1,138)&lt;/li&gt;
&lt;li&gt;loyalty (985)&lt;/li&gt;
&lt;li&gt;soon (886)&lt;/li&gt;
&lt;li&gt;cunt (869)&lt;/li&gt;
&lt;li&gt;impossible (787)&lt;/li&gt;
&lt;li&gt;sorry (781)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;family (766)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;finna (674)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;lovihatibot&lt;/th&gt;
&lt;th&gt;nixibot&lt;/th&gt;
&lt;th&gt;favibot&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;2016 total:&amp;nbsp;85,828&lt;/td&gt;
&lt;td style="text-align: center;"&gt;2016 total: 71,609&lt;/td&gt;
&lt;td style="text-align: center;"&gt;2016&amp;nbsp;total: 10,807&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;bae (3,242)&lt;/li&gt;
&lt;li&gt;no (2,209)&lt;/li&gt;
&lt;li&gt;lit (1,934)&lt;/li&gt;
&lt;li&gt;moist (1,788)&lt;/li&gt;
&lt;li&gt;homophobia (1,182)&lt;/li&gt;
&lt;li&gt;soon (851)&lt;/li&gt;
&lt;li&gt;cunt (798)&lt;/li&gt;
&lt;li&gt;sorry (692)&lt;/li&gt;
&lt;li&gt;panties (575)&lt;/li&gt;
&lt;li&gt;sexy (524)&lt;/li&gt;
&lt;li&gt;bitch (516)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;nut (509)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;slay (496)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;daddy (495)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cock (462)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;baby (432)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;babe (430)&lt;/li&gt;
&lt;li&gt;pussy (425)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hate (415)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;love (402)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (4,035)&lt;/li&gt;
&lt;li&gt;forever (2,003)&lt;/li&gt;
&lt;li&gt;love (1,387)&lt;/li&gt;
&lt;li&gt;conversate (1,345)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bigly (1,341)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;ain't (1,296)&lt;/li&gt;
&lt;li&gt;irregardless (1,186)&lt;/li&gt;
&lt;li&gt;justice (1,134)&lt;/li&gt;
&lt;li&gt;loyalty (961)&lt;/li&gt;
&lt;li&gt;impossible (752)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;family (732)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;anyways (547)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;finna (526)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4ever (508)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;alot (448)&lt;/li&gt;
&lt;li&gt;funner (445)&lt;/li&gt;
&lt;li&gt;worser (421)&lt;/li&gt;
&lt;li&gt;stupider (367)&lt;/li&gt;
&lt;li&gt;hurted (319)&lt;/li&gt;
&lt;li&gt;marriage (301)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;no (109)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bigly (102)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;yikes (91)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shook (42)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (41)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;braggadocious (37)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shenanigans (34)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bragadocious (32)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;wow (31)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;malarkey (30)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;bitch (30)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;headass (29)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;lit (28)&lt;/li&gt;
&lt;li&gt;savage (26)&lt;/li&gt;
&lt;li&gt;tragic (25)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bougie (25)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;petty (22)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cockwomble (22)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;finesse (21)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;soon (20)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here's top 10 charts for each phrase from each bot.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;@lovihatbot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;I love the word X&lt;/th&gt;
&lt;th&gt;I hate the word X&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;cunt (475)&lt;/li&gt;
&lt;li&gt;delicious (315)&lt;/li&gt;
&lt;li&gt;fuck (307)&lt;/li&gt;
&lt;li&gt;moist (242)&lt;/li&gt;
&lt;li&gt;fate (219)&lt;/li&gt;
&lt;li&gt;bitch (216)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;play (127)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;baby (114)&lt;/li&gt;
&lt;li&gt;babe (109)&lt;/li&gt;
&lt;li&gt;lit (94)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;bae (3,208)&lt;/li&gt;
&lt;li&gt;no (2,135)&lt;/li&gt;
&lt;li&gt;lit (1,840)&lt;/li&gt;
&lt;li&gt;moist (1,546)&lt;/li&gt;
&lt;li&gt;homophobia (1,181)&lt;/li&gt;
&lt;li&gt;soon (808)&lt;/li&gt;
&lt;li&gt;sorry (689)&lt;/li&gt;
&lt;li&gt;panties (551)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sexy (507)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;nut (494)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@nixibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is not a word&lt;/th&gt;
&lt;th&gt;X isn't a word&lt;/th&gt;
&lt;th&gt;X ain't a word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (2,679)&lt;/li&gt;
&lt;li&gt;forever (1,981)&lt;/li&gt;
&lt;li&gt;love (943)&lt;/li&gt;
&lt;li&gt;irregardless (890)&lt;/li&gt;
&lt;li&gt;conversate (880)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bigly (837)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;impossible (678)&lt;/li&gt;
&lt;li&gt;loyalty (636)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4ever (507)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;finna (325)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (1,283)&lt;/li&gt;
&lt;li&gt;justice (1,124)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;family (721)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;ain't (528)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bigly (473)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;conversate (441)&lt;/li&gt;
&lt;li&gt;love (391)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;irregardless (281)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;anyways (270)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;stupider (259)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;ain't (509)&lt;/li&gt;
&lt;li&gt;loyalty (121)&lt;/li&gt;
&lt;li&gt;mines (73)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;finna (63)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;ignorantest (56)&lt;/li&gt;
&lt;li&gt;love (53)&lt;/li&gt;
&lt;li&gt;impossible (32)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bigly (31)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;is (28)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;conversate (24)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@favibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is my new favorite word&lt;/th&gt;
&lt;th&gt;X is my new favourite word&lt;/th&gt;
&lt;th&gt;X is my new fave word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;no (98)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bigly (93)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;yikes (65)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shook (32)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (29)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;braggadocious (28)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bragadocious (28)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shenanigans (27)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wow (27)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;malarkey (26)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;cockwomble (19)&lt;/li&gt;
&lt;li&gt;yikes (16)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dampfnudel (11)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (10)&lt;/li&gt;
&lt;li&gt;savage (10)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;no (9)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bigly (9)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wankpuffin (8)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;braggadocious (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shenanigans (7)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;yikes (10)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;shook (5)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;tragic (5)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;soon (4)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;nunty (4)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;yo (3)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bitch (3)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;wow (3)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;malarkey (3)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ganern (2)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Changes from&amp;nbsp;past years&lt;/h2&gt;
&lt;p&gt;Let's see how mentions have changed for some previous years' words.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;People talking about words ‒&amp;nbsp;especially new favourite &amp;nbsp;words, or loved words, or hated words, or words they've decided don't exist ‒&amp;nbsp;may be a good indicator of brand new words or at least words with new currency.&amp;nbsp;Both increases and decreases may reflect an underlying change in use, and decreases may reflect an acceptance of the words.&lt;/p&gt;
&lt;p&gt;The words from &lt;a href="../../2013/twitters-new-favourite-words/"&gt;2013 &lt;/a&gt;and&amp;nbsp;&lt;a href="../..//2015/twitter-woty-2014/"&gt;2014&lt;/a&gt;&amp;nbsp;have all more or less tailed off.&lt;/p&gt;
&lt;p&gt;The words from &lt;a href="../../2013/twitters-new-favourite-words/"&gt;2013 &lt;/a&gt;and&amp;nbsp;&lt;a href="../..//2015/twitter-woty-2014/"&gt;2014&lt;/a&gt;&amp;nbsp;have all more or less tailed off.&lt;/p&gt;
&lt;p&gt;From &lt;a href="../../2016/twitter-woty-2015/"&gt;2015&lt;/a&gt;, &lt;em&gt;lit&lt;/em&gt; is still being talked about. It's commonly used as&amp;nbsp;"lit af", or "lit as fuck", for example&amp;nbsp;&lt;a href="https://twitter.com/BeyondBetter_/status/682990826499211268"&gt;in this tweet&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My Year is starting off lit af👌🏼 ...but is gonna be TD by Monday morning&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Something, I'm not sure what, happened in June 2015 that caused its use to explode on Twitter:&lt;/p&gt;
&lt;p&gt;&lt;img src="lit.png"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fam&amp;nbsp;&lt;/em&gt;(from family, for those closest to you but not necessarily family), has been around a long time, peaked in January 2015 and has tailed off.&lt;/p&gt;
&lt;p&gt;&lt;img src="fam.png"&gt;&lt;/p&gt;
&lt;p&gt;Similarly, &lt;em&gt;slay&amp;nbsp;&lt;/em&gt;("&lt;a href="https://www.urbandictionary.com/define.php?term=slay&amp;amp;defid=7222797"&gt;killed it. succeeded in something amazing&lt;/a&gt;", "&lt;a href="https://www.urbandictionary.com/define.php?term=slay&amp;amp;defid=7842546"&gt;something you tell someone when they look sexy as f***&lt;/a&gt;") has been around for a while and was talked about in 2016 as well.&lt;/p&gt;
&lt;p&gt;&lt;img src="slay.png"&gt;&lt;/p&gt;
&lt;h2&gt;New in 2016&lt;/h2&gt;
&lt;p&gt;2016's words have been dominated by those from the US presidential elections, such as &lt;em&gt;braggadocious&lt;/em&gt; (and &lt;em&gt;bragadocious, &lt;/em&gt;"boastful or arrogant"), &lt;em&gt;shenanigans&lt;/em&gt; ("secret or dishonest activity or maneuvering") and &lt;em&gt;malarkey&lt;/em&gt; ("meaningless talk; nonsense"), but most notably by &lt;em&gt;bigly&lt;/em&gt; (or "big league"):&lt;/p&gt;
&lt;p&gt;&lt;img src="bigly.png"&gt;&lt;/p&gt;
&lt;p&gt;Another with a very recent peak is &lt;em&gt;headass&lt;/em&gt;:&lt;/p&gt;
&lt;p&gt;&lt;img src="headass.png"&gt;&lt;/p&gt;
&lt;h2&gt;2017&lt;/h2&gt;
&lt;p&gt;For words causing a reaction in 2017, follow&amp;nbsp;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt;,&amp;nbsp;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt;&amp;nbsp;on Twitter.&lt;/p&gt;</description></item><item><title>#woty15 nominations</title><link>https://hugovk.dev/blog/2016/woty15-nominations/</link><pubDate>Fri, 08 Jan 2016 21:34:16 +0000</pubDate><guid>https://hugovk.dev/blog/2016/woty15-nominations/</guid><description>&lt;p&gt;Here&amp;rsquo;s some quick charts of some of the words nominated for American Dialect Society
2015 Words of the Year
&lt;a href="https://americandialect.org/wp-content/uploads/2015-WOTY-nominations.pdf"&gt;PDF&lt;/a&gt;), data
taken from a corpus of words talked about on Twitter
(&lt;a href="../twitter-woty-2015"&gt;read more about them here&lt;/a&gt;), as collected by
&lt;a href="https://twitter.com/hugovk/lists/notabletwitterwordbots"&gt;a trio of bots&lt;/a&gt;. Notable
troughs are generally when the bots were offline.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2016/woty15-nominations/squad_hu_17466ac65596e619.webp 330w,https://hugovk.dev/blog/2016/woty15-nominations/squad_hu_d87a503472982fde.webp 660w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/squad_hu_ef4d8e6edac6f13.webp 700w
 
 
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/squad_hu_ef4d8e6edac6f13.webp 700w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="700"
 height="500"
 class="mx-auto my-0 rounded-md"
 alt="squad"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2016/woty15-nominations/squad_hu_85bd1a31099b6b6f.png" srcset="https://hugovk.dev/blog/2016/woty15-nominations/squad_hu_fb9675271d413f2f.png 330w,https://hugovk.dev/blog/2016/woty15-nominations/squad_hu_85bd1a31099b6b6f.png 660w
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/squad.png 700w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/squad.png 700w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 

&lt;figcaption class="text-center"&gt;squad&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2016/woty15-nominations/fuckboy_hu_c386e3ac3450fe90.webp 330w,https://hugovk.dev/blog/2016/woty15-nominations/fuckboy_hu_d2aae44c016b13ee.webp 660w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fuckboy_hu_e17716afce22b893.webp 700w
 
 
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fuckboy_hu_e17716afce22b893.webp 700w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="700"
 height="500"
 class="mx-auto my-0 rounded-md"
 alt="fuckboy"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2016/woty15-nominations/fuckboy_hu_f5be252e49f34da5.png" srcset="https://hugovk.dev/blog/2016/woty15-nominations/fuckboy_hu_785667f3613e0556.png 330w,https://hugovk.dev/blog/2016/woty15-nominations/fuckboy_hu_f5be252e49f34da5.png 660w
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fuckboy.png 700w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fuckboy.png 700w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 

&lt;figcaption class="text-center"&gt;fuckboy&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2016/woty15-nominations/af_hu_d851e0aa3978e14f.webp 330w,https://hugovk.dev/blog/2016/woty15-nominations/af_hu_e4685842e7d47a99.webp 660w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/af_hu_2b4ab1d0c6b55fa3.webp 700w
 
 
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/af_hu_2b4ab1d0c6b55fa3.webp 700w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="700"
 height="500"
 class="mx-auto my-0 rounded-md"
 alt="af"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2016/woty15-nominations/af_hu_b5754a1b86e6d007.png" srcset="https://hugovk.dev/blog/2016/woty15-nominations/af_hu_28b053e64c2739c.png 330w,https://hugovk.dev/blog/2016/woty15-nominations/af_hu_b5754a1b86e6d007.png 660w
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/af.png 700w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/af.png 700w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 

&lt;figcaption class="text-center"&gt;af&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2016/woty15-nominations/lit_hu_a2f0eea849e617ed.webp 330w,https://hugovk.dev/blog/2016/woty15-nominations/lit_hu_839d55fbf8b418aa.webp 660w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/lit_hu_dad27c38a9257d98.webp 700w
 
 
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/lit_hu_dad27c38a9257d98.webp 700w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="700"
 height="500"
 class="mx-auto my-0 rounded-md"
 alt="lit"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2016/woty15-nominations/lit_hu_3da750cb16b5a80.png" srcset="https://hugovk.dev/blog/2016/woty15-nominations/lit_hu_d60a4ed65dea05c8.png 330w,https://hugovk.dev/blog/2016/woty15-nominations/lit_hu_3da750cb16b5a80.png 660w
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/lit.png 700w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/lit.png 700w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 

&lt;figcaption class="text-center"&gt;lit&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2016/woty15-nominations/fleek_hu_a75b25cd93c69634.webp 330w,https://hugovk.dev/blog/2016/woty15-nominations/fleek_hu_980473ecb717c004.webp 660w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fleek_hu_1a21af5989993591.webp 700w
 
 
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fleek_hu_1a21af5989993591.webp 700w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="700"
 height="500"
 class="mx-auto my-0 rounded-md"
 alt="fleek"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2016/woty15-nominations/fleek_hu_fcdcaddcd72d485a.png" srcset="https://hugovk.dev/blog/2016/woty15-nominations/fleek_hu_55af6ac0a8727776.png 330w,https://hugovk.dev/blog/2016/woty15-nominations/fleek_hu_fcdcaddcd72d485a.png 660w
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fleek.png 700w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fleek.png 700w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 

&lt;figcaption class="text-center"&gt;fleek&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id="from-my-2015-nominations" class="relative group"&gt;From my &lt;a href="../twitter-woty-2015"&gt;2015 nominations&lt;/a&gt;: &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#from-my-2015-nominations" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Lit, fleek: see above.&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2016/woty15-nominations/fam_hu_d5dc5bba40746e5.webp 330w,https://hugovk.dev/blog/2016/woty15-nominations/fam_hu_39d96a4f86f1e85e.webp 660w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fam_hu_33d4bac223e0329a.webp 700w
 
 
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fam_hu_33d4bac223e0329a.webp 700w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="700"
 height="500"
 class="mx-auto my-0 rounded-md"
 alt="fam"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2016/woty15-nominations/fam_hu_d0befa332ffcfb2f.png" srcset="https://hugovk.dev/blog/2016/woty15-nominations/fam_hu_6d444f6f8e1380ed.png 330w,https://hugovk.dev/blog/2016/woty15-nominations/fam_hu_d0befa332ffcfb2f.png 660w
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fam.png 700w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/fam.png 700w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 

&lt;figcaption class="text-center"&gt;fam&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;





&lt;figure&gt;
 
 








 
 &lt;picture
 class="mx-auto my-0 rounded-md"
 
 &gt;
 
 
 
 
 &lt;source
 
 srcset="https://hugovk.dev/blog/2016/woty15-nominations/slay_hu_d0067de17d6e20a1.webp 330w,https://hugovk.dev/blog/2016/woty15-nominations/slay_hu_3dce8265d44a69a.webp 660w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/slay_hu_5d299d55b1384d18.webp 700w
 
 
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/slay_hu_5d299d55b1384d18.webp 700w
 
 "
 
 sizes="100vw"
 type="image/webp"
 /&gt;
 
 &lt;img
 width="700"
 height="500"
 class="mx-auto my-0 rounded-md"
 alt="slay"
 loading="lazy" decoding="async"
 
 src="https://hugovk.dev/blog/2016/woty15-nominations/slay_hu_2f2374ab476386fa.png" srcset="https://hugovk.dev/blog/2016/woty15-nominations/slay_hu_b7f14a191d0f4038.png 330w,https://hugovk.dev/blog/2016/woty15-nominations/slay_hu_2f2374ab476386fa.png 660w
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/slay.png 700w
 
 
 ,https://hugovk.dev/blog/2016/woty15-nominations/slay.png 700w
 "
 sizes="100vw"
 
 /&gt;
 &lt;/picture&gt;
 

&lt;figcaption class="text-center"&gt;slay&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;And &lt;a href="../twitter-woty-2015"&gt;for my 2013 nominations see here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Words of the year from Twitter, 2015</title><link>https://hugovk.dev/blog/2016/twitter-woty-2015/</link><pubDate>Sat, 02 Jan 2016 11:46:01 +0000</pubDate><guid>https://hugovk.dev/blog/2016/twitter-woty-2015/</guid><description>&lt;h2&gt;Three bots have been collecting words from Twitter for the past year.&lt;/h2&gt;
&lt;p&gt;They've looked for certain sentences and extracted the X.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt; -- "I love/hate the word X"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt; -- "X is not/isn't/ain't a word"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt; -- "X is my new favorite/favourite/fave word"&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Combining and comparing the logs of all three, let's see the top words of 2015. &lt;strong&gt;Bold&lt;/strong&gt; means a word wasn't in that chart in 2014.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Combined output&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2015 total: &lt;span class="s1"&gt;219,918&lt;/span&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;bae (9,513)&lt;/li&gt;
&lt;li&gt;mines (4,282)&lt;/li&gt;
&lt;li&gt;no (4,239)&lt;/li&gt;
&lt;li&gt;love (2,353)&lt;/li&gt;
&lt;li&gt;moist (2,345)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lit (2,184)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;loyalty (1,984)&lt;/li&gt;
&lt;li&gt;ain't (1,725)&lt;/li&gt;
&lt;li&gt;conversate (1,664)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fleek (1,525)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;justice (1,470)&lt;/li&gt;
&lt;li&gt;soon (1,420)&lt;/li&gt;
&lt;li&gt;impossible (1,306)&lt;/li&gt;
&lt;li&gt;homophobia (1,276)&lt;/li&gt;
&lt;li&gt;cunt (1,258)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;year (1,219)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;forever (1,214)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fam (1,149)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;sorry (1,114)&lt;/li&gt;
&lt;li&gt;panties (1,060)&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;lovihatibot&lt;/th&gt;
&lt;th&gt;nixibot&lt;/th&gt;
&lt;th&gt;favibot&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;2015 total: 125,867&lt;/td&gt;
&lt;td style="text-align: center;"&gt;2015 total: 81,960&lt;/td&gt;
&lt;td style="text-align: center;"&gt;2015 total: 13,976&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;bae (9,151)&lt;/li&gt;
&lt;li&gt;no (4,004)&lt;/li&gt;
&lt;li&gt;moist (2,341)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lit (2,107)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;soon (1,409)&lt;/li&gt;
&lt;li&gt;homophobia (1,278)&lt;/li&gt;
&lt;li&gt;fleek (1,274)&lt;/li&gt;
&lt;li&gt;cunt (1,182)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fam (1,077)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;panties (1,069)&lt;/li&gt;
&lt;li&gt;sorry (1,014)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuckboy (941)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;bitch (880)&lt;/li&gt;
&lt;li&gt;sexy (860)&lt;/li&gt;
&lt;li&gt;babe (815)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;slay (771)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;thot (758)&lt;/li&gt;
&lt;li&gt;pussy (706)&lt;/li&gt;
&lt;li&gt;fuck (698)&lt;/li&gt;
&lt;li&gt;love (672)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (4,299)&lt;/li&gt;
&lt;li&gt;loyalty (2,000)&lt;/li&gt;
&lt;li&gt;love (1,702)&lt;/li&gt;
&lt;li&gt;ain't (1,678)&lt;/li&gt;
&lt;li&gt;conversate (1,661)&lt;/li&gt;
&lt;li&gt;justice (1,472)&lt;/li&gt;
&lt;li&gt;impossible (1,283)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;year (1,226)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;irregardless (1,027)&lt;/li&gt;
&lt;li&gt;forever (953)&lt;/li&gt;
&lt;li&gt;funner (717)&lt;/li&gt;
&lt;li&gt;worser (641)&lt;/li&gt;
&lt;li&gt;bestest (517)&lt;/li&gt;
&lt;li&gt;marriage (514)&lt;/li&gt;
&lt;li&gt;anyways (507)&lt;/li&gt;
&lt;li&gt;alot (496)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hurted (443)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;stupider (442)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;conversating (426)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;bae (413)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;rendezvous (165)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;no (119)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lit (97)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;savage (83)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;yikes (82)&lt;/li&gt;
&lt;li&gt;fuckboy (81)&lt;/li&gt;
&lt;li&gt;cunt (60)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fam (50)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;twat (42)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sensational (37)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;bitch (37)&lt;/li&gt;
&lt;li&gt;fuck (37)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuckery (29)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tragic (29)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;irrelevant (27)&lt;/li&gt;
&lt;li&gt;wow (26)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;kerfuffle (23)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;groovy (22)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;rad (22)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hoe (21)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here's top 10 charts for each phrase from each bot.&lt;/p&gt;
&lt;h2&gt;@lovihatbot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;I love the word X&lt;/th&gt;
&lt;th&gt;I hate the word X&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;cunt (1,253)&lt;/li&gt;
&lt;li&gt;fuck (1,075)&lt;/li&gt;
&lt;li&gt;bae (1,029)&lt;/li&gt;
&lt;li&gt;thot (700)&lt;/li&gt;
&lt;li&gt;bitch (661)&lt;/li&gt;
&lt;li&gt;babe (497)&lt;/li&gt;
&lt;li&gt;woman (323)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;moist (309)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;delicious (295)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;rad (269)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;bae (22,926)&lt;/li&gt;
&lt;li&gt;no (6,274)&lt;/li&gt;
&lt;li&gt;thot (6,155)&lt;/li&gt;
&lt;li&gt;moist (2,967)&lt;/li&gt;
&lt;li&gt;homophobia (2,052)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;soon (1,700)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;panties (1,671)&lt;/li&gt;
&lt;li&gt;selfie (1,506)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sorry (1,446)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;sexy (1,281)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@nixibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is not a word&lt;/th&gt;
&lt;th&gt;X isn't a word&lt;/th&gt;
&lt;th&gt;X ain't a word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (2,682)&lt;/li&gt;
&lt;li&gt;loyalty (1,622)&lt;/li&gt;
&lt;li&gt;impossible (1,159)&lt;/li&gt;
&lt;li&gt;love (1,154)&lt;/li&gt;
&lt;li&gt;conversate (1,101)&lt;/li&gt;
&lt;li&gt;forever (909)&lt;/li&gt;
&lt;li&gt;irregardless (774)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;year (689)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;marriage (422)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;worser (355)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (1,483)&lt;/li&gt;
&lt;li&gt;justice (1,454)&lt;/li&gt;
&lt;li&gt;ain't (722)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;year (530)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;conversate (523)&lt;/li&gt;
&lt;li&gt;love (475)&lt;/li&gt;
&lt;li&gt;funner (423)&lt;/li&gt;
&lt;li&gt;bestest (342)&lt;/li&gt;
&lt;li&gt;stupider (303)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;worser (254)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;ain't (611)&lt;/li&gt;
&lt;li&gt;loyalty (152)&lt;/li&gt;
&lt;li&gt;ignorantest (115)&lt;/li&gt;
&lt;li&gt;mines (79)&lt;/li&gt;
&lt;li&gt;love (60)&lt;/li&gt;
&lt;li&gt;impossible (51)&lt;/li&gt;
&lt;li&gt;just (43)&lt;/li&gt;
&lt;li&gt;conversate (29)&lt;/li&gt;
&lt;li&gt;worser (27)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bestest (26)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@favibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is my new favorite word&lt;/th&gt;
&lt;th&gt;X is my new favourite word&lt;/th&gt;
&lt;th&gt;X is my new fave word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;no (109)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rendezvous (97)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lit (83)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;yikes (64)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;savage (61)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;fuckboy (54)&lt;/li&gt;
&lt;li&gt;cunt (44)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fam (43)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuck (31)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sensational (29&lt;/strong&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;rendezvous (60)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuckboy (22)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;savage (18)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cockwomble (16)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (14)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;yikes (13)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;twat (11)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;lit (11)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;sassy (9)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hunty (8)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;nondindwa (8)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rendezvous (8)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;wow (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;yikes (5)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;twat (5)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;tragic (5)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;fuckboy (5)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;problematic (4)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dope (4)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;savage (4)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Change from 2013&lt;/h2&gt;
&lt;p&gt;Let's see how mentions have changed for some of &lt;a href="../../2013/twitters-new-favourite-words"&gt;2013's words&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;People talking about words ‒ especially new favourite words, or loved words, or hated words, or words they've decided don't exist ‒ may be a good indicator of brand new words or at least words with new currency. Both increases and decreases may reflect an underlying change in use, and decreases may reflect an acceptance of the words.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bae &lt;/em&gt;(babe) emerged in April 2013, peaked at 3,880 in May 2014 and whilst still popular, has tailed off to 437 by December 2015.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bae" src="bae.png" title="Bae"&gt;&lt;/p&gt;
&lt;div&gt;Similarly,&lt;em&gt; t&lt;/em&gt;&lt;em&gt;hot&lt;/em&gt; ("that hoe over there") emerged in March 2013 and grew steadily to a peak of 1,400 uses in March 2014 and dropped off to 28 by December 2015.&lt;/div&gt;
&lt;div&gt;&lt;img alt="Thot" src="thot.png" title="Thot" /&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;&lt;em&gt;Selfie&lt;/em&gt;&lt;/em&gt; (a shared photo taken with a phone) had a couple of mentions in April 2013, and peaked in March 2014 with 373, ending the year with 18 in December 2015.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Selfie" src="selfie.png" title="Selfie" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ratchet&lt;/em&gt; (a diva) peaked with 130 in March 2014, dropping to 4 by December 2015. All but *ratchet* still show up in the charts.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Ratchet" src="ratchet.png" title="Ratchet" /&gt;&lt;/p&gt;
&lt;h2&gt;Changes from 2014&lt;/h2&gt;
&lt;p&gt;From &lt;a href="../../2015/twitter-woty-2014"&gt;2014&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fleek&lt;/em&gt;, or more fully &lt;em&gt;on fleek&lt;/em&gt;, means on point, on the mark, stylish, amazing or impeccable. First mentioned in August 2014, it's dropped off to 14 by December 2015.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Fleek" src="fleek.png" title="Fleek" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bruh&lt;/em&gt; (bro, brother), not a new word but with just a few mentions throughout 2013, picked up in March 2014, and peaked in June/July 2014, but went down to 16 by December 2015 and doesn't make the top charts.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bruh" src="bruh.png" title="Bruh" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fuckboy&lt;/em&gt; still charts for favibot, but peaked in March 2015 with 202 and down to 37 by year end.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Fuckboy" src="fuckboy.png" title="Fuckboy" /&gt;&lt;/p&gt;
&lt;p&gt;Both &lt;em&gt;hella&lt;/em&gt; and &lt;em&gt;twizzle&lt;/em&gt; have dropped off the charts, the latter being a one-off from the 2014 winter Olympics.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hella" src="hella.png" title="Hella" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Twizzle" src="twizzle.png" title="Twizzle" /&gt;&lt;/p&gt;
&lt;h2&gt;New in 2015&lt;/h2&gt;
&lt;p&gt;Here's a few new to these charts.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Lit&lt;/em&gt; is the surprise new entry. It's commonly used as "lit af", or "lit as fuck", for example &lt;a href="https://twitter.com/BeyondBetter_/status/682990826499211268"&gt;in this tweet&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My Year is starting off lit af👌🏼 ...but is gonna be TD by Monday morning&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Something, I'm not sure what, happened in June 2015 that caused its use to explode on Twitter:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Lit" src="lit.png" title="Lit" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fam&lt;/em&gt; (from family, for those closest to you but not necessarily family), has been around a long time, became popular towards the end of 2014 and is still talked about.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Fam" src="fam.png" title="Fam" /&gt;&lt;/p&gt;
&lt;p&gt;Similarly, &lt;em&gt;slay&lt;/em&gt; ("&lt;a href="https://www.urbandictionary.com/define.php?term=slay&amp;amp;defid=7222797"&gt;killed it. succeeded in something amazing&lt;/a&gt;", "&lt;a href="https://www.urbandictionary.com/define.php?term=slay&amp;amp;defid=7842546"&gt;something you tell someone when they look sexy as f***&lt;/a&gt;") has been around for a while and grew towards the end of 2014, peaking in April 2015.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Slay" src="slay.png" title="Slay" /&gt;&lt;/p&gt;
&lt;h2&gt;2016&lt;/h2&gt;
&lt;p&gt;For words causing a reaction in 2016, follow &lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt;, &lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt; and &lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt; on Twitter.&lt;/p&gt;</description></item><item><title>Cutthroat verb-nouns</title><link>https://hugovk.dev/blog/2015/cutthroat-verb-nouns/</link><pubDate>Tue, 26 May 2015 12:54:38 +0000</pubDate><guid>https://hugovk.dev/blog/2015/cutthroat-verb-nouns/</guid><description>&lt;p&gt;&lt;a href="http://thelifeofwords.uwaterloo.ca/catchall-for-cutthroats/"&gt;David-Antoine Williams&lt;/a&gt; writes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;What is the difference between a &lt;em&gt;catch-all&lt;/em&gt; and a &lt;em&gt;catch-phrase&lt;/em&gt;? Both are compounds formed as Verb+Noun, but in &lt;em&gt;catch-all&lt;/em&gt;, the noun is the direct object of the verb, whereas in &lt;em&gt;catch-phrase&lt;/em&gt; it is the subject. That is, a &lt;em&gt;catch-all&lt;/em&gt; is something that catches all things, whereas a &lt;em&gt;catch-phrase&lt;/em&gt; is not something that catches phrases – it is a phrase that catches something. Get it?&lt;/p&gt;
&lt;p&gt;Recently there has been some discussion of &lt;em&gt;catch-all&lt;/em&gt; type compounds, which &lt;a href="http://tankhughes.com/?p=1050"&gt;Brianne Hughes&lt;/a&gt; has named “cutthroat compounds,” after one of the more suggestive of these. Apparently they’re rare, because they violate a general tendency for compounds in English to put the ‘head’ (e.g. &lt;em&gt;phrase&lt;/em&gt;) on the right (‘right-headedness’). Compare F. &lt;em&gt;ouvre-bouteille&lt;/em&gt; to E. &lt;em&gt;bottle-opener&lt;/em&gt; (not &lt;em&gt;open-bottle&lt;/em&gt;), which follows the most common English productive pattern, Object-Verb+er. If &lt;em&gt;catch-all&lt;/em&gt; had followed the normal pattern, we’d be talking about an &lt;em&gt;all-catcher&lt;/em&gt;, as we talk about &lt;em&gt;dog-catchers&lt;/em&gt; and &lt;em&gt;wind-catchers&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;D-AW went on to write a script that unlurked some cutthroats, searching for verbs with left-headed combinations recorded in the entry. I decided to write a script to try another approach -- look for hyphenated nouns where the first part can a verb and the second a noun.&lt;/p&gt;
&lt;p&gt;A WordNet list of 117,953 nouns was reduced to just 3,937 hyphenated words, then further reduced to 916 verb-nouns (i.e. a single hyphen, no spaces) via the &lt;a href="http://developer.wordnik.com/docs.html"&gt;Wordnik API&lt;/a&gt;, then manually whittled down. Of these potential cutthroats, the following &lt;a href="http://www.encyclopediabriannica.com/?p=23"&gt;aren't yet on Brianne's list&lt;/a&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;be-all&lt;/li&gt;
&lt;li&gt;cease-fire&lt;/li&gt;
&lt;li&gt;counter-revolution&lt;/li&gt;
&lt;li&gt;counter-sabotage&lt;/li&gt;
&lt;li&gt;cross-classification&lt;/li&gt;
&lt;li&gt;cross-division&lt;/li&gt;
&lt;li&gt;cross-eye&lt;/li&gt;
&lt;li&gt;cross-purpose&lt;/li&gt;
&lt;li&gt;cross-question&lt;/li&gt;
&lt;li&gt;cross-stitch&lt;/li&gt;
&lt;li&gt;dangle-berry&lt;/li&gt;
&lt;li&gt;dash-pot&lt;/li&gt;
&lt;li&gt;do-good&lt;/li&gt;
&lt;li&gt;drop-leaf&lt;/li&gt;
&lt;li&gt;end-all&lt;/li&gt;
&lt;li&gt;fuss-budget&lt;/li&gt;
&lt;li&gt;knock-knee&lt;/li&gt;
&lt;li&gt;make-work&lt;/li&gt;
&lt;li&gt;squint-eye&lt;/li&gt;
&lt;li&gt;sweep-second&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I've been generous with some of these, some might originally be adjective-noun but can also be seen as verb-noun: it can be argued counter-revolutions are things that counters revolutions, cross-questions are things (also questions) that cross other questions (there are more counter- and cross- words like this). Some might just be wrong, but here they are.&lt;/p&gt;
&lt;p&gt;Here's the ones it found that are already on Brianne's list:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;break-axe&lt;/li&gt;
&lt;li&gt;cure-all&lt;/li&gt;
&lt;li&gt;do-nothing&lt;/li&gt;
&lt;li&gt;drop-seed&lt;/li&gt;
&lt;li&gt;know-all&lt;/li&gt;
&lt;li&gt;make-peace&lt;/li&gt;
&lt;li&gt;rest-harrow&lt;/li&gt;
&lt;li&gt;save-all&lt;/li&gt;
&lt;li&gt;shove-ha'penny, shove-halfpenny&lt;/li&gt;
&lt;li&gt;shut-eye&lt;/li&gt;
&lt;li&gt;spend-all&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And in case I missed any, the full list of 916 verb-nouns is &lt;a href="https://gist.github.com/hugovk/5bdd19859c1bd7d7f7b0#file-wordnet-cutthroats1-txt"&gt;here&lt;/a&gt; alongside the &lt;a href="https://gist.github.com/hugovk/5bdd19859c1bd7d7f7b0#file-cutthroat-py"&gt;Python script&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Clusterstuff</title><link>https://hugovk.dev/blog/2015/clusterstuff/</link><pubDate>Tue, 26 May 2015 12:37:48 +0000</pubDate><guid>https://hugovk.dev/blog/2015/clusterstuff/</guid><description>&lt;p&gt;Strong Language, the swearing blog, recently wrote about &lt;a href="https://stronglang.wordpress.com/2015/05/23/clusterboinks-and-clusterfornications-the-children-of-clusterfuck/" target="_self"&gt;Clusterboinks and clusterfornications: The children of clusterfuck&lt;/a&gt;.&lt;em&gt;&lt;br /&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Three of my Twitter bots have been collecting words from Twitter since 2013. They&amp;#39;ve looked for certain sentences and extracted the X.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt; -- &amp;quot;I love/hate the word X&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt; -- &amp;quot;X is not/isn&amp;#39;t/ain&amp;#39;t a word&amp;quot;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt; -- &amp;quot;X is my new favorite/favourite/fave word&amp;quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;#39;s the &lt;em&gt;clusterfuck&lt;/em&gt; variants they&amp;#39;ve found:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;clustercoitus&lt;/li&gt;
&lt;li&gt;clustercuss&lt;/li&gt;
&lt;li&gt;clusterduck&lt;/li&gt;
&lt;li&gt;clusterfarf&lt;/li&gt;
&lt;li&gt;clusterfark&lt;/li&gt;
&lt;li&gt;clusterfoops&lt;/li&gt;
&lt;li&gt;clusterfox&lt;/li&gt;
&lt;li&gt;clusterfucked&lt;/li&gt;
&lt;li&gt;clusterfuckery&lt;/li&gt;
&lt;li&gt;clusterfudge&lt;/li&gt;
&lt;li&gt;clustermindfuck&lt;/li&gt;
&lt;li&gt;clusterwhoops&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Manwords</title><link>https://hugovk.dev/blog/2015/manwords/</link><pubDate>Tue, 20 Jan 2015 12:31:58 +0000</pubDate><guid>https://hugovk.dev/blog/2015/manwords/</guid><description>&lt;p&gt;EngLangBlog offered some &lt;a href="http://englishlangsfx.blogspot.com/2015/01/oh-man.html"&gt;man-words&lt;/a&gt;&amp;nbsp;and asked for more.&lt;/p&gt;
&lt;p&gt;Three bots have been collecting words from Twitter since 2013. They've looked for certain sentences and extracted the X.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt; -- "I love/hate the word X"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt; -- "X is not/isn't/ain't a word"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt; -- "X is my new favorite/favourite/fave word"&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In alphabetical order, here's all the words they've found starting with &lt;em&gt;man&lt;/em&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;man&lt;/li&gt;
&lt;li&gt;man's&lt;/li&gt;
&lt;li&gt;manaconda&lt;/li&gt;
&lt;li&gt;manage&lt;/li&gt;
&lt;li&gt;manageable&lt;/li&gt;
&lt;li&gt;management&lt;/li&gt;
&lt;li&gt;manager&lt;/li&gt;
&lt;li&gt;managerial&lt;/li&gt;
&lt;li&gt;managering&lt;/li&gt;
&lt;li&gt;managerish&lt;/li&gt;
&lt;li&gt;managership&lt;/li&gt;
&lt;li&gt;manaita&lt;/li&gt;
&lt;li&gt;manaka&lt;/li&gt;
&lt;li&gt;manana&lt;/li&gt;
&lt;li&gt;manarchism&lt;/li&gt;
&lt;li&gt;manarchist&lt;/li&gt;
&lt;li&gt;manarchists&lt;/li&gt;
&lt;li&gt;manbaby&lt;/li&gt;
&lt;li&gt;manband&lt;/li&gt;
&lt;li&gt;manboat&lt;/li&gt;
&lt;li&gt;manboob&lt;/li&gt;
&lt;li&gt;manbun&lt;/li&gt;
&lt;li&gt;mancake&lt;/li&gt;
&lt;li&gt;mancandy&lt;/li&gt;
&lt;li&gt;mancave&lt;/li&gt;
&lt;li&gt;mancer&lt;/li&gt;
&lt;li&gt;manchacha&lt;/li&gt;
&lt;li&gt;manchester&lt;/li&gt;
&lt;li&gt;manchild&lt;/li&gt;
&lt;li&gt;mand&lt;/li&gt;
&lt;li&gt;mandals&lt;/li&gt;
&lt;li&gt;mandate&lt;/li&gt;
&lt;li&gt;mandated&lt;/li&gt;
&lt;li&gt;mandation&lt;/li&gt;
&lt;li&gt;mandatory&lt;/li&gt;
&lt;li&gt;mandelfie&lt;/li&gt;
&lt;li&gt;mandem&lt;/li&gt;
&lt;li&gt;mandemz&lt;/li&gt;
&lt;li&gt;manderstand&lt;/li&gt;
&lt;li&gt;mandingo&lt;/li&gt;
&lt;li&gt;manditorium&lt;/li&gt;
&lt;li&gt;mandolized&lt;/li&gt;
&lt;li&gt;mandy&lt;/li&gt;
&lt;li&gt;mane&lt;/li&gt;
&lt;li&gt;manegment&lt;/li&gt;
&lt;li&gt;maneurysms&lt;/li&gt;
&lt;li&gt;maneuver&lt;/li&gt;
&lt;li&gt;manfant&lt;/li&gt;
&lt;li&gt;manfetti&lt;/li&gt;
&lt;li&gt;manfidence&lt;/li&gt;
&lt;li&gt;manflu&lt;/li&gt;
&lt;li&gt;manfriend&lt;/li&gt;
&lt;li&gt;mang&lt;/li&gt;
&lt;li&gt;manga&lt;/li&gt;
&lt;li&gt;mangaks&lt;/li&gt;
&lt;li&gt;manganèse&lt;/li&gt;
&lt;li&gt;mangape&lt;/li&gt;
&lt;li&gt;mangela&lt;/li&gt;
&lt;li&gt;mangerines&lt;/li&gt;
&lt;li&gt;mangia&lt;/li&gt;
&lt;li&gt;mangificent&lt;/li&gt;
&lt;li&gt;mangify&lt;/li&gt;
&lt;li&gt;mangina&lt;/li&gt;
&lt;li&gt;mangirrrling&lt;/li&gt;
&lt;li&gt;mangled&lt;/li&gt;
&lt;li&gt;mango&lt;/li&gt;
&lt;li&gt;mangos&lt;/li&gt;
&lt;li&gt;mangosteen&lt;/li&gt;
&lt;li&gt;mangrove&lt;/li&gt;
&lt;li&gt;manguish&lt;/li&gt;
&lt;li&gt;mangy&lt;/li&gt;
&lt;li&gt;mangyari&lt;/li&gt;
&lt;li&gt;manhi&lt;/li&gt;
&lt;li&gt;manhole&lt;/li&gt;
&lt;li&gt;manhood&lt;/li&gt;
&lt;li&gt;manhunt&lt;/li&gt;
&lt;li&gt;mania&lt;/li&gt;
&lt;li&gt;maniac&lt;/li&gt;
&lt;li&gt;maniacal&lt;/li&gt;
&lt;li&gt;maniacally&lt;/li&gt;
&lt;li&gt;manic&lt;/li&gt;
&lt;li&gt;manically&lt;/li&gt;
&lt;li&gt;manicle&lt;/li&gt;
&lt;li&gt;manicorn&lt;/li&gt;
&lt;li&gt;manier&lt;/li&gt;
&lt;li&gt;maniest&lt;/li&gt;
&lt;li&gt;manifest&lt;/li&gt;
&lt;li&gt;manifestate&lt;/li&gt;
&lt;li&gt;manifestating&lt;/li&gt;
&lt;li&gt;manifestation&lt;/li&gt;
&lt;li&gt;manifesto&lt;/li&gt;
&lt;li&gt;manigga&lt;/li&gt;
&lt;li&gt;manikan&lt;/li&gt;
&lt;li&gt;manikin&lt;/li&gt;
&lt;li&gt;manila&lt;/li&gt;
&lt;li&gt;manilla&lt;/li&gt;
&lt;li&gt;manimal&lt;/li&gt;
&lt;li&gt;manimony&lt;/li&gt;
&lt;li&gt;maninism&lt;/li&gt;
&lt;li&gt;manipul8te&lt;/li&gt;
&lt;li&gt;manipulable&lt;/li&gt;
&lt;li&gt;manipulate&lt;/li&gt;
&lt;li&gt;manipulated&lt;/li&gt;
&lt;li&gt;manipulation&lt;/li&gt;
&lt;li&gt;manipulative&lt;/li&gt;
&lt;li&gt;manipulatives&lt;/li&gt;
&lt;li&gt;manipulize&lt;/li&gt;
&lt;li&gt;manish&lt;/li&gt;
&lt;li&gt;manism&lt;/li&gt;
&lt;li&gt;mank&lt;/li&gt;
&lt;li&gt;manke&lt;/li&gt;
&lt;li&gt;mankey&lt;/li&gt;
&lt;li&gt;mankind&lt;/li&gt;
&lt;li&gt;mankles&lt;/li&gt;
&lt;li&gt;manky&lt;/li&gt;
&lt;li&gt;manlet&lt;/li&gt;
&lt;li&gt;manliest&lt;/li&gt;
&lt;li&gt;manlihood&lt;/li&gt;
&lt;li&gt;manlike&lt;/li&gt;
&lt;li&gt;manliner&lt;/li&gt;
&lt;li&gt;manliness&lt;/li&gt;
&lt;li&gt;manly&lt;/li&gt;
&lt;li&gt;manlyer&lt;/li&gt;
&lt;li&gt;manlyness&lt;/li&gt;
&lt;li&gt;mannaman&lt;/li&gt;
&lt;li&gt;mannequin&lt;/li&gt;
&lt;li&gt;manner&lt;/li&gt;
&lt;li&gt;mannerable&lt;/li&gt;
&lt;li&gt;mannerful&lt;/li&gt;
&lt;li&gt;mannerible&lt;/li&gt;
&lt;li&gt;mannerism&lt;/li&gt;
&lt;li&gt;mannerisms&lt;/li&gt;
&lt;li&gt;manners&lt;/li&gt;
&lt;li&gt;mannish&lt;/li&gt;
&lt;li&gt;mannitol&lt;/li&gt;
&lt;li&gt;manniversary&lt;/li&gt;
&lt;li&gt;mannn&lt;/li&gt;
&lt;li&gt;mannnnn&lt;/li&gt;
&lt;li&gt;manny&lt;/li&gt;
&lt;li&gt;manophobe&lt;/li&gt;
&lt;li&gt;manosphere&lt;/li&gt;
&lt;li&gt;manpad&lt;/li&gt;
&lt;li&gt;manpain&lt;/li&gt;
&lt;li&gt;manparts&lt;/li&gt;
&lt;li&gt;manpussy&lt;/li&gt;
&lt;li&gt;manraj&lt;/li&gt;
&lt;li&gt;mans&lt;/li&gt;
&lt;li&gt;manscaping&lt;/li&gt;
&lt;li&gt;manservant&lt;/li&gt;
&lt;li&gt;mansion&lt;/li&gt;
&lt;li&gt;manslaughter&lt;/li&gt;
&lt;li&gt;manslut&lt;/li&gt;
&lt;li&gt;mansome&lt;/li&gt;
&lt;li&gt;mansperience&lt;/li&gt;
&lt;li&gt;mansplain&lt;/li&gt;
&lt;li&gt;mansplained&lt;/li&gt;
&lt;li&gt;mansplaining&lt;/li&gt;
&lt;li&gt;manspread&lt;/li&gt;
&lt;li&gt;manstealer&lt;/li&gt;
&lt;li&gt;manstripper&lt;/li&gt;
&lt;li&gt;manstruating&lt;/li&gt;
&lt;li&gt;mant&lt;/li&gt;
&lt;li&gt;mantastic&lt;/li&gt;
&lt;li&gt;mantecado&lt;/li&gt;
&lt;li&gt;manter&lt;/li&gt;
&lt;li&gt;manther&lt;/li&gt;
&lt;li&gt;manties&lt;/li&gt;
&lt;li&gt;mantique&lt;/li&gt;
&lt;li&gt;mantitties&lt;/li&gt;
&lt;li&gt;mantlers&lt;/li&gt;
&lt;li&gt;mantra&lt;/li&gt;
&lt;li&gt;mantrum&lt;/li&gt;
&lt;li&gt;manufactroversy&lt;/li&gt;
&lt;li&gt;manufactured&lt;/li&gt;
&lt;li&gt;manufacturers&lt;/li&gt;
&lt;li&gt;manureisms&lt;/li&gt;
&lt;li&gt;manveries&lt;/li&gt;
&lt;li&gt;manwhore&lt;/li&gt;
&lt;li&gt;many&lt;/li&gt;
&lt;li&gt;manyer&lt;/li&gt;
&lt;li&gt;manyuchi&lt;/li&gt;
&lt;li&gt;manzelish&lt;/li&gt;
&lt;li&gt;manziel&lt;/li&gt;
&lt;li&gt;manzielish&lt;/li&gt;
&lt;li&gt;manzzzzzz&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;Breakdowns by bot&lt;/h2&gt;
&lt;h3&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3&gt;"man[...]&amp;nbsp;is my favourite/favorite/fave word"&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;man&lt;/li&gt;
&lt;li&gt;manaconda&lt;/li&gt;
&lt;li&gt;manaka&lt;/li&gt;
&lt;li&gt;manarchism&lt;/li&gt;
&lt;li&gt;manarchist&lt;/li&gt;
&lt;li&gt;manboat&lt;/li&gt;
&lt;li&gt;mancake&lt;/li&gt;
&lt;li&gt;mancandy&lt;/li&gt;
&lt;li&gt;mancer&lt;/li&gt;
&lt;li&gt;manchacha&lt;/li&gt;
&lt;li&gt;manchild&lt;/li&gt;
&lt;li&gt;mandals&lt;/li&gt;
&lt;li&gt;mandate&lt;/li&gt;
&lt;li&gt;mandelfie&lt;/li&gt;
&lt;li&gt;mandem&lt;/li&gt;
&lt;li&gt;manderstand&lt;/li&gt;
&lt;li&gt;mandingo&lt;/li&gt;
&lt;li&gt;mandolized&lt;/li&gt;
&lt;li&gt;mandy&lt;/li&gt;
&lt;li&gt;mane&lt;/li&gt;
&lt;li&gt;maneurysms&lt;/li&gt;
&lt;li&gt;manfant&lt;/li&gt;
&lt;li&gt;manfetti&lt;/li&gt;
&lt;li&gt;mang&lt;/li&gt;
&lt;li&gt;manga&lt;/li&gt;
&lt;li&gt;manganèse&lt;/li&gt;
&lt;li&gt;mangela&lt;/li&gt;
&lt;li&gt;mangerines&lt;/li&gt;
&lt;li&gt;mangia&lt;/li&gt;
&lt;li&gt;mangify&lt;/li&gt;
&lt;li&gt;mangina&lt;/li&gt;
&lt;li&gt;mangirrrling&lt;/li&gt;
&lt;li&gt;mango&lt;/li&gt;
&lt;li&gt;mangosteen&lt;/li&gt;
&lt;li&gt;manguish&lt;/li&gt;
&lt;li&gt;manhole&lt;/li&gt;
&lt;li&gt;maniac&lt;/li&gt;
&lt;li&gt;maniacal&lt;/li&gt;
&lt;li&gt;manic&lt;/li&gt;
&lt;li&gt;manicorn&lt;/li&gt;
&lt;li&gt;manifest&lt;/li&gt;
&lt;li&gt;manifesto&lt;/li&gt;
&lt;li&gt;manigga&lt;/li&gt;
&lt;li&gt;manimal&lt;/li&gt;
&lt;li&gt;manipulative&lt;/li&gt;
&lt;li&gt;mankey&lt;/li&gt;
&lt;li&gt;manly&lt;/li&gt;
&lt;li&gt;mannaman&lt;/li&gt;
&lt;li&gt;manniversary&lt;/li&gt;
&lt;li&gt;mannn&lt;/li&gt;
&lt;li&gt;mannnnn&lt;/li&gt;
&lt;li&gt;manophobe&lt;/li&gt;
&lt;li&gt;manosphere&lt;/li&gt;
&lt;li&gt;manparts&lt;/li&gt;
&lt;li&gt;manraj&lt;/li&gt;
&lt;li&gt;mans&lt;/li&gt;
&lt;li&gt;manscaping&lt;/li&gt;
&lt;li&gt;mansperience&lt;/li&gt;
&lt;li&gt;mansplain&lt;/li&gt;
&lt;li&gt;mansplained&lt;/li&gt;
&lt;li&gt;mansplaining&lt;/li&gt;
&lt;li&gt;manspread&lt;/li&gt;
&lt;li&gt;manstealer&lt;/li&gt;
&lt;li&gt;manstripper&lt;/li&gt;
&lt;li&gt;manstruating&lt;/li&gt;
&lt;li&gt;mantastic&lt;/li&gt;
&lt;li&gt;manter&lt;/li&gt;
&lt;li&gt;manther&lt;/li&gt;
&lt;li&gt;manties&lt;/li&gt;
&lt;li&gt;mantique&lt;/li&gt;
&lt;li&gt;mantrum&lt;/li&gt;
&lt;li&gt;manufactroversy&lt;/li&gt;
&lt;li&gt;manureisms&lt;/li&gt;
&lt;li&gt;manveries&lt;/li&gt;
&lt;li&gt;many&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;"I love the word man[...]"&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;man&lt;/li&gt;
&lt;li&gt;manarchists&lt;/li&gt;
&lt;li&gt;manbaby&lt;/li&gt;
&lt;li&gt;mancake&lt;/li&gt;
&lt;li&gt;mandate&lt;/li&gt;
&lt;li&gt;mandatory&lt;/li&gt;
&lt;li&gt;mandingo&lt;/li&gt;
&lt;li&gt;mane&lt;/li&gt;
&lt;li&gt;mang&lt;/li&gt;
&lt;li&gt;mangina&lt;/li&gt;
&lt;li&gt;mango&lt;/li&gt;
&lt;li&gt;mangrove&lt;/li&gt;
&lt;li&gt;mangy&lt;/li&gt;
&lt;li&gt;mangyari&lt;/li&gt;
&lt;li&gt;manhi&lt;/li&gt;
&lt;li&gt;manhole&lt;/li&gt;
&lt;li&gt;manhunt&lt;/li&gt;
&lt;li&gt;maniac&lt;/li&gt;
&lt;li&gt;maniacal&lt;/li&gt;
&lt;li&gt;maniacally&lt;/li&gt;
&lt;li&gt;manifest&lt;/li&gt;
&lt;li&gt;manifesto&lt;/li&gt;
&lt;li&gt;manila&lt;/li&gt;
&lt;li&gt;manilla&lt;/li&gt;
&lt;li&gt;manipul8te&lt;/li&gt;
&lt;li&gt;manipulate&lt;/li&gt;
&lt;li&gt;manipulated&lt;/li&gt;
&lt;li&gt;manky&lt;/li&gt;
&lt;li&gt;manlet&lt;/li&gt;
&lt;li&gt;mannequin&lt;/li&gt;
&lt;li&gt;manner&lt;/li&gt;
&lt;li&gt;mannnnn&lt;/li&gt;
&lt;li&gt;manpussy&lt;/li&gt;
&lt;li&gt;manservant&lt;/li&gt;
&lt;li&gt;manslaughter&lt;/li&gt;
&lt;li&gt;mansplain&lt;/li&gt;
&lt;li&gt;mansplained&lt;/li&gt;
&lt;li&gt;mantecado&lt;/li&gt;
&lt;li&gt;mantitties&lt;/li&gt;
&lt;li&gt;mantra&lt;/li&gt;
&lt;li&gt;manyer&lt;/li&gt;
&lt;li&gt;manyuchi&lt;/li&gt;
&lt;li&gt;manzzzzzz&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;"I hate the word man[...]"&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;man&lt;/li&gt;
&lt;li&gt;manage&lt;/li&gt;
&lt;li&gt;management&lt;/li&gt;
&lt;li&gt;manager&lt;/li&gt;
&lt;li&gt;manband&lt;/li&gt;
&lt;li&gt;manboob&lt;/li&gt;
&lt;li&gt;manbun&lt;/li&gt;
&lt;li&gt;mancave&lt;/li&gt;
&lt;li&gt;manchester&lt;/li&gt;
&lt;li&gt;manchild&lt;/li&gt;
&lt;li&gt;mandals&lt;/li&gt;
&lt;li&gt;mandate&lt;/li&gt;
&lt;li&gt;mandated&lt;/li&gt;
&lt;li&gt;mandatory&lt;/li&gt;
&lt;li&gt;mandem&lt;/li&gt;
&lt;li&gt;mandemz&lt;/li&gt;
&lt;li&gt;mane&lt;/li&gt;
&lt;li&gt;manflu&lt;/li&gt;
&lt;li&gt;manfriend&lt;/li&gt;
&lt;li&gt;mang&lt;/li&gt;
&lt;li&gt;manga&lt;/li&gt;
&lt;li&gt;mangled&lt;/li&gt;
&lt;li&gt;mango&lt;/li&gt;
&lt;li&gt;manhole&lt;/li&gt;
&lt;li&gt;manhunt&lt;/li&gt;
&lt;li&gt;mania&lt;/li&gt;
&lt;li&gt;manifest&lt;/li&gt;
&lt;li&gt;manifestation&lt;/li&gt;
&lt;li&gt;manifesto&lt;/li&gt;
&lt;li&gt;manikin&lt;/li&gt;
&lt;li&gt;manila&lt;/li&gt;
&lt;li&gt;manilla&lt;/li&gt;
&lt;li&gt;manipulate&lt;/li&gt;
&lt;li&gt;mankind&lt;/li&gt;
&lt;li&gt;manky&lt;/li&gt;
&lt;li&gt;manliner&lt;/li&gt;
&lt;li&gt;manly&lt;/li&gt;
&lt;li&gt;manners&lt;/li&gt;
&lt;li&gt;mannish&lt;/li&gt;
&lt;li&gt;mannitol&lt;/li&gt;
&lt;li&gt;manpain&lt;/li&gt;
&lt;li&gt;manscaping&lt;/li&gt;
&lt;li&gt;mansion&lt;/li&gt;
&lt;li&gt;manslaughter&lt;/li&gt;
&lt;li&gt;mansplain&lt;/li&gt;
&lt;li&gt;mansplaining&lt;/li&gt;
&lt;li&gt;manufactured&lt;/li&gt;
&lt;li&gt;manufacturers&lt;/li&gt;
&lt;li&gt;manwhore&lt;/li&gt;
&lt;li&gt;many&lt;/li&gt;
&lt;li&gt;manziel&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3&gt;"man[...] isn't/is not/ain't a word"&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;man&lt;/li&gt;
&lt;li&gt;man's&lt;/li&gt;
&lt;li&gt;manageable&lt;/li&gt;
&lt;li&gt;management&lt;/li&gt;
&lt;li&gt;manager&lt;/li&gt;
&lt;li&gt;managerial&lt;/li&gt;
&lt;li&gt;managering&lt;/li&gt;
&lt;li&gt;managerish&lt;/li&gt;
&lt;li&gt;managership&lt;/li&gt;
&lt;li&gt;manaita&lt;/li&gt;
&lt;li&gt;manana&lt;/li&gt;
&lt;li&gt;manband&lt;/li&gt;
&lt;li&gt;manbun&lt;/li&gt;
&lt;li&gt;mancave&lt;/li&gt;
&lt;li&gt;mand&lt;/li&gt;
&lt;li&gt;mandate&lt;/li&gt;
&lt;li&gt;mandation&lt;/li&gt;
&lt;li&gt;manditorium&lt;/li&gt;
&lt;li&gt;mane&lt;/li&gt;
&lt;li&gt;manegment&lt;/li&gt;
&lt;li&gt;maneuver&lt;/li&gt;
&lt;li&gt;manfidence&lt;/li&gt;
&lt;li&gt;mang&lt;/li&gt;
&lt;li&gt;manga&lt;/li&gt;
&lt;li&gt;mangaks&lt;/li&gt;
&lt;li&gt;mangape&lt;/li&gt;
&lt;li&gt;mangificent&lt;/li&gt;
&lt;li&gt;mangina&lt;/li&gt;
&lt;li&gt;mango&lt;/li&gt;
&lt;li&gt;mangos&lt;/li&gt;
&lt;li&gt;manhood&lt;/li&gt;
&lt;li&gt;maniac&lt;/li&gt;
&lt;li&gt;manic&lt;/li&gt;
&lt;li&gt;manically&lt;/li&gt;
&lt;li&gt;manicle&lt;/li&gt;
&lt;li&gt;manier&lt;/li&gt;
&lt;li&gt;maniest&lt;/li&gt;
&lt;li&gt;manifestate&lt;/li&gt;
&lt;li&gt;manifestating&lt;/li&gt;
&lt;li&gt;manikan&lt;/li&gt;
&lt;li&gt;manimony&lt;/li&gt;
&lt;li&gt;maninism&lt;/li&gt;
&lt;li&gt;manipulable&lt;/li&gt;
&lt;li&gt;manipulated&lt;/li&gt;
&lt;li&gt;manipulation&lt;/li&gt;
&lt;li&gt;manipulatives&lt;/li&gt;
&lt;li&gt;manipulize&lt;/li&gt;
&lt;li&gt;manish&lt;/li&gt;
&lt;li&gt;manism&lt;/li&gt;
&lt;li&gt;mank&lt;/li&gt;
&lt;li&gt;manke&lt;/li&gt;
&lt;li&gt;mankles&lt;/li&gt;
&lt;li&gt;manky&lt;/li&gt;
&lt;li&gt;manliest&lt;/li&gt;
&lt;li&gt;manlihood&lt;/li&gt;
&lt;li&gt;manlike&lt;/li&gt;
&lt;li&gt;manliness&lt;/li&gt;
&lt;li&gt;manly&lt;/li&gt;
&lt;li&gt;manlyer&lt;/li&gt;
&lt;li&gt;manlyness&lt;/li&gt;
&lt;li&gt;mannerable&lt;/li&gt;
&lt;li&gt;mannerful&lt;/li&gt;
&lt;li&gt;mannerible&lt;/li&gt;
&lt;li&gt;mannerism&lt;/li&gt;
&lt;li&gt;mannerisms&lt;/li&gt;
&lt;li&gt;mannish&lt;/li&gt;
&lt;li&gt;manny&lt;/li&gt;
&lt;li&gt;manophobe&lt;/li&gt;
&lt;li&gt;manosphere&lt;/li&gt;
&lt;li&gt;manpad&lt;/li&gt;
&lt;li&gt;mans&lt;/li&gt;
&lt;li&gt;manslut&lt;/li&gt;
&lt;li&gt;mansome&lt;/li&gt;
&lt;li&gt;mansplain&lt;/li&gt;
&lt;li&gt;mansplained&lt;/li&gt;
&lt;li&gt;mansplaining&lt;/li&gt;
&lt;li&gt;mant&lt;/li&gt;
&lt;li&gt;mantlers&lt;/li&gt;
&lt;li&gt;manwhore&lt;/li&gt;
&lt;li&gt;many&lt;/li&gt;
&lt;li&gt;manzelish&lt;/li&gt;
&lt;li&gt;manzielish&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Words of the year from Twitter, 2014</title><link>https://hugovk.dev/blog/2015/twitter-woty-2014/</link><pubDate>Tue, 06 Jan 2015 20:19:48 +0000</pubDate><guid>https://hugovk.dev/blog/2015/twitter-woty-2014/</guid><description>&lt;h2&gt;Three bots have been collecting words from Twitter for the past year.&lt;/h2&gt;
&lt;p&gt;They've looked for certain sentences and extracted the X.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt; -- "I love/hate the word X"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt; -- "X is not/isn't/ain't a word"&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt; -- "X is my new favorite/favourite/fave word"&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Combining and comparing the logs of all three, let's see what the top words of 2014 are.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Combined output&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;2014 total: 333,449&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;bae (25,689)&lt;/li&gt;
&lt;li&gt;thot (7,787)&lt;/li&gt;
&lt;li&gt;no (6,888)&lt;/li&gt;
&lt;li&gt;mines (5,038)&lt;/li&gt;
&lt;li&gt;love (3,662)&lt;/li&gt;
&lt;li&gt;moist (3,305)&lt;/li&gt;
&lt;li&gt;impossible (3,092)&lt;/li&gt;
&lt;li&gt;ain't (2,960)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;loved (2,954)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (2,646)&lt;/li&gt;
&lt;li&gt;conversate (2,175)&lt;/li&gt;
&lt;li&gt;homophobia (2,064)&lt;/li&gt;
&lt;li&gt;loyalty (1,982)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;soon (1,767)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;panties (1,741)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sorry (1,617)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;selfie (1,613)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;bitch (1,585)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;justice (1,542)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;babe (1,531)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;lovihatibot&lt;/th&gt;
&lt;th&gt;nixibot&lt;/th&gt;
&lt;th&gt;favibot&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: center;"&gt;2014 total: 192,388&lt;/td&gt;
&lt;td style="text-align: center;"&gt;2014 total: 118,095&lt;/td&gt;
&lt;td style="text-align: center;"&gt;2014 total: 22,974&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;bae (23,955)&lt;/li&gt;
&lt;li&gt;thot (6,855)&lt;/li&gt;
&lt;li&gt;no (6,469)&lt;/li&gt;
&lt;li&gt;moist (3,276)&lt;/li&gt;
&lt;li&gt;cunt (2,487)&lt;/li&gt;
&lt;li&gt;homophobia (2,053)&lt;/li&gt;
&lt;li&gt;panties (1,738)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;soon (1,728)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;selfie (1,538)&lt;/li&gt;
&lt;li&gt;babe (1,494)&lt;/li&gt;
&lt;li&gt;bitch (1,454)&lt;/li&gt;
&lt;li&gt;sorry (1,454)&lt;/li&gt;
&lt;li&gt;sexy (1,318)&lt;/li&gt;
&lt;li&gt;fuck (1,175)&lt;/li&gt;
&lt;li&gt;swag (1,139)&lt;/li&gt;
&lt;li&gt;love (1,098)&lt;/li&gt;
&lt;li&gt;pussy (1,065)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bruh (1,061)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fleek (1,008)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;baby (860)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (5,002)&lt;/li&gt;
&lt;li&gt;impossible (3,031)&lt;/li&gt;
&lt;li&gt;loved (2,942)&lt;/li&gt;
&lt;li&gt;ain't (2,812)&lt;/li&gt;
&lt;li&gt;love (2,560)&lt;/li&gt;
&lt;li&gt;conversate (2,154)&lt;/li&gt;
&lt;li&gt;loyalty (1,907)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bae (1,585)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;justice (1,536)&lt;/li&gt;
&lt;li&gt;funner (1,496)&lt;/li&gt;
&lt;li&gt;irregardless (1,209)&lt;/li&gt;
&lt;li&gt;forever (1,122)&lt;/li&gt;
&lt;li&gt;worser (994)&lt;/li&gt;
&lt;li&gt;anyways (983)&lt;/li&gt;
&lt;li&gt;stupider (856)&lt;/li&gt;
&lt;li&gt;bestest (799)&lt;/li&gt;
&lt;li&gt;alot (753)&lt;/li&gt;
&lt;li&gt;thot (711)&lt;/li&gt;
&lt;li&gt;finna (707)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;marriage (683)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;thot (224)&lt;/li&gt;
&lt;li&gt;no (194)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bruh (176)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;bae (151)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;twizzle (130)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuckboy (121)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (115)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hella (106)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;rad (92)&lt;/li&gt;
&lt;li&gt;twat (84)&lt;/li&gt;
&lt;li&gt;sassy (83)&lt;/li&gt;
&lt;li&gt;bitch (74)&lt;/li&gt;
&lt;li&gt;fuck (64)&lt;/li&gt;
&lt;li&gt;irrelevant (64)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;slunt (58)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;wow (57)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dingus (52)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;slay (49)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;swanky (49)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;yikes (47)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here's top 10 charts for each phrase from each bot.&amp;nbsp;&lt;/p&gt;
&lt;h2&gt;@lovihatbot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;I love the word X&lt;/th&gt;
&lt;th&gt;I hate the word X&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;cunt (1,253)&lt;/li&gt;
&lt;li&gt;fuck (1,075)&lt;/li&gt;
&lt;li&gt;bae (1,029)&lt;/li&gt;
&lt;li&gt;thot (700)&lt;/li&gt;
&lt;li&gt;bitch (661)&lt;/li&gt;
&lt;li&gt;babe (497)&lt;/li&gt;
&lt;li&gt;woman (323)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;moist (309)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;delicious (295)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;rad (269)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;bae (22,926)&lt;/li&gt;
&lt;li&gt;no (6,274)&lt;/li&gt;
&lt;li&gt;thot (6,155)&lt;/li&gt;
&lt;li&gt;moist (2,967)&lt;/li&gt;
&lt;li&gt;homophobia (2,052)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;soon (1,700)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;panties (1,671)&lt;/li&gt;
&lt;li&gt;selfie (1,506)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sorry (1,446)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;sexy (1,281)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@nixibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is not a word&lt;/th&gt;
&lt;th&gt;X isn't a word&lt;/th&gt;
&lt;th&gt;X ain't a word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (3,174)&lt;/li&gt;
&lt;li&gt;impossible (2,769)&lt;/li&gt;
&lt;li&gt;love (1,554)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;loved (1,517)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;conversate (1,395)&lt;/li&gt;
&lt;li&gt;loyalty (1,208)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bae (1,127)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;forever (1,078)&lt;/li&gt;
&lt;li&gt;irregardless (922)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;anyways (602)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;mines (1,745)&lt;/li&gt;
&lt;li&gt;justice (1,529)&lt;/li&gt;
&lt;li&gt;loved (1,425)&lt;/li&gt;
&lt;li&gt;ain't (1,100)&lt;/li&gt;
&lt;li&gt;funner (888)&lt;/li&gt;
&lt;li&gt;love (878)&lt;/li&gt;
&lt;li&gt;conversate (715)&lt;/li&gt;
&lt;li&gt;stupider (589)&lt;/li&gt;
&lt;li&gt;bestest (538)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bae (443)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;ain't (1,131)&lt;/li&gt;
&lt;li&gt;loyalty (380)&lt;/li&gt;
&lt;li&gt;ignorantest (314)&lt;/li&gt;
&lt;li&gt;love (128)&lt;/li&gt;
&lt;li&gt;mines (83)&lt;/li&gt;
&lt;li&gt;impossible (75)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;perfect (54)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;worser (45)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;just (44)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;conversate (44)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;@favibot&lt;/h2&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;th&gt;X is my new favorite word&lt;/th&gt;
&lt;th&gt;X is my new favourite word&lt;/th&gt;
&lt;th&gt;X is my new fave word&lt;/th&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;thot (187)&lt;/li&gt;
&lt;li&gt;no (177)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bruh (145)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;twizzle (123)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bae (111)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuckboy (96)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (82)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hella (79)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;rad (65)&lt;/li&gt;
&lt;li&gt;bitch (61)&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;sassy (45)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bae (30)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;cunt (30)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;thot (26)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;quiche (23)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bruh (21)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rad (20)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hella (20)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;twat (18)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bitchachos (17)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;thot (11)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bae (10)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bruh (10)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fuckboy (9)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rad (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;sassy (7)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;bitchachos (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;puta (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hella (7)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;literally (6)&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Change from 2013&lt;/h2&gt;
&lt;p&gt;Let's see how mentions have changed for some of 2013's words.&lt;/p&gt;
&lt;p&gt;People talking about words ‒&amp;nbsp;especially new favourite &amp;nbsp;words, or loved words, or hated words, or words they've decided don't exist ‒&amp;nbsp;may be a good indicator of brand new words or at least words with new currency.&amp;nbsp;Both increases and decreases may reflect an underlying change in use, and decreases may reflect an acceptance of the words.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bae &lt;/em&gt;(babe)&amp;nbsp;emerged in April 2013, peaked at&amp;nbsp;3,880 in&amp;nbsp;May 2014 and tailed off to 1,602 by December. This is the stronger of the two. Neal Whitman&amp;nbsp;&lt;a href="http://www.vocabulary.com/articles/dictionary/bae-watch-the-ascent-of-a-new-pet-name/" target="_self"&gt;charts its ascent&lt;/a&gt;&amp;nbsp;and its use&amp;nbsp;&lt;a href="http://literalminded.wordpress.com/2015/01/05/getting-on-the-bae-train/" target="_self"&gt;throughout 2014&lt;/a&gt;&amp;nbsp;whilst James Hamblin&amp;nbsp;&lt;a href="http://www.theatlantic.com/entertainment/archive/2014/12/the-lamentable-death-of-bae/384086/" target="_self"&gt;laments its death&lt;/a&gt;&amp;nbsp;since&amp;nbsp;&lt;a href="https://twitter.com/brandssayingbae" target="_self"&gt;brands have adopted it&lt;/a&gt;.&lt;/p&gt;
&lt;div&gt;Similarly,&lt;em&gt; t&lt;/em&gt;&lt;em&gt;hot&lt;/em&gt;&amp;nbsp;("that hoe over there") emerged in March 2013 and grew steadily to a peak of 1,400 uses in March 2014 and dropped off to 162 by December.&amp;nbsp;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;&lt;em&gt;Selfie&lt;/em&gt;&lt;/em&gt;&amp;nbsp;(a shared photo taken with one's own phone) had a couple of mentions in April 2013, and peaked in March 2014 with 373, ending the year with 86 in December.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Ratchet&lt;/em&gt;&amp;nbsp;(a diva) peaked with 130 in March 2014, dropping to 20 by December.&lt;/p&gt;
&lt;h2&gt;New in 2014&lt;/h2&gt;
&lt;p&gt;Here's a few new to these charts.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Fleek&lt;/em&gt;, or more fully&amp;nbsp;&lt;em&gt;on fleek&lt;/em&gt;, means&amp;nbsp;on point, on the mark, stylish, amazing or impeccable. First mentioned in August 2014, it's still riding the peak. First used on&amp;nbsp;Vine by Peaches Monroe in June 2014, it was later popularised by Ariana Grande, who sang the Vine on MTV in August.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Bruh&amp;nbsp;&lt;/em&gt;(bro, brother), not a new word but with just a few mentions throughout 2013, picked up in March 2014, and peaked in June/July 2014. Use is likely&amp;nbsp;spurred on by its use in a&amp;nbsp;&lt;a href="http://www.brobible.com/entertainment/article/why-bruh-movement-taking-over-vine/" target="_self"&gt;Vine clip&lt;/a&gt;&amp;nbsp;from May 2014 and in copied Vines:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;CallHimBzar took the clip, and a friend of his named Headgraphix, added a disappointed “bruh” as Farmer collapsed. The “bruh” is meant to imply, “bruh, you’re a 6’7, 220-lb guy, and you beat the shit out of your girlfriend, quit acting like a bitch.” The Vine quickly blew up, and after seeing all the excitement surrounding the Vine, Headgraphix and CallHimBzar started the #BruhMovement, encouraging others to make their own Bruh Vines. And Bruh Vine they did… to the &amp;nbsp;tune of&amp;nbsp;&lt;em&gt;thousands&lt;/em&gt;&amp;nbsp;of Bruh vines.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Likewise,&lt;em&gt; fuckboy&lt;/em&gt; isn't new, but has seen increasing mentions. &lt;a href="http://knowyourmeme.com/memes/fuckboy" target="_self"&gt;Know Your Meme&lt;/a&gt; says:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Fuckboy” used as a pejorative toward men who are perceived as oversexed or disrespectful toward women.&amp;nbsp;On Tumblr, the term has also spawned a derivative phrase “en garde (English: on your guard), fuckboy," which is often accompanied with images of skeletons in a similar vein to 2Spooky.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Intensifier &lt;em&gt;hella&lt;/em&gt;&amp;nbsp;saw some use in 2013, but it increased in 2014 peaking between February and August.&lt;/p&gt;
&lt;p&gt;Finally, &lt;em&gt;twizzle &lt;/em&gt;was a one-off coinciding with the Sochi 2014 Winter Olympics. Virtually every mention was in February 2014 nearly all as a "favorite word" (with US spelling).&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href="http://fansided.com/2014/02/17/sochi-olympics-ice-dancing-twizzle/" target="_self"&gt;FanSided&lt;/a&gt; confirms the US connection to the Olympics:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you have been watching figure skating throughout the 2014 Winter Olympics, it is impossible not to notice the term “twizzle” that is constantly being dropped by Tara Lipinksi and Johnny Weir throughout the Sochi Games.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;2015&lt;/h2&gt;
&lt;p&gt;For words causing a reaction in 2015, follow&amp;nbsp;&lt;a href="https://twitter.com/lovihatibot" target="_self"&gt;@lovihatibot&lt;/a&gt;,&amp;nbsp;&lt;a href="https://twitter.com/nixibot" target="_self"&gt;@nixibot&lt;/a&gt;&amp;nbsp;and&amp;nbsp;&lt;a href="https://twitter.com/favibot" target="_self"&gt;@favibot&lt;/a&gt;&amp;nbsp;on Twitter.&lt;/p&gt;</description></item><item><title>Twitter's new favourite words</title><link>https://hugovk.dev/blog/2013/twitters-new-favourite-words/</link><pubDate>Fri, 25 Oct 2013 10:22:54 +0000</pubDate><guid>https://hugovk.dev/blog/2013/twitters-new-favourite-words/</guid><description>&lt;p&gt;I wrote a Python script that searches Twitter for tweets containing &amp;ldquo;[X] is my new
favourite word&amp;rdquo;. It then takes all those new favourite words, logs them, lowercases
them, and adds them to a list of words on Wordnik. And, from this week, it also tweets
them and makes word clouds.&lt;/p&gt;
&lt;p&gt;It also does the same for &amp;ldquo;[X] is my new favorite word&amp;rdquo; and &amp;ldquo;[X] is my new fave word&amp;rdquo; so
you can see some geographic variation.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s been going six months since 24th February 2013, runs nearly every day, and has
found some 18,360 new favourite words.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s top tens for the whole set and each subset.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/hugovk/10472408163/" title="Twitter: [X] is my new favourite word + X is my new favorite word + X is my new fave word by hugovk, on Flickr"&gt;&lt;img alt="Twitter: [X] is my new favourite word + X is my new favorite word + X is my new fave word" height="281" src="https://farm6.staticflickr.com/5500/10472408163_0dd4f504f1.jpg" width="500" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The whole data set contains 18,360 words and 10,018 unique words.&lt;/p&gt;
&lt;p&gt;The top 10 words are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;no (151)&lt;/li&gt;
&lt;li&gt;cunt (120)&lt;/li&gt;
&lt;li&gt;sassy (99)&lt;/li&gt;
&lt;li&gt;bitch (95)&lt;/li&gt;
&lt;li&gt;ratchet (87)&lt;/li&gt;
&lt;li&gt;fab (86)&lt;/li&gt;
&lt;li&gt;twat (73)&lt;/li&gt;
&lt;li&gt;cheeky (72)&lt;/li&gt;
&lt;li&gt;faggot (66)&lt;/li&gt;
&lt;li&gt;fuck (62)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/hugovk/10472228714/" title="Twitter: [X] is my new favorite word by hugovk, on Flickr"&gt;&lt;img alt="Twitter: [X] is my new favorite word" height="281" src="https://farm4.staticflickr.com/3819/10472228714_db154ae9f3.jpg" width="500" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;New favorite words&amp;rdquo; contains 10,299 words and 5,964 unique words.&lt;/p&gt;
&lt;p&gt;The top 10 words are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;no (129)&lt;/li&gt;
&lt;li&gt;cunt (75)&lt;/li&gt;
&lt;li&gt;bitch (73)&lt;/li&gt;
&lt;li&gt;ratchet (55)&lt;/li&gt;
&lt;li&gt;thot (53)&lt;/li&gt;
&lt;li&gt;fuck (47)&lt;/li&gt;
&lt;li&gt;twat (41)&lt;/li&gt;
&lt;li&gt;sassy (38)&lt;/li&gt;
&lt;li&gt;fuckery (36)&lt;/li&gt;
&lt;li&gt;whatever (35)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/hugovk/10472230196/" title="Twitter: [X] is my new favourite word by hugovk, on Flickr"&gt;&lt;img alt="Twitter: [X] is my new favourite word" height="281" src="https://farm6.staticflickr.com/5545/10472230196_f9d192c163.jpg" width="500" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;New favourite words&amp;rdquo; contains 6,623 words and 4,505 unique words.&lt;/p&gt;
&lt;p&gt;The top 10 words are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;fab (50)&lt;/li&gt;
&lt;li&gt;sassy (50)&lt;/li&gt;
&lt;li&gt;cunt (41)&lt;/li&gt;
&lt;li&gt;cretin (36)&lt;/li&gt;
&lt;li&gt;cheeky (34)&lt;/li&gt;
&lt;li&gt;faggot (33)&lt;/li&gt;
&lt;li&gt;fabulous (32)&lt;/li&gt;
&lt;li&gt;twat (29)&lt;/li&gt;
&lt;li&gt;ratchet (24)&lt;/li&gt;
&lt;li&gt;serendipity (20)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;a href="https://www.flickr.com/photos/hugovk/10472211615/" title="Twitter: [X] is my new fave word by hugovk, on Flickr"&gt;&lt;img alt="Twitter: [X] is my new fave word" height="281" src="https://farm8.staticflickr.com/7334/10472211615_b71fd6e6f1.jpg" width="500" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;New fave words&amp;rdquo; contains 1,436 words and 1,162 unique words.&lt;/p&gt;
&lt;p&gt;The top 10 words are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;fab (19)&lt;/li&gt;
&lt;li&gt;sassy (11)&lt;/li&gt;
&lt;li&gt;ratchet (8)&lt;/li&gt;
&lt;li&gt;cheeky (7)&lt;/li&gt;
&lt;li&gt;swaggot (7)&lt;/li&gt;
&lt;li&gt;cretin (7)&lt;/li&gt;
&lt;li&gt;coont (6)&lt;/li&gt;
&lt;li&gt;bastard (6)&lt;/li&gt;
&lt;li&gt;dope (6)&lt;/li&gt;
&lt;li&gt;dude (5)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;What does this tell us? I don&amp;rsquo;t know, but whilst the top ten contains lots of swearing,
around half (or about 2/3 for favourites) of each list contains unique words. Browsing
the lists shows lots of neologisms (and neoswearisms).&lt;/p&gt;
&lt;p&gt;The word lists on Wordnik:&lt;br&gt;&lt;a href="https://www.wordnik.com/lists/twitter-favourites"&gt;https://www.wordnik.com/lists/twitter-favourites&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.wordnik.com/lists/twitter-favorites"&gt;https://www.wordnik.com/lists/twitter-favorites&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.wordnik.com/lists/twitter-faves"&gt;https://www.wordnik.com/lists/twitter-faves&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Follow along:&lt;br&gt;&lt;a href="https://twitter.com/favibot"&gt;https://twitter.com/favibot&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;See word clouds:&lt;br&gt;&lt;a href="https://www.flickr.com/photos/hugovk/sets/72157636928894765/"&gt;https://www.flickr.com/photos/hugovk/sets/72157636928894765/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;See the script:&lt;br&gt;
&lt;a href="https://github.com/hugovk/word-tools/blob/master/new_favourite_words.py"&gt;https://github.com/hugovk/word-tools/blob/master/new_favourite_words.py&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Inspired by:&lt;br&gt;&lt;a href="https://www.wordnik.com/lists/outcasts"&gt;https://www.wordnik.com/lists/outcasts&lt;/a&gt;&lt;/p&gt;</description></item><item><title>How to toggle playing/pausing of Spotify using the Pause key in Ubuntu</title><link>https://hugovk.dev/blog/2012/how-to-toggle-playingpausing-of-spotify-using-the-pause-key-in-ubuntu/</link><pubDate>Fri, 13 Jan 2012 13:05:26 +0000</pubDate><guid>https://hugovk.dev/blog/2012/how-to-toggle-playingpausing-of-spotify-using-the-pause-key-in-ubuntu/</guid><description>&lt;p&gt;According to
&lt;a href="https://web.archive.org/web/20120509091435/http://www.mabishu.com/blog/2010/11/15/playing-with-d-bus-interface-of-spotify-for-linux/"&gt;Mabishu&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After a lot of requests from Linux users, Spotify developers have integrated D-Bus
support in version 0.4.8.282. So, what this means is simply and awesome! Now
Linux developers could use this programmatic interface to interact with Spotify from
other apps.&lt;/p&gt;&lt;/p&gt;
&lt;p&gt;In other words, now is quite simple to send «play», «pause», «move next/previous song»
events to Spotify and with this get Spotify fully integrate into our desktop.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To toggle playing and pausing from the terminal, run:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To hook it to the Pause/Break keyboard key:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Select System -&amp;gt; Preferences -&amp;gt; Keyboard Shortcuts&lt;/li&gt;
&lt;li&gt;Click Add&lt;/li&gt;
&lt;li&gt;Enter any Name: Play or pause Spotify&lt;/li&gt;
&lt;li&gt;Paste in the Command:
&lt;code&gt;dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Click Apply&lt;/li&gt;
&lt;li&gt;Click Disabled on the right so it changes to New Shortcut&amp;hellip;&lt;/li&gt;
&lt;li&gt;Press the Pause/Break key&lt;/li&gt;
&lt;li&gt;Click Close&lt;/li&gt;
&lt;li&gt;Listen to music in Spotify and press the Pause/Break key to pause or play the music.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See the
&lt;a href="http://www.mabishu.com/blog/2010/11/15/playing-with-d-bus-interface-of-spotify-for-linux/"&gt;Mabishu&lt;/a&gt;
post to see how to check the other commands you can hook up to other keys.&lt;/p&gt;
&lt;p&gt;(Tested with Ubuntu 10.10 and
&lt;a href="https://web.archive.org/web/20120105155314/https://www.spotify.com/fi/download/previews/"&gt;Spotify&lt;/a&gt;
for Linux preview 0.6.291)&lt;/p&gt;</description></item><item><title>Symbian errors, an illustrated catalogue of errors</title><link>https://hugovk.dev/blog/2009/symbian-errors-an-illustrated-guide/</link><pubDate>Thu, 05 Feb 2009 12:14:33 +0000</pubDate><guid>https://hugovk.dev/blog/2009/symbian-errors-an-illustrated-guide/</guid><description>&lt;style type="text/css"&gt;
table#catalogue td { vertical-align: middle !important; }
table#catalogue td img { margin: 0 !important; }
&lt;/style&gt;
&lt;table id="catalogue"&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/27828598/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/23/27828598_0d6140c6b6_s.jpg" width="75" height="75" alt="Nakki" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrNone&gt;KErrNone&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;0&lt;/td&gt;&lt;td&gt; &lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/173755166/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/58/173755166_c5dbeb5855_s.jpg" width="75" height="75" alt="404 not found?" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrNotFound&gt;KErrNotFound&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-1&lt;/td&gt;&lt;td&gt;&lt;i&gt;Unable to find the specified object&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/223190002/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/69/223190002_b99af8d9f5_s.jpg" width="75" height="75" alt="&amp;quot;Your set-top box is experiencing an issue.&amp;quot;" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrGeneral&gt;KErrGeneral&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-2&lt;/td&gt;&lt;td&gt;&lt;i&gt;General (unspecified) error&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/2649887975/" title="Cancelled! by hugovk, on Flickr"&gt;&lt;img src="https://farm4.static.flickr.com/3277/2649887975_33cef6cd5d_s.jpg" width="75" height="75" alt="Cancelled!" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCancel&gt;KErrCancel&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-3&lt;/td&gt;&lt;td&gt;&lt;i&gt;The operation was cancelled&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/242917/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/1/242917_a0d8907ff3_s.jpg" width="75" height="75" alt="OOM" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrNoMemory&gt;KErrNoMemory&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-4&lt;/td&gt;&lt;td&gt;&lt;i&gt;Not enough memory&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/135592452/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/46/135592452_4249cf0db6_s.jpg" width="75" height="75" alt="Photo of fireman taking a photo" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrNotSupported&gt;KErrNotSupported&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-5&lt;/td&gt;&lt;td&gt;&lt;i&gt;The operation requested is not supported&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/73617475/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/35/73617475_fef9674f62_s.jpg" width="75" height="75" alt="Centaurs vs. Lapiths" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrArgument&gt;KErrArgument&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-6&lt;/td&gt;&lt;td&gt;&lt;i&gt;Bad request&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt; &lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrTotalLossOfPrecision&gt;KErrTotalLossOfPrecision&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-7&lt;/td&gt;&lt;td&gt;&lt;i&gt;Total loss of precision&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/211992830/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/60/211992830_ee1aeaae45_s.jpg" width="75" height="75" alt="Decapitated BMX bars" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrBadHandle&gt;KErrBadHandle&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-8&lt;/td&gt;&lt;td&gt;&lt;i&gt;Bad object&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/361855572/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/166/361855572_8f8125454a_s.jpg" width="75" height="75" alt="Mersey, 18.01.2007, at bursting point" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrOverflow&gt;KErrOverflow&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-9&lt;/td&gt;&lt;td&gt;&lt;i&gt;Overflow&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/435691285/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/163/435691285_401f9be0e8_s.jpg" width="75" height="75" alt="Low Mersey, 26.03.2007" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrUnderflow&gt;KErrUnderflow&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-10&lt;/td&gt;&lt;td&gt;&lt;i&gt;Underflow&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/462968094/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/186/462968094_bd472f1069_s.jpg" width="75" height="75" alt="Pair of pink Jopos" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrAlreadyExists&gt;KErrAlreadyExists&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-11&lt;/td&gt;&lt;td&gt;&lt;i&gt;Already exists&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/249339347/" title="Woods by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/84/249339347_852c31c5dc_s.jpg" width="75" height="75" alt="Woods" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrPathNotFound&gt;KErrPathNotFound&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-12&lt;/td&gt;&lt;td&gt;&lt;i&gt;Unable to find the specified folder&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/35947705/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/25/35947705_22d0bf1387_s.jpg" width="75" height="75" alt="Coupla dodos" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrDied&gt;KErrDied&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-13&lt;/td&gt;&lt;td&gt;&lt;i&gt;Closed&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/314352914/" title="Going up by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/100/314352914_8273c85c8b_s.jpg" width="75" height="75" alt="Going up" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrInUse&gt;KErrInUse&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-14&lt;/td&gt;&lt;td&gt;&lt;i&gt;The specified object is currently in use by another program&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/40577760/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/30/40577760_b291261310_s.jpg" width="75" height="75" alt="Wrong end of a Dalek" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrServerTerminated&gt;KErrServerTerminated&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-15&lt;/td&gt;&lt;td&gt;&lt;i&gt;Server has closed&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/33499541/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/23/33499541_9456b2ba41_s.jpg" width="75" height="75" alt="Not your average berry merchant!" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrServerBusy&gt;KErrServerBusy&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-16&lt;/td&gt;&lt;td&gt;&lt;i&gt;Server busy&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/237141721/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/87/237141721_a51e30e44b_s.jpg" width="75" height="75" alt="Spraycan devil sketch" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCompletion&gt;KErrCompletion&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-17&lt;/td&gt;&lt;td&gt;&lt;i&gt;Completion error&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/471114424/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/207/471114424_6f38df8de8_s.jpg" width="75" height="75" alt="A classic" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrNotReady&gt;KErrNotReady&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-18&lt;/td&gt;&lt;td&gt;&lt;i&gt;Not ready&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/414901818/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/175/414901818_e56001d5e3_s.jpg" width="75" height="75" alt="yoowot?" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrUnknown&gt;KErrUnknown&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-19&lt;/td&gt;&lt;td&gt;&lt;i&gt;Unknown error&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/18331584/" title="Halme, Halonen, Bush, Saddam by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/13/18331584_d056b77a8f_s.jpg" width="75" height="75" alt="Halme, Halonen, Bush, Saddam" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCorrupt&gt;KErrCorrupt&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-20&lt;/td&gt;&lt;td&gt;&lt;i&gt;Corrupt&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/249307766/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/79/249307766_20ddc4cb4c_s.jpg" width="75" height="75" alt="Razor wire (no flash)" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrAccessDenied&gt;KErrAccessDenied&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-21&lt;/td&gt;&lt;td&gt;&lt;i&gt;Access denied&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/238301707/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/97/238301707_258865dc4a_s.jpg" width="75" height="75" alt="Dogs running free inside - Do not enter" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrLocked&gt;KErrLocked&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-22&lt;/td&gt;&lt;td&gt;&lt;i&gt;Locked&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/406690247/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/167/406690247_a4d42dcc15_s.jpg" width="75" height="75" alt="Bargin Box" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrWrite&gt;KErrWrite&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-23&lt;/td&gt;&lt;td&gt;&lt;i&gt;Failed to write&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/1328087307/" title="Carousel horses off to market by hugovk, on Flickr"&gt;&lt;img src="https://farm2.static.flickr.com/1244/1328087307_aab0a773ba_s.jpg" width="75" height="75" alt="Carousel horses off to market" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrDisMounted&gt;KErrDisMounted&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-24&lt;/td&gt;&lt;td&gt;&lt;i&gt;Wrong disk present&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/74892995/" title="End of the line by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/36/74892995_ecd4a1a80e_s.jpg" width="75" height="75" alt="End of the line" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrEof&gt;KErrEof&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-25&lt;/td&gt;&lt;td&gt;&lt;i&gt;Unexpected end of file&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/381079658/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/137/381079658_27e4684586_s.jpg" width="75" height="75" alt="Full English close-up" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrDiskFull&gt;KErrDiskFull&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-26&lt;/td&gt;&lt;td&gt;&lt;i&gt;Disk full&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/157511894/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/57/157511894_81ecbfe7c0_s.jpg" width="75" height="75" alt="Phrasebook Italian no 23: &amp;quot;C'è stato un incidente!&amp;quot;" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrBadDriver&gt;KErrBadDriver&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-27&lt;/td&gt;&lt;td&gt;&lt;i&gt;Bad device driver&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/9079736/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/6/9079736_504d8d4b73_s.jpg" width="75" height="75" alt="Obligatory photo of comedy shop sign" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrBadName&gt;KErrBadName&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-28&lt;/td&gt;&lt;td&gt;&lt;i&gt;Bad name&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/217968714/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/93/217968714_a81ec22431_s.jpg" width="75" height="75" alt="Split red paint" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCommsLineFail&gt;KErrCommsLineFail&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-29&lt;/td&gt;&lt;td&gt;&lt;i&gt;Comms line failed&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/1249667196/" title="Baltic, framed by hugovk, on Flickr"&gt;&lt;img src="https://farm2.static.flickr.com/1068/1249667196_8840257609_s.jpg" width="75" height="75" alt="Baltic, framed" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCommsFrame&gt;KErrCommsFrame&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-30&lt;/td&gt;&lt;td&gt;&lt;i&gt;Comms frame error&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/217196412/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/79/217196412_c2f3a8a092_s.jpg" width="75" height="75" alt="You can't stop there!" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCommsOverrun&gt;KErrCommsOverrun&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-31&lt;/td&gt;&lt;td&gt;&lt;i&gt;Comms overrun error&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/177990345/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/75/177990345_81677b82b2_s.jpg" width="75" height="75" alt="LOST PARROT" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCommsParity&gt;KErrCommsParity&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-32&lt;/td&gt;&lt;td&gt;&lt;i&gt;Comms parity error&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/453759007/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/242/453759007_862e78a1c6_s.jpg" width="75" height="75" alt="Clockside clock" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrTimedOut&gt;KErrTimedOut&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-33&lt;/td&gt;&lt;td&gt;&lt;i&gt;Timed out&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/111051747/" title="10th March 2006: Blimey, Christmas comes earlier every year!!! by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/38/111051747_2d5d128be4_s.jpg" width="75" height="75" alt="10th March 2006: Blimey, Christmas comes earlier every year!!!" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCouldNotConnect&gt;KErrCouldNotConnect&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-34&lt;/td&gt;&lt;td&gt;&lt;i&gt;Failed to connect&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/904750301/" title="Photo Sharing"&gt;&lt;img src="https://farm2.static.flickr.com/1067/904750301_cda1c38c74_s.jpg" width="75" height="75" alt="Lots of locks" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrCouldNotDisconnect&gt;KErrCouldNotDisconnect&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-35&lt;/td&gt;&lt;td&gt;&lt;i&gt;Failed to disconnect&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/298256261/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/117/298256261_2cdd981e2f_s.jpg" width="75" height="75" alt="The 2006 Allsaints Bakewell Disaster" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrDisconnected&gt;KErrDisconnected&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-36&lt;/td&gt;&lt;td&gt;&lt;i&gt;Disconnected&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/10854047/" title="Kallio library by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/6/10854047_f2a6e825d5_s.jpg" width="75" height="75" alt="Kallio library" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrBadLibraryEntryPoint&gt;KErrBadLibraryEntryPoint&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-37&lt;/td&gt;&lt;td&gt;&lt;i&gt;Bad library entry point&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/382022751/" title="Absolutely, Absolutety, Absoloutely Fabulous! by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/150/382022751_d6c649c145_s.jpg" width="75" height="75" alt="Absolutely, Absolutety, Absoloutely Fabulous!" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrBadDescriptor&gt;KErrBadDescriptor&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-38&lt;/td&gt;&lt;td&gt;&lt;i&gt;Bad descriptor&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/248514690/" title="Excuses, excuses, excuses... by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/88/248514690_93fdc27a55_s.jpg" width="75" height="75" alt="Excuses, excuses, excuses..." /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrAbort&gt;KErrAbort&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-39&lt;/td&gt;&lt;td&gt;&lt;i&gt;Interrupted&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/460070284/" title="15042007037 by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/214/460070284_b57b4b503a_s.jpg" width="75" height="75" alt="15042007037" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrTooBig&gt;KErrTooBig&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-40&lt;/td&gt;&lt;td&gt;&lt;i&gt;Too big&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/453767101/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/230/453767101_7db0562204_s.jpg" width="75" height="75" alt="Zero post" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrDivideByZero&gt;KErrDivideByZero&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-41&lt;/td&gt;&lt;td&gt;&lt;i&gt;Divide by zero&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/1615872818/" title="Photo Sharing"&gt;&lt;img src="https://farm3.static.flickr.com/2406/1615872818_ccb901e782_s.jpg" width="75" height="75" alt="I broke the Internet" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrBadPower&gt;KErrBadPower&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-42&lt;/td&gt;&lt;td&gt;&lt;i&gt;Batteries too low&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/172131691/" title="Full skip by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/54/172131691_2d770c1369_s.jpg" width="75" height="75" alt="Full skip" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrDirFull&gt;KErrDirFull&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-43&lt;/td&gt;&lt;td&gt;&lt;i&gt;Folder full&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/118936584/" title="Photo Sharing"&gt;&lt;img src="https://farm1.static.flickr.com/55/118936584_3b24a72359_s.jpg" width="75" height="75" alt="Half a bike" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrHardwareNotAvailable&gt;KErrHardwareNotAvailable&lt;/a&gt;&lt;/strong&gt; &amp;nbsp;&lt;/td&gt;&lt;td&gt;-44&lt;/td&gt;&lt;td&gt;&lt;i&gt;&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/209492159/" title="The boarded up Church Inn by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/88/209492159_00ce3cb4a3_s.jpg" width="75" height="75" alt="The boarded up Church Inn" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrSessionClosed&gt;KErrSessionClosed&lt;/a&gt;&lt;/strong&gt; &lt;/td&gt;&lt;td&gt;-45&lt;/td&gt;&lt;td&gt;&lt;i&gt;&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;a href="https://www.flickr.com/photos/hugovk/238299605/" title="Private Property - No thoroughfare by order by hugovk, on Flickr"&gt;&lt;img src="https://farm1.static.flickr.com/98/238299605_07a1ef371b_s.jpg" width="75" height="75" alt="Private Property - No thoroughfare by order" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;td&gt; &lt;strong&gt;&lt;a href=https://www.flickr.com/search/?q=KErrPermissionDenied&gt;KErrPermissionDenied&lt;/a&gt;&lt;/strong&gt; &amp;nbsp;&lt;/td&gt;&lt;td&gt;-46&lt;/td&gt;&lt;td&gt;&lt;i&gt;&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Original table of errors nabbed from
&lt;a href="https://web.archive.org/web/20090222234218/http://newlc.com/Symbian-OS-Error-Codes.html"&gt;http://newlc.com/Symbian-OS-Error-Codes.html&lt;/a&gt;&lt;/p&gt;</description></item><item><title>Booze It!</title><link>https://hugovk.dev/blog/2007/boozeit/</link><pubDate>Mon, 16 Apr 2007 18:04:49 +0000</pubDate><guid>https://hugovk.dev/blog/2007/boozeit/</guid><description>&lt;p&gt;Another &lt;a href="BoozeIt.sis"&gt;amazing application&lt;/a&gt; for S60 phones! (But not S60 3rd Ed.) Like
all the best applications it has a hard-coded exchange rate and a rubbish UI!&lt;/p&gt;
&lt;p&gt;Booze It! is the perfect tool for those who like the odd drink or two. It has two modes:
Home and Away.&lt;/p&gt;
&lt;h2 id="away-blurb" class="relative group"&gt;Away blurb &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#away-blurb" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Ever been in a foreign pub drinking half a litre of beer (sometimes in a pint glass) and
wondering what it would cost in proper money? Well, it&amp;rsquo;s easy enough to convert from
euros to pounds, either in your head or with your phone&amp;rsquo;s converter app. But that only
gives the price of a half litre when what you really want to know is how much a full
British pint would set you back. Well, this is the app for you!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keys:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Type the price of the foreign draught with the number keys&lt;/li&gt;
&lt;li&gt;Up key increases the digit at the &amp;lsquo;imaginary cursor&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Down key decreases the digit at the &amp;lsquo;imaginary cursor&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Left/right keys move the &amp;lsquo;imaginary cursor&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Send/green key: Send an SMS to tell your friends about your beer, you may edit before
sending&lt;/li&gt;
&lt;li&gt;Press * to toggle between Home and Away modes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Menu:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Glass size: To change the size of the foreign draught. Select either preset sizes or
enter your own size.&lt;/li&gt;
&lt;li&gt;Change rate: View the current exchange rate, or enter your own, with either euro or
pound as the base. &lt;em&gt;Note: the exchange rate isn&amp;rsquo;t stored and will be reset to the
default when you exit&amp;hellip;&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Send via SMS: Send an SMS to tell your friends about your beer, you may edit before
sending&lt;/li&gt;
&lt;li&gt;Send via Bluetooth: Share this amazing application with a friend!&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="home-blurb" class="relative group"&gt;Home blurb &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" style="text-decoration-line: none !important;" href="#home-blurb" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Picture the scene: you&amp;rsquo;re in an off licence or supermarket, choosing some booze for a
party. It&amp;rsquo;s easy to see which is the cheapest, but which gives the most alcohol for your
hard earned pound? You have to account for number of cans or bottles, volume in each,
percentage, and price. Simply consult Booze It! to help &lt;strong&gt;Choose Your Booze!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The home mode currently has a very dodgy UI. Sorry. You have to imagine a cursor, and
use the left and right keys to move the cursor to the digit you want to change. You then
press up or down to increase or decrease the digit at the cursor.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keys:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Up key increases the digit at the &amp;lsquo;imaginary cursor&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Down key decreases the digit at the &amp;lsquo;imaginary cursor&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Left/right keys move the &amp;lsquo;imaginary cursor&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Send/green key: Send a booze report via SMS, you may edit before sending&lt;/li&gt;
&lt;li&gt;Press * to toggle between Home and Away modes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Menu:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Send via SMS: Send a booze report, you may edit before sending&lt;/li&gt;
&lt;li&gt;Send via Bluetooth: Share this amazing application with a friend!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See more S60 stuff &lt;a href="https://hugovk.dev/tags/s60"&gt;here&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Gmail on Series 60</title><link>https://hugovk.dev/blog/2005/gmail-on-series-60/</link><pubDate>Mon, 13 Jun 2005 10:35:00 +0000</pubDate><guid>https://hugovk.dev/blog/2005/gmail-on-series-60/</guid><description>&lt;p&gt;These settings work for sending on the
&lt;a href="https://web.archive.org/web/20050318101754/http://www.nokia.com/nokia/0,1522,,00.html?orig=/6680"&gt;Nokia 6680&lt;/a&gt;,
but not receiving&amp;hellip; I used to be able to receive as well, but at least sending works
and means I can use the &amp;ldquo;Send as email&amp;rdquo; option in things like
&lt;a href="https://web.archive.org/web/20050305085929/http://www.nokia.com/nokia/0,,54630,00.html"&gt;Lifeblog&lt;/a&gt;,
and use Opera or the phone&amp;rsquo;s built-in Web app to check received email at gmail.com.&lt;/p&gt;</description></item><item><title>Series 60 Big Clock</title><link>https://hugovk.dev/blog/2005/big-clock/</link><pubDate>Mon, 30 May 2005 21:40:06 +0000</pubDate><guid>https://hugovk.dev/blog/2005/big-clock/</guid><description>&lt;p&gt;And here&amp;rsquo;s a freeware
&lt;a href="https://web.archive.org/web/20060702204202/https://www.sevenball.co.uk/bigclock.html"&gt;big clock&lt;/a&gt;
for your Series 60 phone :)&lt;/p&gt;</description></item><item><title>Morse Texter</title><link>https://hugovk.dev/blog/2005/morse-texter/</link><pubDate>Mon, 30 May 2005 17:07:09 +0000</pubDate><guid>https://hugovk.dev/blog/2005/morse-texter/</guid><description>&lt;p&gt;After reading &lt;a href="https://www.russellbeattie.com/blog/1008476.html"&gt;this&lt;/a&gt; and
&lt;a href="https://web.archive.org/web/20050526230439/http://muddybranch.thejkgroup.com/2005/05/morse_code_trum.html"&gt;this&lt;/a&gt;
and &lt;a href="https://www.m0tzo.co.uk/2004/01/11/morse-code-software/"&gt;this&lt;/a&gt; I decided to knock
up this &lt;del&gt;dodgy prototype&lt;/del&gt;
&lt;a href="https://web.archive.org/web/20110122015431/http://morsetexter.googlecode.com/files/MorseTexter.sis"&gt;amazing application&lt;/a&gt;
for S60 v2.x phones (but not v1.x or v3.x &amp;ndash; check your version
&lt;a href="https://en.wikipedia.org/wiki/S60_%28software_platform%29"&gt;here&lt;/a&gt;. Tap in some Morse code,
and then send it as an SMS. Keys are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1, 4, 7, or * - Dot&lt;/li&gt;
&lt;li&gt;2, 5, 8, or 0 - Space&lt;/li&gt;
&lt;li&gt;3, 6, 9, or # - Dash&lt;/li&gt;
&lt;li&gt;Left arrow - Dot&lt;/li&gt;
&lt;li&gt;OK key - Space&lt;/li&gt;
&lt;li&gt;Right arrow - Dash&lt;/li&gt;
&lt;li&gt;C key - Delete last dot, dash or letter&lt;/li&gt;
&lt;li&gt;Call/green key - Send as SMS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a gap between letters, press space once. For a gap between words, press space a
second time.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the
&lt;a href="https://web.archive.org/web/20160328104543/https://morsetexter.googlecode.com/files/MorseTexter.zip"&gt;source&lt;/a&gt;
(&lt;a href="https://code.google.com/archive/p/morsetexter/"&gt;Google Code&lt;/a&gt;,
&lt;a href="https://github.com/hugovk/morsetexter"&gt;GitHub&lt;/a&gt;), do whatever you want with it. Some
ideas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Add short and long key press handling on the OK key for dot and dash input, with
proper timing to add character and word spaces&lt;/li&gt;
&lt;li&gt;Centre the Morse characters and make them bigger/bolder&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.m0tzo.co.uk/2004/01/11/morse-code-software/"&gt;More ideas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;. / -. / .&amp;mdash; / &amp;mdash; / -.&amp;ndash; / -.-.&amp;ndash;&lt;/p&gt;</description></item></channel></rss>