<?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>python on Hugo van Kemenade</title><link>https://hugovk.dev/tags/python/</link><description>Recent content in python 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>Thu, 10 Sep 2026 08:08:01 +0000</lastBuildDate><atom:link href="https://hugovk.dev/tags/python/index.xml" rel="self" type="application/rss+xml"/><item><title>Soft-deprecating re.match()</title><link>https://hugovk.dev/blog/2026/soft-deprecating-re.match/</link><pubDate>Thu, 10 Sep 2026 08:08:01 +0000</pubDate><guid>https://hugovk.dev/blog/2026/soft-deprecating-re.match/</guid><description>&lt;p&gt;Quick, without looking it up, what does &lt;code&gt;re.match()&lt;/code&gt; do? Which of these return a match?&lt;/p&gt;&#10;&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;re&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;re&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;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;re&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;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;re&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;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;re&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;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;magpie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;How does it compare to &lt;code&gt;re.search()&lt;/code&gt; and &lt;code&gt;re.fullmatch()&lt;/code&gt;?&lt;/p&gt;&#10;&lt;p&gt;Whilst you&amp;rsquo;re (quickly) thinking about it, let&amp;rsquo;s introduce &lt;em&gt;soft deprecation&lt;/em&gt;.&lt;/p&gt;&#10;&lt;h2 id="soft-deprecation" class="relative group"&gt;Soft deprecation &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="#soft-deprecation" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Python&amp;rsquo;s backwards compatibility policy (PEP 387) introduced&#10;&lt;a href="https://peps.python.org/pep-0387/#soft-deprecation"&gt;soft deprecation&lt;/a&gt; in 2023:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;A soft deprecation can be used when using an API which should no longer be used to&#10;write new code, but it remains safe to continue using it in existing code. The API&#10;remains documented and tested, but will not be developed further (no enhancement).&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;A soft deprecation does not imply future removal of the API, nor does it issue a&#10;warning. It&amp;rsquo;s a docs-only recommendation to not use an API, ideally with a suggested&#10;replacement.&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s a completely separate decision whether, if ever, to turn a soft deprecation into a&#10;regular &amp;ldquo;hard&amp;rdquo; deprecation (where removal may follow); soft deprecations don&amp;rsquo;t&#10;&amp;ldquo;graduate&amp;rdquo; into regular deprecations or removals.&lt;/p&gt;&#10;&lt;h2 id="rematch" class="relative group"&gt;&lt;code&gt;re.match()&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="#rematch" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Now the answer:&lt;/p&gt;&#10;&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;re&lt;/span&gt;&#10;&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="n"&gt;re&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;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ✅ Matches&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;=&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 class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pi&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&#10;&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="n"&gt;re&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;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ✅ Matches&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;=&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 class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pi&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&#10;&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="n"&gt;re&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;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ❌ No match&lt;/span&gt;&#10;&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="n"&gt;re&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;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;magpie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ❌ No match&lt;/span&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;So &lt;a href="https://docs.python.org/3/library/re.html#re.match"&gt;&lt;code&gt;re.match()&lt;/code&gt;&lt;/a&gt; only matches at&#10;the &lt;em&gt;beginning&lt;/em&gt; of a string! This can be surprising: why is the start of the string&#10;special?&lt;/p&gt;&#10;&lt;h2 id="research" class="relative group"&gt;&lt;code&gt;re.search()&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="#research" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;If you don&amp;rsquo;t want to anchor at the start, and want to match anywhere in the string, use&#10;&lt;a href="https://docs.python.org/3/library/re.html#re.search"&gt;&lt;code&gt;re.search()&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#10;&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;re&lt;/span&gt;&#10;&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="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ✅ Matches&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;=&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 class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pi&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&#10;&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="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ✅ Matches&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;=&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 class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pi&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&#10;&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="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ✅ Matches&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&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;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pi&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&#10;&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="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;magpie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ✅ Matches&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pi&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="refullmatch" class="relative group"&gt;&lt;code&gt;re.fullmatch()&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="#refullmatch" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;If you want to anchor both the start and the end, and check the entire string matches,&#10;use &lt;a href="https://docs.python.org/3/library/re.html#re.fullmatch"&gt;&lt;code&gt;re.fullmatch()&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#10;&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;re&lt;/span&gt;&#10;&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="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ✅ Matches&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Match&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;=&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 class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;pi&amp;#39;&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&#10;&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="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ❌ No match&lt;/span&gt;&#10;&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="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ❌ No match&lt;/span&gt;&#10;&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="n"&gt;re&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;magpie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ❌ No match&lt;/span&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="introducing-reprefixmatch" class="relative group"&gt;Introducing &lt;code&gt;re.prefixmatch()&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="#introducing-reprefixmatch" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Because of the surprising half-anchored behaviour,&#10;&lt;a href="https://docs.python.org/3.15/library/re.html#prefixmatch-vs-match"&gt;we&amp;rsquo;ve introduced a new alias for &lt;code&gt;re.match()&lt;/code&gt; in Python 3.15&lt;/a&gt;,&#10;named &lt;a href="https://docs.python.org/3.15/library/re.html#re.prefixmatch"&gt;&lt;code&gt;re.prefixmatch()&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Quoting from the Zen Of Python (&lt;code&gt;python3 -m this&lt;/code&gt;): &lt;em&gt;“Explicit is better than&#10;implicit”.&lt;/em&gt; Anyone reading the name &lt;code&gt;prefixmatch()&lt;/code&gt; is likely to understand the&#10;intended semantics. When reading &lt;code&gt;match()&lt;/code&gt; there remains a seed of doubt about the&#10;intended behavior to anyone not already familiar with this old Python gotcha.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="soft-deprecating-rematch" class="relative group"&gt;Soft-deprecating &lt;code&gt;re.match()&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="#soft-deprecating-rematch" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;And with a more explicit replacement, we&amp;rsquo;ve&#10;&lt;a href="https://docs.python.org/3.15/library/re.html#prefixmatch-vs-match"&gt;soft-deprecated &lt;code&gt;re.match()&lt;/code&gt; in Python 3.15&lt;/a&gt;:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;We do not plan to remove the older &lt;code&gt;match()&lt;/code&gt; name, as it has been used in code for&#10;over 30 years. It has been&#10;&lt;a href="https://docs.python.org/3/glossary.html#term-soft-deprecated"&gt;soft deprecated&lt;/a&gt;: code&#10;supporting older versions of Python should continue to use &lt;code&gt;match()&lt;/code&gt;, while new code&#10;should prefer &lt;code&gt;prefixmatch()&lt;/code&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Use &lt;code&gt;re.prefixmatch()&lt;/code&gt; if you only really meant to use the half-anchor; otherwise use&#10;&lt;code&gt;re.search()&lt;/code&gt; or &lt;code&gt;re.fullmatch()&lt;/code&gt;.&lt;/p&gt;&#10;&lt;h2 id="comparison" class="relative group"&gt;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="#comparison" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Function&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Start anchor&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;End anchor&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Added in&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;With &lt;a href="https://docs.python.org/3/library/re.html#regular-expression-syntax"&gt;special characters&lt;/a&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;re.search()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;❌&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;❌&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;re.search(&amp;quot;pi&amp;quot;, string)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;re.match()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;❌&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;re.search(&amp;quot;^pi&amp;quot;, string)&lt;/code&gt;&lt;br&gt;&lt;code&gt;re.search(r&amp;quot;\Api&amp;quot;, string)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;re.prefixmatch()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;❌&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.15&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;re.search(&amp;quot;^pi&amp;quot;, string)&lt;/code&gt;&lt;br&gt;&lt;code&gt;re.search(r&amp;quot;\Api&amp;quot;, string)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;re.fullmatch()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;✅&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;re.search(&amp;quot;^pi$&amp;quot;, string)&lt;/code&gt;&lt;br&gt;&lt;code&gt;re.search(r&amp;quot;\Api\z&amp;quot;, string)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The functions without special characters are generally a bit faster.&lt;/p&gt;&#10;&lt;h2 id="lint" class="relative group"&gt;Lint &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" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;You can avoid &lt;code&gt;re.match()&lt;/code&gt; in your project with&#10;&lt;a href="https://docs.astral.sh/ruff/rules/banned-api/"&gt;Ruff&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# pyproject.toml&lt;/span&gt;&#10;&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="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ruff&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend-select&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;TID251&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;# flake8-tidy-imports: banned-api&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;lint&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flake8-tidy-imports&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;banned-api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;re.match&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Use re.fullmatch() or re.search() instead&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Or run:&lt;/p&gt;&#10;&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;ruff check . --isolated --select TID251 &lt;span class="se"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --config &lt;span class="s1"&gt;&amp;#39;lint.flake8-tidy-imports.banned-api.&amp;#34;re.match&amp;#34;.msg = &amp;#34;use re.fullmatch() or re.search() instead&amp;#34;&amp;#39;&lt;/span&gt;&#10;&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;&#10;&lt;li&gt;Seth Larson: &lt;a href="https://sethmlarson.dev/use-backslash-A-and-z-not-%5E-and-$-with-python-regular-expressions"&gt;Use “\A&amp;hellip;\z”, not&#10;“^&amp;hellip;$” with Python regular expressions&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo: Double-exposure bike and pedestrian stencils&#10;(&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;CC&#10;BY-NC-SA 2.0&lt;/a&gt;&#10;&lt;a href="https://www.flickr.com/photos/hugovk/26955055864/"&gt;Hugo van Kemenade&lt;/a&gt;).&lt;/small&gt;&lt;/p&gt;&#10;</description></item><item><title>Help test Python 3.15!</title><link>https://hugovk.dev/blog/2026/help-test-python-315/</link><pubDate>Tue, 01 Sep 2026 14:33:38 +0000</pubDate><guid>https://hugovk.dev/blog/2026/help-test-python-315/</guid><description>&lt;p&gt;Calling all Python library maintainers! 🐍&lt;/p&gt;&#10;&lt;p&gt;The &lt;em&gt;second and final&lt;/em&gt; Python 3.15 release candidate is out! 🎉&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s your last chance to join the cool testers&amp;rsquo; club and say that YOU helped test 3.15&#10;before the big day on 1st October! 😎&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://peps.python.org/pep-0790/#release-schedule"&gt;PEP 790&lt;/a&gt; defines the release&#10;schedule for Python 3.15.0:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The first release candidate came out on 4th August 2026&lt;/li&gt;&#10;&lt;li&gt;The second release candidate came out on 1st September 2026&lt;/li&gt;&#10;&lt;li&gt;And the full release is set for 1st October 2026&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In his&#10;&lt;a href="https://discuss.python.org/t/python-3-15-0-candidate-2-is-here/108841/1?u=hugovk"&gt;announcement&lt;/a&gt;,&#10;Hugo van Kemenade (👋 hi, that&amp;rsquo;s me!), release manager for Python 3.14 and 3.15, gave a&#10;call to action:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;We &lt;em&gt;&lt;strong&gt;strongly encourage&lt;/strong&gt;&lt;/em&gt; maintainers of third-party Python projects to prepare&#10;their projects for 3.15 during this phase, and publish Python 3.15 wheels on PyPI to&#10;be ready for the final release of 3.15.0, and to help other projects do their own&#10;testing. Any binary wheels built against Python 3.15.0 release candidates &lt;em&gt;&lt;strong&gt;will&#10;work&lt;/strong&gt;&lt;/em&gt; with future versions of Python 3.15. As always, report any issues to&#10;&lt;a href="https://github.com/python/cpython/issues"&gt;the Python bug tracker&lt;/a&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;h2 id="test-with-315" class="relative group"&gt;Test with 3.15 &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-315" 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.15.&#10;There&amp;rsquo;s two big benefits:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;There have been&#10;&lt;a href="https://docs.python.org/3.15/whatsnew/3.15.html#removed"&gt;removals and changes&lt;/a&gt; in&#10;Python 3.15. Testing now helps us make our code compatible and avoid any big&#10;surprises (for us and our users) at the big launch in October.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;We might find bugs in Python itself! Reporting those will help get them fixed and&#10;help everyone.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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;To test the latest alpha, beta or release candidate on GitHub Actions with&#10;&lt;a href="https://github.com/actions/setup-python#supported-version-syntax"&gt;actions/setup-python&lt;/a&gt;,&#10;add &lt;code&gt;3.15&lt;/code&gt; and &lt;code&gt;allow-prereleases: true&lt;/code&gt; to your workflow matrix.&lt;/p&gt;&#10;&lt;p&gt;For example:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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.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 class="s2"&gt;&amp;#34;3.15&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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@3d3c42e5aac5ba805825da76410c181273ba90b1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v7.0.1&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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@5fda3b95a4ea91299a34e894583c3862153e4b97&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# v7.0.0&lt;/span&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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.15-dev&lt;/code&gt; and omit &lt;code&gt;allow-prereleases: true&lt;/code&gt;, but I find the above&#10;a bit neater, and when 3.15.0 final is released in October, it will continue testing&#10;with full release versions.)&lt;/p&gt;&#10;&lt;h2 id="when-to-support-315" class="relative group"&gt;When to support 3.15? &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-315" 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&#10;&lt;code&gt;Programming Language :: Python :: 3.15&lt;/code&gt;&#10;&lt;a href="https://pypi.org/classifiers/"&gt;Trove classifier&lt;/a&gt;. Many&#10;&lt;a href="https://pyreadiness.org/3.15/"&gt;projects already have&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;Especially if you have extension modules and other projects depend on yours, a release&#10;with binary wheels will help them test and prepare.&lt;/p&gt;&#10;&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;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;There will be &lt;em&gt;&lt;strong&gt;no ABI changes&lt;/strong&gt;&lt;/em&gt; from this point forward in the 3.15 series, and the&#10;goal is that there will be as few code changes as possible.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Let&amp;rsquo;s start testing 3.15 now! 🚀&lt;/p&gt;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://docs.python.org/3.15/whatsnew/3.15.html"&gt;What’s New In Python 3.15&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo: A woodcut of a crowned snake in the Museo di Palazzo Poggi,&#10;Bologna, used for Ulisse Aldrovandi&amp;rsquo;s&#10;&lt;a href="https://archive.org/details/bub_gb_Ul-fTdRAMIwC/page/n371/mode/1up"&gt;&lt;em&gt;Serpentum, et draconum historiæ libri duo&lt;/em&gt; (1640)&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</description></item><item><title>Security: line goes up</title><link>https://hugovk.dev/blog/2026/security-line-goes-up/</link><pubDate>Mon, 13 Jul 2026 20:44:50 +0000</pubDate><guid>https://hugovk.dev/blog/2026/security-line-goes-up/</guid><description>&lt;p&gt;Like many other projects, CPython is experiencing a huge increase in security reports.&lt;/p&gt;&#10;&lt;h2 id="cves-per-year" class="relative group"&gt;CVEs per year &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="#cves-per-year" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Last month, PSF Security Developer-in-Residence &lt;a href="https://sethmlarson.dev/"&gt;Seth Larson&lt;/a&gt;&#10;posted a chart of&#10;&lt;a href="https://mastodon.social/@sethmlarson/116680835974208152"&gt;CVEs per year&lt;/a&gt;, showing a&#10;large increase in 2026:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; srcset="https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year_hu_46eaec89582d04c1.webp 330w,https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year_hu_db185073a5efff9b.webp 660w&#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year_hu_26459e66135505d0.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year_hu_893642da8a1f9460.webp 1257w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1257"&#10; height="705"&#10; class="mx-auto my-0 rounded-md"&#10; alt="2007-2024 all below 20 per year. 2025 and year-to-date 2026 had just over 20, with 2026 extrapolated to around 65."&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year_hu_2601403f38fe525a.png" srcset="https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year_hu_4fadb0136dac3618.png 330w,https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year_hu_2601403f38fe525a.png 660w&#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year_hu_dc377bf619c7de4.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/cves-per-year.png 1257w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;But this only represents the &lt;em&gt;output&lt;/em&gt; of security work, and doesn&amp;rsquo;t show all the work&#10;dealing with incoming reports. Many are closed and dealt with as non-security bug&#10;reports instead; many are closed as neither security nor bug reports.&lt;/p&gt;&#10;&lt;p&gt;Let&amp;rsquo;s reveal some of this unseen work by the&#10;&lt;a href="https://devguide.python.org/security/psrt/"&gt;Python Security Response Team&lt;/a&gt; (PSRT).&lt;/p&gt;&#10;&lt;h2 id="ghsas-by-month" class="relative group"&gt;GHSAs by month &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="#ghsas-by-month" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Here are the number of incoming GitHub Security Advisories (GHSA) reports created since&#10;July 2024:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; srcset="https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_fa6bba1498f0d631.webp 330w,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_4feb12a20d2b059a.webp 660w&#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_331214cef87ddc0f.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_3237be4c8e92a1a5.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="2100"&#10; height="900"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Chart of new security reports. Single digits or zero per month from 2024, increasing to around 40 in 2026."&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_e0223ff9b32735ff.png" srcset="https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_d83bb7f01ae34372.png 330w,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_e0223ff9b32735ff.png 660w&#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_1871b5c920562091.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-month_hu_a51eb091c2fc1770.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;h2 id="ghsas-by-year" class="relative group"&gt;GHSAs by year &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="#ghsas-by-year" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;Here is the same thing by year, and remembering we&amp;rsquo;re only &lt;em&gt;halfway through 2026&lt;/em&gt;:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; srcset="https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_a5e56e0a0d314d24.webp 330w,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_26f25babbef795a8.webp 660w&#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_1674a5eb779a5f7b.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_51b6562e0e3de6f7.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="2100"&#10; height="900"&#10; class="mx-auto my-0 rounded-md"&#10; alt="18 in 2024, 41 in 2025, 175 so far in 2026."&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_fa7a5184a8320d7e.png" srcset="https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_63bd8cd56b492976.png 330w,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_fa7a5184a8320d7e.png 660w&#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_e984503f866642dc.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/ghsas-by-year_hu_28493ea9be3ee5ab.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;h2 id="email-reports-by-month" class="relative group"&gt;Email reports by month &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="#email-reports-by-month" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;We&amp;rsquo;ve only fairly recently been&#10;&lt;a href="https://devguide.python.org/security/policy/"&gt;encouraging new reports&lt;/a&gt; be made via&#10;GHSA. Before this, they were usually made by email. The next chart is the number of&#10;email discussions (or threads) and participants by month:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; srcset="https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_c2b3a2849cd29f7a.webp 330w,https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_84729fd62083085.webp 660w&#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_e426233e353b8984.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_a9f5cdb3f343d2f0.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="2100"&#10; height="900"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Number of discussions and participants per month follow each other closely. Single digits from 2014, around 20 by 2019, 40 by 2021 and 2022, a dip to 15 for 2023 to 2025, up to 50 for 2026 so far."&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_f73ebb8b42dcf9fe.png" srcset="https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_8b675f5b86a37d8c.png 330w,https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_f73ebb8b42dcf9fe.png 660w&#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_1aab79a15185fdba.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/security-line-goes-up/email_hu_fd8ff243f0402a1c.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;Big thanks to Seth for all his work as Security Developer-in-Residence: helping shepherd&#10;all these reports, developing a&#10;&lt;a href="https://devguide.python.org/security/policy/"&gt;security policy&lt;/a&gt; to improve the quality&#10;of incoming reports and help us assess them, and defining PSRT membership and&#10;responsibilities via &lt;a href="https://peps.python.org/pep-0811/"&gt;PEP 811&lt;/a&gt; to build an active&#10;team. All this would be much harder without his guidance! And thanks to&#10;&lt;a href="https://alpha-omega.dev/"&gt;Alpha-Omega&lt;/a&gt; for sponsoring his position at the PSF.&lt;/p&gt;&#10;</description></item><item><title>Fixing the dictionary with Python 3.14</title><link>https://hugovk.dev/blog/2026/fixing-dict/</link><pubDate>Wed, 08 Jul 2026 16:40:05 +0000</pubDate><guid>https://hugovk.dev/blog/2026/fixing-dict/</guid><description>&lt;p&gt;Yes, but not the&#10;&lt;a href="https://docs.python.org/3/library/stdtypes.html#mapping-types-dict"&gt;&lt;code&gt;dict&lt;/code&gt;&lt;/a&gt; kind of&#10;dictionary.&lt;/p&gt;&#10;&lt;p&gt;When working on CPython, we often find obscure bugs elsewhere, in compilers, operating&#10;systems and elsewhere:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://emmatyping.dev/finding-a-miscompilation-in-rustllvm.html"&gt;Rust/LLVM&lt;/a&gt;,&#10;&lt;a href="https://github.com/llvm/llvm-project/issues/106846"&gt;clang-19&lt;/a&gt;,&#10;&lt;a href="https://github.com/llvm/llvm-project/issues/179695"&gt;clang 21&lt;/a&gt; and&#10;&lt;a href="https://github.com/llvm/llvm-project/pull/120267"&gt;BOLT&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python/cpython/issues/129987#issuecomment-2761279236"&gt;GCC 13&lt;/a&gt;,&#10;&lt;a href="https://gcc.gnu.org/bugzilla/show_bug.cgi?id=118430"&gt;GCC 15&lt;/a&gt;,&#10;&lt;a href="https://github.com/python/cpython/issues/127000"&gt;glibc&lt;/a&gt;,&#10;&lt;a href="https://github.com/python/cpython/issues/122431"&gt;readline&lt;/a&gt; and&#10;&lt;a href="https://github.com/python/cpython/issues/120378"&gt;curses&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python/cpython/issues/131032"&gt;musl fma&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Since Python 3.8, the release notes have a section called &amp;ldquo;And now for something&#10;completely different&amp;rdquo;. These have included Monty Python sketches, astrophysics facts and&#10;poetry.&lt;/p&gt;&#10;&lt;p&gt;For Python 3.14, I&amp;rsquo;m doing all things&#10;&lt;a href="https://discuss.python.org/t/python-3-14-0-final-is-here/104210?u=hugovk#p-273519-and-now-for-something-completely-different-9"&gt;&lt;em&gt;π&lt;/em&gt;&lt;/a&gt;,&#10;&lt;a href="https://discuss.python.org/t/python-3-14-0-alpha-3/74542?u=hugovk#p-214989-and-now-for-something-completely-different-3"&gt;pie&lt;/a&gt;&#10;and&#10;&lt;a href="https://discuss.python.org/t/python-3-14-0rc2-and-3-13-7-are-go/102403?u=hugovk#p-267064-and-now-for-something-completely-different-13"&gt;[mag]pie&lt;/a&gt;&#10;(&lt;a href="../../2025/and-now/"&gt;more here&lt;/a&gt;). As part of the research for this important task, I&#10;looked up &lt;em&gt;pi&lt;/em&gt; in the&#10;&lt;a href="https://www.oed.com/dictionary/pi_n1?tab=factsheet&amp;amp;tl=true#30481837"&gt;&lt;em&gt;Oxford English Dictionary&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;As we all recall from the&#10;&lt;a href="https://discuss.python.org/t/python-3-14-0-beta-1-is-here/91117#p-246172-and-now-for-something-completely-different-8"&gt;Python 3.14.0b1 release notes&lt;/a&gt;,&#10;William Jones was the first person to use the &lt;em&gt;π&lt;/em&gt; symbol to denote the circle&amp;rsquo;s&#10;circumference to its diameter in his&#10;&lt;a href="https://archive.org/details/SynopsisPalmariorumMatheseosOrANewIntroductionToTheMathematics/page/n283/mode/1up?view=theater"&gt;&lt;em&gt;Synopsis Palmariorum Matheseos&lt;/em&gt;&lt;/a&gt;&#10;(1706):&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; srcset="https://hugovk.dev/blog/2026/fixing-dict/og-pi_hu_d93b28295a2335e8.webp 330w,https://hugovk.dev/blog/2026/fixing-dict/og-pi_hu_abec1a08b28f9b81.webp 660w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/og-pi_hu_eaff68fa85379314.webp 767w&#10; &#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/og-pi_hu_eaff68fa85379314.webp 767w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="767"&#10; height="443"&#10; class="mx-auto my-0 rounded-md"&#10; alt="In the Circle, the Diameter is to Circumference as 1 to 16/5 - 4/239 - 1/3 15/(53) - 4/(2393) &amp;#43; 1/5 16/(55) - 4/(2395) -, &amp;amp;c. = 3.1459, &amp;amp;c. = π. This Series..I receiv&amp;rsquo;d from the Excellent Analyst..Mr. John Machin; and by means thereof, Van Ceulen&amp;rsquo;s Number..may be Examin&amp;rsquo;d."&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2026/fixing-dict/og-pi_hu_11079b3c0d4638a7.jpg" srcset="https://hugovk.dev/blog/2026/fixing-dict/og-pi_hu_84eecc0f9d8623a5.jpg 330w,https://hugovk.dev/blog/2026/fixing-dict/og-pi_hu_11079b3c0d4638a7.jpg 660w&#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/og-pi.jpg 767w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/og-pi.jpg 767w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;However, the &lt;em&gt;OED&lt;/em&gt;&amp;rsquo;s first citation had a markup bug:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; srcset="https://hugovk.dev/blog/2026/fixing-dict/broken-pi_hu_44174aaf24a4d1af.webp 330w,https://hugovk.dev/blog/2026/fixing-dict/broken-pi_hu_a712234b96493cb4.webp 660w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/broken-pi_hu_fb4f5f8e45f4cc9e.webp 824w&#10; &#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/broken-pi_hu_fb4f5f8e45f4cc9e.webp 824w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="824"&#10; height="249"&#10; class="mx-auto my-0 rounded-md"&#10; alt="As previous image, but: 1 to Misplaced &amp;amp;, where Misplaced and the ampersand are red on yellow background"&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2026/fixing-dict/broken-pi_hu_3c12ffb5d11aad82.png" srcset="https://hugovk.dev/blog/2026/fixing-dict/broken-pi_hu_d8ba9f149a59ce4d.png 330w,https://hugovk.dev/blog/2026/fixing-dict/broken-pi_hu_3c12ffb5d11aad82.png 660w&#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/broken-pi.png 824w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/broken-pi.png 824w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;I duly reported this to the &lt;em&gt;OED&lt;/em&gt; in July 2024; and by the next time I looked it up, in&#10;June 2025, it was fixed!&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; srcset="https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_6dd1cb572fd33cb0.webp 330w,https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_3b32cbdfcf05cfe0.webp 660w&#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_cf34297d1d41c5d9.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_5296bebf8734feb7.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1704"&#10; height="584"&#10; class="mx-auto my-0 rounded-md"&#10; alt="As previous image, but the sequence is now fixed"&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_c3d86b566f46681c.png" srcset="https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_f4ebb5f3173af471.png 330w,https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_c3d86b566f46681c.png 660w&#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_21ed4e8a433d2289.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/fixing-dict/fixed-pi_hu_ba058c1f4cef57c.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;Hooray!&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo: Part of the definition for &amp;ldquo;get&amp;rdquo; in the &lt;em&gt;OED&lt;/em&gt;&amp;rsquo;s 1901 forerunner, &lt;em&gt;A&#10;New English Dictionary on Historical Principles&lt;/em&gt;&#10;(&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;CC&#10;BY-NC-SA 2.0&lt;/a&gt;&#10;&lt;a href="https://www.flickr.com/photos/hugovk/8192259573"&gt;Hugo van Kemenade&lt;/a&gt;).&lt;/small&gt;&lt;/p&gt;&#10;</description></item><item><title>I'm delighted to rejoin the Sovereign Tech Fellowship</title><link>https://hugovk.dev/blog/2026/sovereign-tech-fellowship/</link><pubDate>Fri, 12 Jun 2026 07:35:12 +0000</pubDate><guid>https://hugovk.dev/blog/2026/sovereign-tech-fellowship/</guid><description>&lt;p&gt;I&amp;rsquo;m happy to rejoin the&#10;&lt;a href="https://www.sovereign.tech/programs/fellowship"&gt;Sovereign Tech Fellowship&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;I was one of six participants in the&#10;&lt;a href="../../2025/im-excited-to-join-the-sovereign-tech-fellowship/"&gt;2025 pilot&lt;/a&gt; to pay&#10;maintainers of critical open source technologies in the public interest. By all accounts&#10;this first cohort was a resounding success, and I&amp;rsquo;m glad to see the programme continue.&lt;/p&gt;&#10;&lt;p&gt;It was wonderful to be part of the inaugural Sovereign Tech Fellowship, and incredibly&#10;beneficial to my projects: it gave me the time to focus on releasing Python 3.14 and&#10;3.15 smoothly, to mentor and onboard others, and to support the wider community.&lt;/p&gt;&#10;&lt;h2 id="2025-impact" class="relative group"&gt;2025 impact &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="#2025-impact" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;The&#10;&lt;a href="https://www.sovereign.tech/publications/evaluation-sovereign-tech-fellowship-2025"&gt;2025 evaluation report&lt;/a&gt;&#10;covers all six of us and the benefits of the programme at a higher level. Here&amp;rsquo;s some of&#10;the specific things I achieved.&lt;/p&gt;&#10;&lt;p&gt;I was happy with how the&#10;&lt;a href="https://discuss.python.org/t/python-3-14-0-final-is-here/104210"&gt;big Python 3.14.0&lt;/a&gt;&#10;&lt;a href="../../2025/releasing-python-3-14-0/"&gt;release went&lt;/a&gt;. This is in part from having a good&#10;team to work with, and building on the past, but no doubt also due to being able to&#10;focus and invest time thanks to the Fellowship.&lt;/p&gt;&#10;&lt;p&gt;On many occasions, having time to dedicate to the role meant I could prioritise things&#10;as they occurred. For example, when needing to make expedited releases, I could dedicate&#10;time to go through all the necessary prep, and make the release without any stress of&#10;fitting it in around a regular job. Similarly, when last-minute problems came up on&#10;release day, such as newly-committed code not passing tests, I didn&amp;rsquo;t need to rush, and&#10;could contact the contributor to arrange a fix. Some other release managers had reverted&#10;similar changes to let the contributor try again for a later release, but there was less&#10;pressure for me and I could wait longer.&lt;/p&gt;&#10;&lt;p&gt;I was able to mentor other project members, such as helping onboard the next release&#10;manager, and also answer questions for other triagers. I promoted two new triagers in&#10;different projects. Other community members sometimes asked me about how to contribute.&#10;I attended many community &amp;ldquo;office hours&amp;rdquo; meetings and Monthly Conference Organisers'&#10;calls to share what&amp;rsquo;s going on and answer questions, and likewise&#10;&lt;a href="https://hugovk.dev/blog/"&gt;blogged&lt;/a&gt; and shared on social media such as&#10;&lt;a href="https://mastodon.social/@hugovk"&gt;Mastodon&lt;/a&gt;,&#10;&lt;a href="https://bsky.app/profile/hugovk.dev"&gt;Bluesky&lt;/a&gt; and&#10;&lt;a href="https://www.linkedin.com/in/hugovk/"&gt;LinkedIn&lt;/a&gt;. I was able to attend many conferences&#10;and give talks about the upcoming release, and discuss with other attendees what happens&#10;with Python releases and the project in general. This all helps improve transparency. I&#10;also chaired many monthly&#10;&lt;a href="https://docs-community.readthedocs.io/community/monthly-meeting.html"&gt;Docs Working Group meetings&lt;/a&gt;,&#10;and attended many other meetings from different projects.&lt;/p&gt;&#10;&lt;p&gt;I was able to make many improvements in the release process, through additional&#10;automation and testing to remove tedious manual steps. I&amp;rsquo;ve improved the accessibility&#10;of websites visited by tens of millions per month.&lt;/p&gt;&#10;&lt;p&gt;I created a triage dashboard that helped us close hundreds of issues, and also complete&#10;forgotten backports including security fixes.&lt;/p&gt;&#10;&lt;p&gt;I was also able to invest time on non-technical, social, organisational and governance&#10;improvements. I&amp;rsquo;m proud&#10;&lt;a href="https://discuss.python.org/t/alternate-the-language-summit-between-pycon-us-and-europython/102156"&gt;my proposal was accepted&lt;/a&gt;&#10;to alternate the Language Summit between PyCon US and EuroPython, rather than always&#10;being in the US, to improve the diversity of voices of who will shape the future of&#10;Python. The&#10;&lt;a href="https://ep2026.europython.eu/language-summit/"&gt;2026 summit will be held at EuroPython&lt;/a&gt;&#10;and I&amp;rsquo;m helping organise.&lt;/p&gt;&#10;&lt;p&gt;Since 2009, the summit has been a one-day event that takes place at PyCon US before the&#10;main conference days. It has also been held at EuroPython twice, in 2010 and 2011. The&#10;PSF mission is &lt;em&gt;&amp;ldquo;to support and facilitate the growth of a diverse and international&#10;community of Python programmers&amp;rdquo;&lt;/em&gt;, and not all potential attendees can travel to the US&#10;each year. This proposal took a lot of work:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;In November 2024, the core team discussed on Discord the possibility of alternating&#10;the summit between PyCon US and EuroPython. People were in favour, but said it would&#10;need to be discussed at the summit at PyCon US in May 2025.&lt;/li&gt;&#10;&lt;li&gt;In March 2025, I asked for the topic to be added to the agenda, as I wasn&amp;rsquo;t attending.&lt;/li&gt;&#10;&lt;li&gt;In May, the discussion took place. The minutes simply said: &amp;ldquo;Watch out for a Discourse&#10;thread to discuss this.&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;In July, during EuroPython, I asked summit attendees what the impression was, and they&#10;said people were in favour. I also spoke with the chair of EuroPython Society about&#10;the summit requirements, and they said they&amp;rsquo;d be happy to host us.&lt;/li&gt;&#10;&lt;li&gt;In August, because no Discourse thread had appeared, I opened a proposal to alternate.&lt;/li&gt;&#10;&lt;li&gt;In September, during the Steering Council Q&amp;amp;A at the Core Team Sprint, I asked about&#10;next steps. The consensus was for the SC to open a formal poll amongst the core team.&lt;/li&gt;&#10;&lt;li&gt;In October, the SC opened a poll for core team members.&lt;/li&gt;&#10;&lt;li&gt;In November, the poll concluded overwhelmingly in favour of alternating.&lt;/li&gt;&#10;&lt;li&gt;In December, the SC approved my request. I volunteered to help organise the summit and&#10;opened discussions with the EuroPython Society to make it happen in 2026.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I had more free time to spend on non-open source things, but also more free time to help&#10;the local Python community such as by co-organising two&#10;&lt;a href="https://helsinki-python.github.io/"&gt;local&lt;/a&gt;&#10;&lt;a href="https://pydata-helsinki.github.io/"&gt;meetups&lt;/a&gt;. One person I nominated became a Fellow of&#10;the Python Software Foundation and another of the EuroPython Society, which recognises&#10;the importance of community work.&lt;/p&gt;&#10;&lt;p&gt;Finally, I enjoyed our monthly Fellowship meetings where the six of us all gave a&#10;summary of our last month&amp;rsquo;s work. Similarly, it was great to meet most of them in person&#10;along with people from the Agency at the&#10;&lt;a href="https://www.sovereign.tech/news/who-will-maintain-our-digital-future"&gt;event to mark the inaugural Sovereign Tech Fellowship cohort&lt;/a&gt;&#10;and hear the results of the evaluation report.&lt;/p&gt;&#10;&lt;h2 id="arbitrary-statistics" class="relative group"&gt;Arbitrary statistics &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="#arbitrary-statistics" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;On GitHub:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Total contributions: 6,642&lt;/li&gt;&#10;&lt;li&gt;Issues created: 90&lt;/li&gt;&#10;&lt;li&gt;PRs created: 901&lt;/li&gt;&#10;&lt;li&gt;Issues closed: 446&lt;/li&gt;&#10;&lt;li&gt;PRs merged: 1,401&lt;/li&gt;&#10;&lt;li&gt;PRs closed: 142&lt;/li&gt;&#10;&lt;li&gt;Total issues involved with: 1,409&lt;/li&gt;&#10;&lt;li&gt;Total PRs involved with: 4,095&lt;/li&gt;&#10;&lt;li&gt;Repositories affected: 409&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Made 55 releases:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;13 of Python 3.14&lt;/li&gt;&#10;&lt;li&gt;3 of Python 3.15&lt;/li&gt;&#10;&lt;li&gt;39 of PyPI projects&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Started maintaining:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://planetpython.org/"&gt;Python Planet&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pythonwheels.com/"&gt;Python Wheels&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Archived:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python-twitter-tools/twitter/issues/476"&gt;Python Twitter Tools&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://discuss.python.org/t/archive-python-ideas-mailing-list-in-favour-of-discourse/88398"&gt;python-ideas mailing list&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Attended eight conferences in Berlin (&lt;a href="https://25.foss-backstage.de/"&gt;FOSS Backstage&lt;/a&gt;&#10;and &lt;a href="https://25.foss-backstage.design/"&gt;Design&lt;/a&gt;), Bologna&#10;(&lt;a href="https://2025.pycon.it/en"&gt;PyCon Italia&lt;/a&gt;), Prague&#10;(&lt;a href="https://ep2025.europython.eu/"&gt;EuroPython&lt;/a&gt;), Athens&#10;(&lt;a href="https://2025.pycon.gr/en/"&gt;PyCon Greece&lt;/a&gt;), Manchester&#10;(&lt;a href="https://2025.pyconuk.org/"&gt;PyCon UK&lt;/a&gt;), Tallinn&#10;(&lt;a href="https://2025.pycon.ee/"&gt;PyCon Estonia&lt;/a&gt;), Jyväskylä&#10;(&lt;a href="https://fi.pycon.org/"&gt;PyCon Finland&lt;/a&gt;) and Stockholm&#10;(&lt;a href="https://pycon.se/"&gt;PyCon Sweden&lt;/a&gt;)&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;On a discussion panel at one&lt;/li&gt;&#10;&lt;li&gt;Gave a lightning talk at five&lt;/li&gt;&#10;&lt;li&gt;Announced PyCon Finland at four&lt;/li&gt;&#10;&lt;li&gt;Helped new contributors at sprints at three&lt;/li&gt;&#10;&lt;li&gt;Hosted a barcamp session at one&lt;/li&gt;&#10;&lt;li&gt;Helped organise one by reviewing talks and through promotion&lt;/li&gt;&#10;&lt;li&gt;Volunteered at one&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Attended three online conferences:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;March: SustainOSS Virtual Forum&lt;/li&gt;&#10;&lt;li&gt;May: Maintainer Summit&lt;/li&gt;&#10;&lt;li&gt;December: PyLadiesCon&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Other events:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;September: &lt;a href="../../2025/python-core-sprint/"&gt;Core Team Sprint&lt;/a&gt; in Cambridge&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://open.spotify.com/episode/3zMQYlP7hF7fKi8wdlAxyn"&gt;Podcast interview&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;December:&#10;&lt;a href="https://www.sovereign.tech/news/who-will-maintain-our-digital-future"&gt;Sovereign Tech Agency event&lt;/a&gt;&#10;in Berlin to mark the inaugural Fellowship cohort&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Meetups:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Co-organised 16 meetups for &lt;a href="https://helsinki-python.github.io/"&gt;two&lt;/a&gt; groups,&#10;&lt;a href="https://pydata-helsinki.github.io/"&gt;one&lt;/a&gt; which we restarted in 2025&lt;/li&gt;&#10;&lt;li&gt;Attended 27 meetups of 11 groups in four cities and three countries&lt;/li&gt;&#10;&lt;li&gt;Gave one long talk and four lightning talks&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;a href="https://docs-community.readthedocs.io/community/index.html"&gt;Docs Working Group&lt;/a&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Organised 12&#10;&lt;a href="https://docs-community.readthedocs.io/monthly-meeting/index.html"&gt;monthly meetings&lt;/a&gt;,&#10;chaired 9, attended 10&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&lt;a href="https://github.com/psf/user-success-wg/blob/main/WG-charter.md"&gt;User Success Workgroup&lt;/a&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Attended two meetings&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Published 17 blog posts:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;February: &lt;a href="../../2025/how-to-delay-a-python-release/"&gt;How to delay a Python release&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;February:&#10;&lt;a href="../../2025/im-excited-to-join-the-sovereign-tech-fellowship/"&gt;I&amp;rsquo;m excited to join the Sovereign Tech Fellowship&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;February: &lt;a href="../../2025/improving-licence-metadata/"&gt;Improving licence metadata&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;March:&#10;&lt;a href="../../2025/free-threaded-python-on-github-actions/"&gt;Free-threaded Python on GitHub Actions&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;April:&#10;&lt;a href="../../2025/my-most-used-command-line-commands/"&gt;My most used command-line commands&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;May: &lt;a href="../../2025/peps-and-co/"&gt;PEPs &amp;amp; Co.&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;June: &lt;a href="../../2025/run-coverage-on-tests/"&gt;Run coverage on tests&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;August:&#10;&lt;a href="../../2025/europython-2025-a-roundup-of-writeups/"&gt;EuroPython 2025: A roundup of writeups&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;September: &lt;a href="../../2025/ready-prek-go/"&gt;Ready prek go!&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;October: &lt;a href="../../2025/releasing-python-3-14-0/"&gt;Releasing Python 3.14.0&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;October: &lt;a href="../../2025/lazy-imports/"&gt;Three times faster with lazy imports&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;November: &lt;a href="../../2025/python-core-sprint/"&gt;Python Core Sprint 2025&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;November: &lt;a href="../../2025/secrets-in-env-vars/"&gt;Setting secrets in env vars&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;December: &lt;a href="../../2025/steering-council-election/"&gt;Steering Council election&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;December: &lt;a href="../../2025/steering-council-results/"&gt;Steering Council results&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;December: &lt;a href="../../2025/and-now/"&gt;And now for something completely different&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;December: &lt;a href="../../2025/minus-six/"&gt;Replacing python-dateutil to remove six&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Reported 67 accounts to GitHub for spam/abuse/inauthentic activity.&lt;/p&gt;&#10;&lt;h2 id="2026-and-beyond" class="relative group"&gt;2026 and beyond &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="#2026-and-beyond" aria-label="Anchor"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h2&gt;&lt;p&gt;This time we&amp;rsquo;re 14 Fellows, and not only maintainers but also community managers and&#10;technical writers. It&amp;rsquo;s great that Python core dev&#10;&lt;a href="https://stan.ulbrych.org/"&gt;Stan Ulbrych&lt;/a&gt; and PSF director&#10;&lt;a href="https://georgiker.com/"&gt;Georgi Ker&lt;/a&gt; are also joining, and I&amp;rsquo;m looking forward to&#10;meeting the other&#10;&lt;a href="https://www.sovereign.tech/news/meet-the-2026-sovereign-tech-fellows"&gt;Sovereign Tech Fellows&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m really pleased to again be working with the Sovereign Tech Agency. They&amp;rsquo;re showing&#10;the world some of the ways we can improve open source and critical digital&#10;infrastructure, through a range of different&#10;&lt;a href="https://www.sovereign.tech/programs"&gt;programmes&lt;/a&gt;. Their success has informed the&#10;proposal for an &lt;a href="https://eu-stf.openforumeurope.org/"&gt;EU Sovereign Tech Fund (EU-STF)&lt;/a&gt;,&#10;and they have also helped shape the&#10;&lt;a href="https://digital-commons-edic.eu/"&gt;European Digital Infrastructure Consortium for digital commons (DC-EDIC)&lt;/a&gt;,&#10;with an &lt;a href="https://digital-commons-edic.eu/projects/#eu-stf"&gt;EU-STF pilot&lt;/a&gt; kicking off&#10;later this month which builds on their experience. And it&amp;rsquo;s good to see the focus on&#10;maintenance and long-term sustainability in the brand new&#10;&lt;a href="https://digital-strategy.ec.europa.eu/en/policies/open-source-strategy"&gt;EU Open Source Strategy&lt;/a&gt;,&#10;announced just last week.&lt;/p&gt;&#10;&lt;p&gt;And by the way, the Sovereign Tech Agency are currently hiring, check out their&#10;&lt;a href="https://www.sovereign.tech/jobs"&gt;open positions&lt;/a&gt;.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo by &lt;a href="https://www.janmichalko.com/"&gt;Jan Michalko&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</description></item><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;&#10;&lt;a href="https://github.com/python/cpython/issues/144900"&gt;get&lt;/a&gt;&#10;&lt;a href="https://github.com/python/cpython/issues/144899"&gt;a&lt;/a&gt;&#10;&lt;a href="https://github.com/python/cpython/pull/144806"&gt;lot&lt;/a&gt;&#10;&lt;a href="https://github.com/python/cpython/issues/144671"&gt;of&lt;/a&gt;&#10;&lt;a href="https://github.com/python/cpython/issues/144654"&gt;spam&lt;/a&gt;&#10;&lt;a href="https://github.com/python/cpython/issues/144468"&gt;in&lt;/a&gt;&#10;&lt;a href="https://github.com/python/cpython/pull/144352"&gt;the&lt;/a&gt;&#10;&lt;a href="https://github.com/python/cpython/issues/144344"&gt;CPython&lt;/a&gt;&#10;&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;&#10;&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&#10;mostly worthless &amp;ldquo;nothing&amp;rdquo; issues and PRs that barely fill in the issue template, or add&#10;a line of nonsense to some arbitrary file.&lt;/p&gt;&#10;&lt;p&gt;They&amp;rsquo;re often from new accounts with usernames like:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;za9066559-wq&lt;/li&gt;&#10;&lt;li&gt;quanghuynh10111-png&lt;/li&gt;&#10;&lt;li&gt;riffocristobal579-cmd&lt;/li&gt;&#10;&lt;li&gt;sajjad5giot&lt;/li&gt;&#10;&lt;li&gt;satyamchoudhary1430-boop&lt;/li&gt;&#10;&lt;li&gt;SilaMey&lt;/li&gt;&#10;&lt;li&gt;standaell1234-maker&lt;/li&gt;&#10;&lt;li&gt;eedamhmd2005-ui&lt;/li&gt;&#10;&lt;li&gt;ksdmyanmar-lighter&lt;/li&gt;&#10;&lt;li&gt;experments-studios&lt;/li&gt;&#10;&lt;li&gt;madurangarathanayaka5-art&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;A new issue from a username following the pattern &lt;code&gt;nameNNNN-short_suffix&lt;/code&gt; is a dead&#10;giveaway. I think they&amp;rsquo;re trying to farm &amp;ldquo;realistic&amp;rdquo; accounts: open a PR, open an issue,&#10;comment on something, make a fake review.&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s easy but tedious to:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;close the PR/issue as not planned&lt;/li&gt;&#10;&lt;li&gt;retitle to &amp;ldquo;spam&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;apply the &amp;ldquo;invalid&amp;rdquo; label&lt;/li&gt;&#10;&lt;li&gt;remove other labels&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&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),&#10;and it&amp;rsquo;s straightforward to write your own Python-based extensions, so I wrote&#10;&lt;a href="https://github.com/hugovk/gh-triage"&gt;&lt;code&gt;gh triage&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Install:&lt;/p&gt;&#10;&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&#10;&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;...&#10;&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.&#10;&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.&#10;&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.&#10;&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)&#10;&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.&#10;&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.&#10;&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&#10;&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;&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&amp;ldquo;spam&amp;rdquo; labels, but only if they exist in the repo.&lt;/p&gt;&#10;&lt;p&gt;Next step: perhaps it could print out the URL to make it easy to report the account to&#10;GitHub (usually for &amp;ldquo;Spam or inauthentic Activity&amp;rdquo;).&lt;/p&gt;&#10;&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;&#10;&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&#10;that ends up bringing in lots of code changes. This often assigns the PR to dozens of&#10;people via&#10;&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;,&#10;for example:&#10;&lt;a href="https://github.com/python/cpython/pull/142564#event-21644515910"&gt;python/cpython#142564&lt;/a&gt;.&lt;/p&gt;&#10;&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,&#10;but we can automate unassigning them all so at least the PR is not in their &amp;ldquo;assigned&#10;to&amp;rdquo; list.&lt;/p&gt;&#10;&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;&#10;&lt;ul&gt;&#10;&lt;li&gt;remove all assignees (issues and PRs)&lt;/li&gt;&#10;&lt;li&gt;remove all requested reviewers (PRs only)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;For example:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;li&gt;Adam Johnson&amp;rsquo;s&#10;&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;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&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;,&#10;originally published 1888, from the&#10;&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;&#10;</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&#10;&lt;a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-sampling-profiler"&gt;Tachyon&lt;/a&gt;,&#10;the new &amp;ldquo;high-frequency statistical sampling profiler&amp;rdquo; coming in&#10;&lt;a href="https://www.python.org/downloads/latest/python3.15/"&gt;Python 3.15&lt;/a&gt;, to see if we can&#10;speed up the Pillow imaging library. I started with a simple script to open an image:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then ran:&lt;/p&gt;&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &lt;picture class="mx-auto my-0 rounded-md" &gt;&#10; &lt;img&#10; src="https://hugovk.dev/blog/2026/faster-pillow/flamegraph-png.svg"&#10; width="1158"&#10; height="620"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Flame graph for opening a PNG with Pillow"&#10; loading="lazy" decoding="async"&#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;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&#10;&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;&#10;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;&#10;&lt;p&gt;Do we really need to import all those plugins when we&amp;rsquo;re only interested in PNG?&lt;/p&gt;&#10;&lt;p&gt;Okay, let&amp;rsquo;s try another kind of image:&lt;/p&gt;&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &lt;picture class="mx-auto my-0 rounded-md" &gt;&#10; &lt;img&#10; src="https://hugovk.dev/blog/2026/faster-pillow/flamegraph-webp.svg"&#10; width="1158"&#10; height="620"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Flame graph for opening a WebP with Pillow"&#10; loading="lazy" decoding="async"&#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;&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;,&#10;&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&#10;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;&#10;&lt;p&gt;Again, why import &lt;em&gt;even more&lt;/em&gt; plugins when we only care about WebP?&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;When&#10;&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&#10;or&#10;&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&#10;an image, if Pillow isn&amp;rsquo;t yet initialised, we call a&#10;&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;&#10;function. This loads five drivers for five formats by importing their plugins: BMP, GIF,&#10;JPEG, PPM and PNG.&lt;/p&gt;&#10;&lt;p&gt;During import, each plugin&#10;&lt;a href="https://github.com/python-pillow/Pillow/blob/12.1.0/src/PIL/GifImagePlugin.py#L1204-L1211"&gt;registers&lt;/a&gt;&#10;its file extensions, MIME types and some methods used for opening and saving.&lt;/p&gt;&#10;&lt;p&gt;Then we check each of these plugins in turn to see if one will accept the image. Most of&#10;Pillow&amp;rsquo;s plugins detect an image by opening the file and checking if the first few bytes&#10;match a magic prefix. For example:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;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;&#10;&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;&#10;starts with &lt;code&gt;b&amp;quot;\211PNG\r\n\032\n&amp;quot;&lt;/code&gt;&#10;(&lt;a href="https://www.w3.org/TR/PNG-Rationale.html#R.PNG-file-signature"&gt;reference&lt;/a&gt;).&lt;/li&gt;&#10;&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;&#10;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&#10;the start of the next marker&#10;(&lt;a href="https://en.wikipedia.org/wiki/JPEG#Syntax_and_structure"&gt;reference&lt;/a&gt;).&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;If none of these five match, we call&#10;&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;,&#10;which imports the remaining 42 plugins. We then check each of these for a match.&lt;/p&gt;&#10;&lt;p&gt;This has been the case since at least&#10;&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;&#10;released in 2000 (this is the oldest version I have to check). There were 33 builtin&#10;plugins then and 47 now.&lt;/p&gt;&#10;&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&#10;lifetime, especially for things like CLIs. Longer running programs may need a few more,&#10;but unlikely all 47.&lt;/p&gt;&#10;&lt;p&gt;A benefit of the plugin system is third parties can&#10;&lt;a href="https://pillow.readthedocs.io/en/stable/handbook/third-party-plugins.html"&gt;create their own plugins&lt;/a&gt;,&#10;but we can be more efficient with our builtins.&lt;/p&gt;&#10;&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&#10;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&#10;cheap lookup, which may save us importing, registering, and checking all those plugins.&lt;/p&gt;&#10;&lt;p&gt;Of course, we may have an image without an extension, or with the &amp;ldquo;wrong&amp;rdquo; extension, but&#10;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;&#10;-&amp;gt; &lt;code&gt;init()&lt;/code&gt; flow.&lt;/p&gt;&#10;&lt;p&gt;After merging the PR, here&amp;rsquo;s a new flame graph for opening PNG&#10;(&lt;a href="flamegraph_27477.html"&gt;HTML page&lt;/a&gt;):&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &lt;picture class="mx-auto my-0 rounded-md" &gt;&#10; &lt;img&#10; src="https://hugovk.dev/blog/2026/faster-pillow/flamegraph-png2.svg"&#10; width="1158"&#10; height="580"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Much less compressed flame graph showing less work"&#10; loading="lazy" decoding="async"&#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;And for WebP (&lt;a href="flamegraph_26821.html"&gt;HTML page&lt;/a&gt;):&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &lt;picture class="mx-auto my-0 rounded-md" &gt;&#10; &lt;img&#10; src="https://hugovk.dev/blog/2026/faster-pillow/flamegraph-webp2.svg"&#10; width="1158"&#10; height="420"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Much less compressed for WebP"&#10; loading="lazy" decoding="async"&#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;The flame graphs are scaled to the same width, but there&amp;rsquo;s far fewer boxes meaning&#10;there&amp;rsquo;s much less work now. We&amp;rsquo;re down from 40 and 60 milliseconds to 20 and 20&#10;milliseconds.&lt;/p&gt;&#10;&lt;p&gt;The PR has a bunch of benchmarks which show opening a PNG (that previously loaded five&#10;plugins) is now 2.6 times faster. Opening a WebP (that previously loaded all 47&#10;plugins), is now 14 times faster. Similarly, Saving PNG is improved by 2.2 times and&#10;WebP by 7.9 times. Success! This will be in Pillow 12.2.0.&lt;/p&gt;&#10;&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;&#10;&lt;li&gt;&#10;&lt;p&gt;Henry Schreiner on&#10;&lt;a href="https://iscinumpy.dev/post/packaging-faster/"&gt;making packaging faster&lt;/a&gt;.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&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&#10;another new tool which is useful for things like this.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</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&#10;things coming in &lt;a href="https://hugovk.dev/blog/2025/and-now/"&gt;Python 3.14&lt;/a&gt;, including&#10;&lt;a href="https://t-strings.help/"&gt;template strings&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;One thing we can use t-strings for is to prevent SQL injection. The user gives you an&#10;untrusted t-string, and you can sanitise it, before using it in a safer way.&lt;/p&gt;&#10;&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;,&#10;but commonly known as &amp;ldquo;Little Bobby Tables&amp;rdquo;.&lt;/p&gt;&#10;&lt;p&gt;I localised most of the slides for the PyCon I was at, including this comic. Here they&#10;are!&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_28f444017a20f626.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_e5d7409854dfeb32.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1480"&#10; height="455"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Did you really name your son Roberto&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Piccolo Berto Tables, we call him."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_983cee40d8a7df8a.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/italia_hu_636e648764ba49c0.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_a6fa45b89775c954.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_f7a86970f8e419a5.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1480"&#10; height="455"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Did you really name your son Κωνσταντίνος&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Μικρός Ντίνος Tables, we call him."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_e5f987a1869d3d8.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/greece_hu_df9734df0a3b8cf2.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_9ec1ae1dbd122955.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_1f02e918756eeb05.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1480"&#10; height="456"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Did you really name your son Raivo&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Pisikene Tables, we call him."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_6d311c9260c8fca5.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/estonia_hu_c0d9cd8c668d3add.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_c514fb93cbf48753.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_f846147908bd5e4f.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1480"&#10; height="456"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Did you really name your son Juhani&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Pikku Jussi, we call him."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_d85823f003af379b.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/finland_hu_e0daf49d92b129d2.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_c69d817d779ef38e.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_3a53e97ac889d9cb.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1480"&#10; height="456"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Did you really name your son Oskar&amp;rsquo;); DROP TABLE Students;&amp;ndash; ? Oh, yes. Lille Ogge Bord, we call him."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_b169ef5e792c325d.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2026/localising-xkcd/sweden_hu_1940d36e84c40c54.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;&lt;a href="https://creativecommons.org/licenses/by-nc/2.5/"&gt;Creative Commons Attribution-NonCommercial 2.5 License&lt;/a&gt;.&#10;These adaptations are therefore licensed the same way.&lt;/p&gt;&#10;&lt;p&gt;Finally, here&amp;rsquo;s links for 2026, I recommend them all:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://2026.pycon.it/en"&gt;PyCon Italia, 27-30 May&lt;/a&gt;: the&#10;&lt;a href="https://2026.pycon.it/en/call-for-proposals"&gt;CFP&lt;/a&gt; is open until 6th January&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pycon.ee/"&gt;PyCon Estonia, 8-9 October&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://2026.pycon.gr/"&gt;PyCon Greece, 12-13 October&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pycon.se/"&gt;PyCon Sweden, TBA&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="http://fi.pycon.org/"&gt;PyCon Finland, TBA&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</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&#10;times.&lt;/p&gt;&#10;&lt;p&gt;However, it still supports Python 2.7 by&#10;&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;,&#10;and I&amp;rsquo;d prefer not to install for Python 3.10 and higher.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s how I replaced three uses of its&#10;&lt;a href="https://dateutil.readthedocs.io/en/stable/relativedelta.html"&gt;&lt;code&gt;relativedelta&lt;/code&gt;&lt;/a&gt; in a&#10;couple of CLIs that didn&amp;rsquo;t really need to use it.&lt;/p&gt;&#10;&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&#10;calculate six months from now:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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&#10;library&amp;rsquo;s&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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&#10;of a month:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Instead:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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&#10;&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;&#10;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&#10;which is the class.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&lt;a href="https://archive.org/details/lfaaustria0001/LFA_Austria_0001_016.jpg"&gt;Ver Sacrum calendar&lt;/a&gt;&#10;by &lt;a href="https://www.museumoo.com/Libri/Ver-Sacrum-Kalender"&gt;Alfred Roller&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;&lt;a href="https://www.python.org/downloads/release/python-390/"&gt;3.9&lt;/a&gt; release manager Łukasz Langa&#10;added a new section to the release notes called &amp;ldquo;And now for something completely&#10;different&amp;rdquo; with a sketch transcript from Monty Python.&lt;/p&gt;&#10;&lt;p&gt;For Python&#10;&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;&#10;and&#10;&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;,&#10;the next release manager Pablo Galindo Salgado continued the section but included&#10;astrophysics facts.&lt;/p&gt;&#10;&lt;p&gt;For Python&#10;&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;,&#10;the next RM Thomas Wouters shared poems (and took a break for 3.13).&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Here&amp;rsquo;s a collection of my different things for the first year (and a bit) of Python&#10;3.14.&lt;/p&gt;&#10;&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;&#10;&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&#10;circumference to its diameter. It is an irrational number, which means it cannot be&#10;written as a simple fraction of two integers. When written as a decimal, its digits go&#10;on forever without ever repeating a pattern.&lt;/p&gt;&#10;&lt;p&gt;Here’s 76 digits of &lt;em&gt;π&lt;/em&gt;:&lt;/p&gt;&#10;&lt;p&gt;3.141592653589793238462643383279502884197169399375105820974944592307816406286&lt;/p&gt;&#10;&lt;p&gt;Piphilology is the creation of mnemonics to help remember digits of &lt;em&gt;π&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;In a pi-poem, or “piem”, the number of letters in a word equal the corresponding digit.&#10;This covers 9 digits, 3.14159265:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;em&gt;How I wish I could recollect pi easily today!&lt;/em&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;One of the most well-known covers 15 digits, 3.14159265358979:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;em&gt;How I want a drink, alcoholic of course, after the heavy chapters involving quantum&#10;mechanics!&lt;/em&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Here’s a 35-word piem in the shape of a circle, 3.1415926535897932384626433832795728:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&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&#10;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&#10;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;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;The Guinness World Record for memorising the most digits is held by Rajveer Meena, who&#10;recited 70,000 digits blindfold in 2015. The unofficial record is held by Akira&#10;Haraguchi who recited 100,000 digits in 2006.&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Ludolph van Ceulen (1540-1610) was a fencing and mathematics teacher in Leiden,&#10;Netherlands, and spent around 25 years calculating &lt;em&gt;π&lt;/em&gt; (or pi), using essentially the&#10;same methods Archimedes employed some seventeen hundred years earlier.&lt;/p&gt;&#10;&lt;p&gt;Archimedes estimated &lt;em&gt;π&lt;/em&gt; by calculating the circumferences of polygons that fit just&#10;inside and outside of a circle, reasoning the circumference of the circle lies between&#10;these two values. Archimedes went up to polygons with 96 sides, for a value between&#10;3.1408 and 3.1428, which is accurate to two decimal places.&lt;/p&gt;&#10;&lt;p&gt;Van Ceulen used a polygon with half a billion sides. He published a 20-decimal value in&#10;his 1596 book&#10;&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;&#10;(&lt;a href="https://www.lindahall.org/about/news/scientist-of-the-day/ludolph-van-ceulen/"&gt;“On the Circle”&lt;/a&gt;),&#10;and later expanded it to 35 decimals:&lt;/p&gt;&#10;&lt;p&gt;3.14159265358979323846264338327950288&lt;/p&gt;&#10;&lt;p&gt;Van Ceulen’s 20 digits is more than enough precision for any conceivable practical&#10;purpose. For example, even if a printed circle was perfect down to the atomic scale, the&#10;thermal vibrations of the molecules of ink would make most of those digits physically&#10;meaningless. NASA Jet Propulsion Laboratory’s highest accuracy calculations, for&#10;interplanetary navigation, uses 15 decimals: 3.141592653589793.&lt;/p&gt;&#10;&lt;p&gt;At Van Ceulen’s request, his upper and lower bounds for &lt;em&gt;π&lt;/em&gt; were engraved on his&#10;&lt;a href="https://www.atlasobscura.com/places/ludolph-van-ceulen-memorial-leiden"&gt;tombstone in Leiden&lt;/a&gt;.&#10;The tombstone was eventually lost but restored in 2000. In the Netherlands and Germany,&#10;π is sometimes referred to as the “Ludolphine number”, after Van Ceulen.&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;A mince pie is a small, round covered tart filled with “mincemeat”, usually eaten during&#10;the Christmas season – the UK consumes some 800 million each Christmas. Mincemeat is a&#10;mixture of things like apple, dried fruits, candied peel and spices, and originally&#10;would have contained meat chopped small, but rarely nowadays. They are often served warm&#10;with brandy butter.&lt;/p&gt;&#10;&lt;p&gt;According to the Oxford English Dictionary, the earliest mention of Christmas mince pies&#10;is by Thomas Dekker, writing in the aftermath of the&#10;&lt;a href="https://en.wikipedia.org/wiki/1603_London_plague"&gt;1603 London plague&lt;/a&gt;, in&#10;&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;&#10;(1604):&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Ten thousand in London swore to feast their neighbors with nothing but plum-porredge,&#10;and mince-pyes all Christmas.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Here’s a meaty recipe from&#10;&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;&#10;(1678):&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;XV. &lt;em&gt;How to make Mince-pies.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;To every pound of Meat, take two pound of beef Suet, a pound of Corrants, and a&#10;quarter of an Ounce of Cinnamon, one Nutmeg, a little beaten Mace, some beaten Colves,&#10;a little Sack &amp;amp; Rose-water, two large Pippins, some Orange and Lemon peel cut very&#10;thin, and shred very small, a few beaten Carraway-seeds, if you love them the Juyce of&#10;half a Lemon squez’d into this quantity of meat; for Sugar, sweeten it to your relish;&#10;then mix all these together and fill your Pie. The best meat for Pies is&#10;Neats-Tongues, or a leg of Veal; you may make them of a leg of Mutton if you please;&#10;the meat must be parboyl’d if you do not spend it presently; but if it be for present&#10;use, you may do it raw, and the Pies will be the better.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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;&#10;&lt;p&gt;In Python, you can use Greek letters as constants. For example:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&lt;p&gt;2025-01-29 marked the start of a new lunar year, the Year of the Snake :snake: (and the&#10;Year of Python?).&lt;/p&gt;&#10;&lt;p&gt;For centuries, &lt;em&gt;π&lt;/em&gt; was often approximated as 3 in China. Some time between the years 1&#10;and 5 CE, astronomer, librarian, mathematician and politician Liu Xin (劉歆) calculated&#10;&lt;em&gt;π&lt;/em&gt; as 3.154.&lt;/p&gt;&#10;&lt;p&gt;Around 130 CE, mathematician, astronomer, and geographer Zhang Heng (張衡, 78–139)&#10;compared the celestial circle with the diameter of the earth as 736:232 to get 3.1724.&#10;He also came up with a formula for the ratio between a cube and inscribed sphere as 8:5,&#10;implying the ratio of a square’s area to an inscribed circle is √8:√5. From this, he&#10;calculated &lt;em&gt;π&lt;/em&gt; as √10 (~3.162).&lt;/p&gt;&#10;&lt;p&gt;Third century mathematician Liu Hui (刘徽) came up with an algorithm for calculating &lt;em&gt;π&lt;/em&gt;&#10;iteratively: calculate the area of a polygon inscribed in a circle, then as the number&#10;of sides of the polygon is increased, the area becomes closer to that of the circle,&#10;from which you can approximate &lt;em&gt;π&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;This algorithm is similar to the method used by Archimedes in the 3rd century BCE and&#10;Ludolph van Ceulen in the 16th century CE (see&#10;&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;),&#10;but Archimedes only went up to a 96-sided polygon (96-gon). Liu Hui went up to a 192-gon&#10;to approximate &lt;em&gt;π&lt;/em&gt; as 157/50 (3.14) and later a 3072-gon for 3.14159.&lt;/p&gt;&#10;&lt;p&gt;Liu Hu wrote a commentary on the book The Nine Chapters on the Mathematical Art which&#10;included his &lt;em&gt;π&lt;/em&gt; approximations.&lt;/p&gt;&#10;&lt;p&gt;In the fifth century, astronomer, inventor, mathematician, politician, and writer Zu&#10;Chongzhi (祖沖之, 429–500) used Liu Hui’s algorithm to inscribe a 12,288-gon to compute&#10;&lt;em&gt;π&lt;/em&gt; between 3.1415926 and 3.1415927, correct to seven decimal places. This was more&#10;accurate than Hellenistic calculations and wouldn’t be improved upon for 900 years.&lt;/p&gt;&#10;&lt;p&gt;Happy Year of the Snake!&lt;/p&gt;&#10;&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;&#10;&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&#10;approximation of &lt;em&gt;π&lt;/em&gt;. The day is observed by eating pies (savoury and/or sweet) and&#10;celebrating &lt;em&gt;π&lt;/em&gt;. The first pi day was organised by physicist and tinkerer Larry Shaw of&#10;the San Francisco &lt;a href="https://annex.exploratorium.edu/learning_studio/pi/"&gt;Exploratorium&lt;/a&gt;&#10;in 1988. It is also the &lt;a href="https://www.idm314.org/"&gt;International Day of Mathematics&lt;/a&gt; and&#10;Albert Einstein’s birthday. Let’s all eat some pie, recite some &lt;em&gt;π&lt;/em&gt;, install and test&#10;some py, and wish a happy birthday to Albert, Loren and all the other pi day children!&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;On Saturday, 5th April, 3.141592653589793 months of the year had elapsed.&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;The mathematical constant pi is represented by the Greek letter &lt;em&gt;π&lt;/em&gt; and represents the&#10;ratio of a circle’s circumference to its diameter. The first person to use &lt;em&gt;π&lt;/em&gt; as a&#10;symbol for this ratio was Welsh self-taught mathematician William Jones in 1706. He was&#10;a farmer’s son born in Llanfihangel Tre’r Beirdd on Angelsy (Ynys Môn) in 1675 and only&#10;received a basic education at a local charity school. However, the owner of his parents’&#10;farm noticed his mathematical ability and arranged for him to move to London to work in&#10;a bank.&lt;/p&gt;&#10;&lt;p&gt;By age 20, he served at sea in the Royal Navy, teaching sailors mathematics and helping&#10;with the ship’s navigation. On return to London seven years later, he became a maths&#10;teacher in coffee houses and a private tutor. In 1706, Jones published &lt;em&gt;Synopsis&#10;Palmariorum Matheseos&lt;/em&gt; which used the symbol &lt;em&gt;π&lt;/em&gt; for the ratio of a circle’s&#10;circumference to diameter (hunt for it on pages&#10;&lt;a href="https://archive.org/details/SynopsisPalmariorumMatheseosOrANewIntroductionToTheMathematics/page/n261/mode/1up?view=theater"&gt;243&lt;/a&gt;&#10;and&#10;&lt;a href="https://archive.org/details/SynopsisPalmariorumMatheseosOrANewIntroductionToTheMathematics/page/n283/mode/1up?view=theater"&gt;263&lt;/a&gt;&#10;or&#10;&lt;a href="https://commons.wikimedia.org/wiki/File:Synopsis_Palmariorum_Matheseos_pi.jpg"&gt;here&lt;/a&gt;).&#10;Jones was also the first person to realise &lt;em&gt;π&lt;/em&gt; is an irrational number, meaning it can&#10;be written as decimal number that goes on forever, but cannot be written as a fraction&#10;of two integers.&lt;/p&gt;&#10;&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&#10;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&#10;wasn’t the first to use it in as part of the ratio. William Oughtred, in his 1631&#10;&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&#10;call pi. His &lt;em&gt;π&lt;/em&gt; was the circumference, not the ratio of circumference to diameter.&#10;James Gregory, in his 1668 &lt;em&gt;Geometriae Pars Universalis&lt;/em&gt; (&lt;em&gt;The Universal Part of&#10;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&#10;&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&#10;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;&#10;&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;&#10;&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;&#10;&lt;p&gt;Of course, it’s not that simple.&lt;/p&gt;&#10;&lt;p&gt;Edwin J. Goodwin, M.D., claimed to have come up with a solution to an ancient&#10;geometrical problem called squaring the circle, first proposed in Greek mathematics. It&#10;involves trying to draw a circle and a square with the same area, using only a compass&#10;and a straight edge. It turns out to be impossible because &lt;em&gt;π&lt;/em&gt; is transcendental (and&#10;this had been proved just 13 years earlier by Ferdinand von Lindemann), but Goodwin&#10;fudged things so the value of &lt;em&gt;π&lt;/em&gt; was 3.2 (his writings have included at least nine&#10;different values of &lt;em&gt;π&lt;/em&gt;: including 4, 3.236, 3.232, 3.2325… and even 9.2376…).&lt;/p&gt;&#10;&lt;p&gt;Goodwin had copyrighted his proof and offered it to the State of Indiana to use in their&#10;educational textbooks without paying royalties, provided they endorsed it. And so&#10;Indiana Bill No. 246 was introduced to the House on 18th January 1897. It was not&#10;understood and initially referred to the House Committee on Canals, also called the&#10;Committee on Swamp Lands. They then referred it to the Committee on Education, who duly&#10;recommended on 2nd February that “said bill do pass”. It passed its second reading on&#10;the 5th and the education chair moved that they suspend the constitutional rule that&#10;required bills to be read on three separate days. This passed 72-0, and the bill itself&#10;passed 67-0.&lt;/p&gt;&#10;&lt;p&gt;The bill was referred to the Senate on 10th February, had its first reading on the 11th,&#10;and was referred to the Committee on Temperance, whose chair on the 12th recommended&#10;“that said bill do pass”.&lt;/p&gt;&#10;&lt;p&gt;A mathematics professor,&#10;&lt;a href="https://www.biodiversitylibrary.org/page/14641808#page/455/mode/1up"&gt;Clarence Abiathar Waldo&lt;/a&gt;,&#10;happened to be in the State Capitol on the day the House passed the bill and walked in&#10;during the debate to hear an ex-teacher argue:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;The case is perfectly simple. If we pass this bill which establishes a new and correct&#10;value for pi , the author offers to our state without cost the use of his discovery&#10;and its free publication in our school text books, while everyone else must pay him a&#10;royalty.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Waldo ensured the senators were “properly coached”; and on the 12th, during the second&#10;reading, after an unsuccessful attempt to amend the bill it was postponed indefinitely.&#10;But not before the senators had some fun.&lt;/p&gt;&#10;&lt;p&gt;The Indiana News reported on the 13th:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;…the bill was brought up and made fun of. The Senators made bad puns about it,&#10;ridiculed it and laughed over it. The fun lasted half an hour. Senator Hubbell said&#10;that it was not meet for the Senate, which was costing the State $250 a day, to waste&#10;its time in such frivolity. He said that in reading the leading newspapers of Chicago&#10;and the East, he found that the Indiana State Legislature had laid itself open to&#10;ridicule by the action already taken on the bill. He thought consideration of such a&#10;proposition was not dignified or worthy of the Senate. He moved the indefinite&#10;postponement of the bill, and the motion carried.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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;&#10;&lt;p&gt;If you’re heading out to sea, remember the&#10;&lt;a href="https://xkcd.com/3023/"&gt;Maritime Approximation&lt;/a&gt;:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;em&gt;π&lt;/em&gt; mph = &lt;em&gt;e&lt;/em&gt; knots&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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;&#10;&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;&#10;(June 28th, 6/28 in the US) celebrates &lt;em&gt;τ&lt;/em&gt; as the “true circle constant”, as the ratio&#10;of a circle’s circumference to its radius, &lt;em&gt;C/r&lt;/em&gt; = 6.283185… The&#10;&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&#10;unnatural choice for the circle constant”, in part because “2&lt;em&gt;π&lt;/em&gt; occurs with astonishing&#10;frequency throughout mathematics”.&lt;/p&gt;&#10;&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;&#10;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&#10;in 2016:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;When working with radians, it is trivial to convert any given fraction of a circle to&#10;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&#10;&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&#10;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&#10;multiplication by two is gone.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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;&#10;&lt;p&gt;Today, 22nd July, is Pi Approximation Day, because 22/7 is a common approximation of &lt;em&gt;π&lt;/em&gt;&#10;and closer to &lt;em&gt;π&lt;/em&gt; than 3.14.&lt;/p&gt;&#10;&lt;p&gt;22/7 is a Diophantine approximation, named after Diophantus of Alexandria (3rd century&#10;CE), which is a way of estimating a real number as a ratio of two integers. 22/7 has&#10;been known since antiquity; Archimedes (3rd century BCE) wrote the first known proof&#10;that 22/7 overestimates &lt;em&gt;π&lt;/em&gt; by comparing 96-sided polygons to the circle it&#10;circumscribes.&lt;/p&gt;&#10;&lt;p&gt;Another approximation is 355/113. In Chinese mathematics, 22/7 and 355/113 are&#10;respectively known as Yuelü (约率; yuēlǜ; “approximate ratio”) and Milü (密率; mìlǜ;&#10;“close ratio”).&lt;/p&gt;&#10;&lt;p&gt;Happy &lt;a href="https://piapproximationday.com/"&gt;Pi Approximation Day&lt;/a&gt;!&lt;/p&gt;&#10;&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;&#10;&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&#10;for its chattering call.&lt;/p&gt;&#10;&lt;p&gt;The first-known use in English is from a&#10;&lt;a href="https://archive.org/details/transactionscong5191cong/page/360/mode/2up?q=magpy"&gt;1589 poem&lt;/a&gt;,&#10;where magpie is spelled “magpy” and cuckoo is “cookow”:&lt;/p&gt;&#10;&lt;p&gt;&lt;em&gt;Th[e]y fly to wood like breeding hauke, And leave old neighbours loue, They pearch&#10;themselves in syluane lodge, And soare in th’ aire aboue. There : magpy teacheth them to&#10;chat, And cookow soone doth hit them pat.&lt;/em&gt;&lt;/p&gt;&#10;&lt;p&gt;The name comes from Mag, short for Margery or Margaret (compare robin redbreast, jenny&#10;wren, and its corvid relative jackdaw); and pie, a magpie or other bird with black and&#10;white (or pied) plumage. The sea-pie (1552) is the oystercatcher, the grey pie (1678)&#10;and murdering pie (1688) is the great grey shrike. Others birds include the yellow and&#10;black pie, red-billed pie, wandering tree-pie, and river pie. The rain-pie, wood-pie and&#10;French pie are woodpeckers.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;p&gt;According to Pablo Galindo Salgado at&#10;&lt;a href="https://www.youtube.com/live/uTu4H3cynOI?t=29965s"&gt;PyCon Greece&lt;/a&gt;:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;There are things that are supercool indeed, like for instance, this is one of the&#10;results that I&amp;rsquo;m more proud about. This equation over here, which you don&amp;rsquo;t need to&#10;understand, you don&amp;rsquo;t need to be scared about, but this equation here tells what is&#10;the maximum time that it takes for a ray of light to fall into a black hole. And as&#10;you can see the math is quite complicated but the answer is quite simple: it&amp;rsquo;s 2π&#10;times the mass of the black hole. So if you normalise by the mass of the black hole,&#10;the answer is 2π. And because there is nothing specific about your election of things&#10;in this formula, this formula is universal. It means it doesn&amp;rsquo;t depend on anything&#10;other than nature itself. Which means that you can use this as a definition of π. This&#10;is a valid alternative definition of the number π. It&amp;rsquo;s literally half the maximum&#10;time it takes to fall into a black hole, which is kind of crazy. So next time someone&#10;asks you what π means you can just drop this thing and impress them quite a lot. Maybe&#10;Hugo could use this information to put it into the release notes of πthon [yes, I&#10;can, thank you!].&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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;&#10;&lt;p&gt;Edgar Allen Poe died on 7th October 1849.&lt;/p&gt;&#10;&lt;p&gt;As we all recall from&#10;&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;,&#10;piphilology is the creation of mnemonics to help memorise the digits of &lt;em&gt;π&lt;/em&gt;, and the&#10;number of letters in each word in a pi-poem (or “piem”) successively correspond to the&#10;digits of &lt;em&gt;π&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p&gt;In 1995, Mike Keith, an American mathematician and author of constrained writing, retold&#10;Poe’s &lt;em&gt;The Raven&lt;/em&gt; as a 740-word piem. Here’s the first two stanzas of&#10;&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;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;&lt;strong&gt;Poe, E. Near a Raven&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;Midnights so dreary, tired and weary. Silently pondering volumes extolling all by-now&#10;obsolete lore. During my rather long nap - the weirdest tap! An ominous vibrating&#10;sound disturbing my chamber’s antedoor. “This”, I whispered quietly, “I ignore”.&lt;/p&gt;&#10;&lt;p&gt;Perfectly, the intellect remembers: the ghostly fires, a glittering ember. Inflamed by&#10;lightning’s outbursts, windows cast penumbras upon this floor. Sorrowful, as one&#10;mistreated, unhappy thoughts I heeded: That inimitable lesson in elegance - Lenore -&#10;Is delighting, exciting…nevermore.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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;&#10;&lt;p&gt;Seki Takakazu (関 孝和; c. March 1642 – December 5, 1708) was a Japanese mathematician&#10;and samurai who laid the foundations of Japanese mathematics, later known as &lt;em&gt;wasan&lt;/em&gt;&#10;(和算, from &lt;em&gt;wa&lt;/em&gt; (“Japanese”) and &lt;em&gt;san&lt;/em&gt; (“calculation”).&lt;/p&gt;&#10;&lt;p&gt;Seki was a contemporary of Isaac Newton and Gottfried Leibniz but worked independently.&#10;He created a new algebraic system, worked on infinitesimal calculus, and is credited&#10;with the discovery of Bernoulli numbers (before Bernoulli’s birth).&lt;/p&gt;&#10;&lt;p&gt;Seki also&#10;&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&#10;polygon with 131,072 sides inscribed within a circle, using an acceleration method now&#10;known as Aitken’s delta-squared process, which was rediscovered by Alexander Aitken&#10;in 1926.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&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&#10;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;&#10;</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&#10;&lt;a href="https://discuss.python.org/t/steering-council-election-results-2026-term/105296/?u=hugovk"&gt;results are in&lt;/a&gt;&#10;and congratulations to the new Python Steering Council!&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://bsky.app/profile/pumpichank.bsky.social/post/3m7vgyr7q5s23"&gt;Barry Warsaw&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://bsky.app/profile/corona10.bsky.social/post/3m7uuvphz3c2j"&gt;Donghee Na&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://bsky.app/profile/pablogsal.com/post/3m7uyldewf22p"&gt;Pablo Galindo Salgado&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://fosstodon.org/@savannah/115712735914446109"&gt;Savannah Ostrowski&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://social.coop/@Yhg1s"&gt;Thomas Wouters&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Welcome Savannah for the first time, and thank you to&#10;&lt;a href="https://infosec.exchange/@gpshead"&gt;Greg Smith&lt;/a&gt; and&#10;&lt;a href="https://bsky.app/profile/emilyemorehouse.bsky.social"&gt;Emily Morehouse&lt;/a&gt; for four and&#10;three years&amp;rsquo; service each.&lt;/p&gt;&#10;&lt;p&gt;Three are starting their sixth terms, and four members have been or are&#10;&lt;a href="https://devguide.python.org/versions/"&gt;release managers&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://hugovk.dev/python-steering-council/"&gt;chart above&lt;/a&gt; only covers the Steering&#10;Council years. Let&amp;rsquo;s also not forget Guido van Rossum&amp;rsquo;s&#10;&lt;a href="https://hugovk.dev/python-steering-council/bdfl.html"&gt;BDFL years&lt;/a&gt;:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_9575c67d2aa99f59.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_d592046b252fd865.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="2808"&#10; height="1138"&#10; class="mx-auto my-0 rounded-md"&#10; alt="The same chart but including Guido&amp;rsquo;s 28 year stint as BDFL."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_2038c25456619b65.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/steering-council-results/bdfl_hu_c45c4ea893687c45.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;</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;&#10; &lt;span class="pe-3 text-primary-400"&gt;&#10; &#10; &lt;/span&gt;&#10; &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;&#10;&lt;/div&gt;&#10;&#10;&lt;p&gt;The Python Steering Council election is on!&lt;/p&gt;&#10;&lt;p&gt;This year six candidates are running for the five seats. See&#10;&lt;a href="https://peps.python.org/pep-8107/"&gt;PEP 8107&lt;/a&gt; for links to their nomination statements&#10;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;&#10;&lt;p&gt;I made a chart to show when the nominations arrived during the nomination period: six is&#10;fewer than in previous years, but they didn&amp;rsquo;t all wait until the last three days like in&#10;2023 and 2024.&lt;/p&gt;&#10;&lt;p&gt;This year we&amp;rsquo;re also using a new voting system: with&#10;&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&#10;stars per candidate.&lt;/p&gt;&#10;&lt;p&gt;If you&amp;rsquo;re in the core team, remember to vote by Friday!&lt;/p&gt;&#10;</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&#10;&lt;a href="https://developer.arm.com/"&gt;Arm&lt;/a&gt; in Cambridge, UK!&lt;/p&gt;&#10;&lt;p&gt;The plan: put 35 core developers and 13 special guests in a room for a week, and see&#10;what they cook up.&lt;/p&gt;&#10;&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;,&#10;&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;&#10;&lt;ul&gt;&#10;&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&#10;[effect] of new C API&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://mastodon.social/@antocuni"&gt;Antonio Cuni&lt;/a&gt; - Tracing JITs on real code&lt;/li&gt;&#10;&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&#10;binaries from python.org&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/hoodmane"&gt;Hood Chatham&lt;/a&gt; - Upstreaming Pyodide FFI&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://cloudisland.nz/@freakboy3742"&gt;Russell Keith-Magee&lt;/a&gt; - Managing cross-platform&#10;wheel builds&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python/steering-council"&gt;Steering Council&lt;/a&gt; -&#10;&lt;a href="https://peps.python.org/pep-0793/"&gt;PEP 793&lt;/a&gt; and abi3/abi3t/abi4&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/mjp41"&gt;Matthew Parkinson&lt;/a&gt; - Designing Deep Immutability&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;I did some &lt;del&gt;sprint&lt;/del&gt; spring cleaning of our PyPI projects, dropping support for&#10;&lt;a href="https://devguide.python.org/versions/"&gt;then-&lt;em&gt;almost&lt;/em&gt;-EOL&lt;/a&gt;&#10;&lt;a href="https://peps.python.org/pep-0596/"&gt;Python 3.9&lt;/a&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python/blurb/pull/68"&gt;python/blurb#68&lt;/a&gt;&lt;/li&gt;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://github.com/python/peps/pull/4587"&gt;python/peps#4587&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python/pyperformance/pull/414"&gt;python/pyperformance#414&lt;/a&gt;&lt;/li&gt;&#10;&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;&#10;&lt;/ul&gt;&#10;&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&#10;all-important&#10;&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;&#10;&lt;ul&gt;&#10;&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;&#10;(Greg, Guido)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://ep2023.europython.eu/"&gt;EuroPython 2023&lt;/a&gt; (Antonio)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://ep2023.europython.eu/"&gt;EuroPython 2024 volunteer&lt;/a&gt; (Hugo)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://2016.pycon.ca/en/"&gt;PyCon Canada 2016&lt;/a&gt; (Brett)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://us.pycon.org/"&gt;PyCon US&lt;/a&gt; Pittsburgh Charlas (Thomas)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2025/python-core-sprint-2025/"&gt;Cambridge 2025 core sprint&lt;/a&gt;&#10;(Diego)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_94f726c3a8f4346b.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_9a9b597e39fdb7fb.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; 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."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_1f0d7b651963784f.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/room_hu_2c4386562c798b7.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;The sprint room&lt;/small&gt;&lt;/center&gt;&#10;&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;&#10;&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;&#10;on the Tuesday, but the morning was full of presentations, and the afternoon had an&#10;early departure for the social event, so I moved it to Wednesday instead.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://chaos.social/@trallard"&gt;Tania Allard&lt;/a&gt; gave a presentation about the different&#10;types of mentorship and how we can improve, followed by an open discussion.&lt;/p&gt;&#10;&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&#10;tools like Claude with CPython.&lt;/p&gt;&#10;&lt;p&gt;Tania, &lt;a href="https://publicidentity.net/@jezdez"&gt;Jannis Leidel&lt;/a&gt;,&#10;&lt;a href="https://hachyderm.io/@willingc"&gt;Carol Willing&lt;/a&gt; and I discussed the&#10;&lt;a href="https://github.com/psf/user-success-wg"&gt;User Success Workgroup&lt;/a&gt; and we came up with&#10;some ideas on next steps.&lt;/p&gt;&#10;&lt;p&gt;We ended the day with a punting tour on the river Cam and dinner at Jesus College, thank&#10;you, Arm!&lt;/p&gt;&#10;&lt;p&gt;And &lt;a href="https://social.coop/@Yhg1s"&gt;Thomas Wouters&lt;/a&gt; gave a fun session of his&#10;&lt;a href="https://discuss.python.org/t/shall-we-play-a-game/96085"&gt;Feuding Pythonistas game&lt;/a&gt;&#10;(spoiler: people are wrong on the internet).&lt;/p&gt;&#10;&lt;p&gt;Python/tech t-shirt census:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://2025.pycon.it/en"&gt;PyCon Italia 2025&lt;/a&gt; (Antonio)&lt;/li&gt;&#10;&lt;li&gt;Python 3.14 pie (Hugo)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://why2025.org/"&gt;WHY 2025&lt;/a&gt; (Thomas)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://us.pycon.org/2025/"&gt;PyCon US 2025&lt;/a&gt; (Diego)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.pycascades.com/"&gt;PyCascades&lt;/a&gt; (Guido)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://ep2022.europython.eu/"&gt;EuroPython 2022&lt;/a&gt; (Mark)&lt;/li&gt;&#10;&lt;li&gt;Pythonista (Jacob)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_4e23566483c7ebba.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_5d7a9544815d4711.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; 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"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_bb4cbf0221fa1551.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/punt_hu_407c364d30c9c790.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;Punting on the Cam&lt;/small&gt;&lt;/center&gt;&#10;&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;&#10;&lt;p&gt;The Steering Council asked for an extra day to decide about a possible typing revert&#10;(&lt;a href="https://github.com/python/steering-council/issues/307"&gt;python/steering-council#307&lt;/a&gt;),&#10;so not today.&lt;/p&gt;&#10;&lt;p&gt;Lightning talks:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;&lt;li&gt;⚡ &lt;a href="https://mastodon.social/@EWDurbin"&gt;Ee Durbin&lt;/a&gt; on modernising the last bits of&#10;infra and bots&lt;/li&gt;&#10;&lt;li&gt;⚡ &lt;a href="https://github.com/zooba"&gt;Steve Dower&lt;/a&gt; demoed the new Windows installer&lt;/li&gt;&#10;&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;&#10;&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;&#10;&lt;li&gt;⚡ Greg shared a &lt;a href="https://github.com/python/peps/pull/4592"&gt;draft PEP&lt;/a&gt; for timestamps&#10;on async tracebacks&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Carol, Adam, Thomas, &lt;a href="https://mastodon.social/@encukou"&gt;Petr Viktorin&lt;/a&gt; and I discussed a&#10;number of docs topics.&lt;/p&gt;&#10;&lt;p&gt;I released the&#10;&lt;a href="https://github.com/python/python-docs-theme/releases/tag/2025.9.2"&gt;Python Docs Sphinx Theme &lt;/a&gt;&#10;with more translations.&lt;/p&gt;&#10;&lt;p&gt;We had a Q&amp;amp;A session with the Steering Council, three in-person and two joining&#10;remotely.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://fosstodon.org/@Monorepo"&gt;Jacob Coffee&lt;/a&gt; and I looked into upgrading the&#10;&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;&#10;blogs into something a little more modern.&lt;/p&gt;&#10;&lt;p&gt;T-shirt census:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://2025.pycon.gr/en/"&gt;PyCon Greece 2025&lt;/a&gt; (Hugo)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://2025.pycon.kr/"&gt;PyCon Korea 2025&lt;/a&gt; (Donghee)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pyladies.com/"&gt;PyLadies&lt;/a&gt; (Savannah, Petr)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://ep2023.europython.eu/"&gt;EuroPython 2024&lt;/a&gt; (Lys)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://2023.pycon.it/en"&gt;PyCon Italia 2023&lt;/a&gt; (Antonio)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2025/python-core-sprint-2025/"&gt;Cambridge 2025 core sprint&lt;/a&gt;&#10;(Guido)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_b8f25ceb6f62dd61.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_bee77c3731d85ed4.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Steering Council Q&amp;amp;A: Greg, Pablo and Donghee on stools and Barry and Emily on screen."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_a9d200015128fa9f.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/steering-council_hu_f7e2d43aff97bc3.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;The Python Steering Council&lt;/small&gt;&lt;/center&gt;&#10;&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;&#10;&lt;p&gt;The Steering Council&#10;&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;,&#10;so full steam ahead with the&#10;&lt;a href="https://discuss.python.org/t/python-3-14-0rc3-is-go/103815"&gt;release&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://fosstodon.org/@savannah"&gt;Savannah Ostrowski&lt;/a&gt;, release manager for 3.16 and&#10;3.17, &lt;a href="https://bsky.app/profile/savannah.dev/post/3lz4ek7aw6224"&gt;shadowed&lt;/a&gt; to see what&#10;the process looks like (not as bad as it looks like in&#10;&lt;a href="https://peps.python.org/pep-0101/"&gt;PEP 101&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;Time for a couple of &lt;a href="https://github.com/python/release-tools/pull/280"&gt;quick&lt;/a&gt;&#10;&lt;a href="https://github.com/python/peps/pull/4596"&gt;PRs&lt;/a&gt; and an interview with&#10;&lt;a href="https://bsky.app/profile/pablogsal.com"&gt;Pablo Galindo Salgado&lt;/a&gt; and&#10;&lt;a href="https://mastodon.social/@ambv"&gt;Łukasz Langa&lt;/a&gt; on the&#10;&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;,&#10;along with 29 others!&lt;/p&gt;&#10;&lt;p&gt;T-census:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://web.archive.org/web/20160419232557/http://ua.pycon.org/"&gt;PyCon UA&lt;/a&gt; 2016&#10;(Łukasz)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2025/python-core-sprint-2025/"&gt;Cambridge 2025 core sprint&lt;/a&gt;&#10;(Donghee)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://blog.readthedocs.com/read-the-docs-2014-stats/"&gt;Read the Docs Budapest 2014&lt;/a&gt;&#10;(Petr)&lt;/li&gt;&#10;&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;&#10;hoodie (Lys)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_3acbdca409ecaf7e.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_dd4dcee303b9e3.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; 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."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_3cbba1771ab14a41.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/release-room_hu_43224075190693fb.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;The 3.14.0rc3 release in progress&lt;/small&gt;&lt;/center&gt;&#10;&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;&#10;&lt;ul&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/ul&gt;&#10;&lt;p&gt;The conference also included &lt;a href="https://2025.pyconuk.org/sprints/"&gt;sprints&lt;/a&gt;, and Adam and&#10;I ran the CPython sprint. We had a big table full of contributors and a few made their&#10;very first contributions, which is always rewarding for all involved!&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_c7712ec7e8ffdc9d.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_cfe6dc5f933df1f9.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Another roomful of people working at laptops around tables"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_4be8f07050ac4965.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/python-core-sprint/pycon-uk-sprint_hu_f03d5f12e31eee7e.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;PyCon UK sprint&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;Some numbers for me during the week:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Python release candidates released: 1&lt;/li&gt;&#10;&lt;li&gt;Issues created: 1&lt;/li&gt;&#10;&lt;li&gt;PRs created: 8&lt;/li&gt;&#10;&lt;li&gt;Issues closed: 7&lt;/li&gt;&#10;&lt;li&gt;PRs merged: 28&lt;/li&gt;&#10;&lt;li&gt;PRs closed: 1&lt;/li&gt;&#10;&lt;li&gt;Total issues involved with: 18&lt;/li&gt;&#10;&lt;li&gt;Total PRs involved with: 70&lt;/li&gt;&#10;&lt;li&gt;Repositories affected: 19&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&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;&#10;and &lt;a href="https://developer.arm.com/"&gt;Arm&lt;/a&gt; for arranging and hosting us. The core sprint is&#10;always a highlight of the year and an incredibly productive week.&lt;/p&gt;&#10;&lt;p&gt;Read writeups by&#10;&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;&#10;and&#10;&lt;a href="https://antocuni.eu/2025/09/24/tracing-jits-in-the-real-world--cpython-core-dev-sprint/"&gt;Antonio&lt;/a&gt;,&#10;and I recommend listening to Łukasz and Pablo&amp;rsquo;s core.py podcast for interviews with 18&#10;(&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;)&#10;and 12 sprinters&#10;(&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;).&#10;They&amp;rsquo;re long, but it&amp;rsquo;s fascinating to hear all the different things everyone is working&#10;on.&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo by Arm&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;3.15:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Lazy imports defer the loading and execution of a module until the first time the&#10;imported name is used, in contrast to ‘normal’ imports, which eagerly load and execute&#10;a module at the point of the import statement.&lt;/p&gt;&#10;&lt;p&gt;By allowing developers to mark individual imports as lazy with explicit syntax, Python&#10;programs can reduce startup time, memory usage, and unnecessary work. This is&#10;particularly beneficial for command-line tools, test suites, and applications with&#10;large dependency graphs.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;It&amp;rsquo;s not been accepted yet, but let&amp;rsquo;s try out the&#10;&lt;a href="https://github.com/LazyImportsCabal/cpython/tree/lazy"&gt;reference implementation&lt;/a&gt; on one&#10;of my CLI tools, &lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;.&lt;/p&gt;&#10;&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;&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch LazyImportsCabal&#10;&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;&#10;&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&#10;use the binary wheels instead of having to build from source:&lt;/p&gt;&#10;&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&#10;&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&#10;&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 */&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /*--start constants--*/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #define PY_MAJOR_VERSION 3&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #define PY_MICRO_VERSION 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #define PY_RELEASE_LEVEL PY_RELEASE_LEVEL_ALPHA&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; #define PY_RELEASE_SERIAL 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /* Version as a string */&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /*--end constants--*/&#10;&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&#10;&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&#10;&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])&#10;&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])&#10;&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;&#10;&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;\&#10;&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;\&#10;&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;\&#10;&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;\&#10;&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&#10;&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;&#10;&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&#10;&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;&#10;&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&#10;&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;&#10;&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&#10;&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&#10;layers deep, not counting NumPy and pandas:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &#10; &lt;picture class="mx-auto my-0 rounded-md" &gt;&#10; &lt;img&#10; src="https://hugovk.dev/blog/2025/lazy-imports/pipdeptree.svg"&#10; width="1355"&#10; height="729"&#10; class="mx-auto my-0 rounded-md"&#10; alt="A tree of dependencies: seven wide, and about six layers deep."&#10; loading="lazy" decoding="async"&#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/sharkdp/hyperfine"&gt;hyperfine&lt;/a&gt;:&lt;/p&gt;&#10;&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&#10;&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&#10;functions that call them (the PEP calls these&#10;&lt;a href="https://peps.python.org/pep-0810/#motivation"&gt;&amp;ldquo;inline imports&amp;rdquo;&lt;/a&gt;).&lt;/p&gt;&#10;&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&#10;using the&#10;&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;&#10;here to make it easy to compare two different runs.&lt;/p&gt;&#10;&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;\&#10;&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; \&#10;&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;&#10;&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&#10;&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]&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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&#10;&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]&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Plotted with&#10;&lt;a href="https://github.com/sharkdp/hyperfine/tree/master/scripts"&gt;plot_progression.py&lt;/a&gt;:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; &#10; srcset="https://hugovk.dev/blog/2025/lazy-imports/inline-imports_hu_e3be42cb0da8959c.webp"&#10; &#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="640"&#10; height="480"&#10; class="mx-auto my-0 rounded-md"&#10; alt="A progression chart of 20 runs for each benchmark: non-lazy runs are about 46 ms, lazy are about 35 ms."&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2025/lazy-imports/inline-imports.png"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;From 46 to 35 milliseconds, or, 1.31 times faster, not bad.&lt;/p&gt;&#10;&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;&#10;&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;&#10;guards. Here&amp;rsquo;s a&#10;&lt;a href="https://github.com/hugovk/pypistats/commit/646bc6f70656df26d55a0bf4977a878a2b4379e5"&gt;diff&lt;/a&gt;.&lt;/p&gt;&#10;&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;\&#10;&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; \&#10;&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;&#10;&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&#10;&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]&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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&#10;&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]&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; &#10; srcset="https://hugovk.dev/blog/2025/lazy-imports/fully-lazy_hu_a179eb64034f4f.webp"&#10; &#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="640"&#10; height="480"&#10; class="mx-auto my-0 rounded-md"&#10; alt="A progression chart of 20 runs for each benchmark: non-lazy runs are about 104 ms, lazy are about 36 ms."&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2025/lazy-imports/fully-lazy.png"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;From 104 to 36 milliseconds, or 2.92 times faster, much better!&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://flickr.com/photos/usnationalarchives/4272370812/"&gt;&#10;&amp;ldquo;Lazy Man Fishing&amp;rdquo; at Cascade Locks on the Columbia River 05/1973&lt;/a&gt; in the&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://flickr.com/photos/usnationalarchives/"&gt;U.S.&#10;National Archives &lt;/a&gt;, with&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no&#10;known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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;&#10;&lt;hr&gt;&#10;&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&#10;&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;&#10;&lt;p&gt;What are you looking forward to?&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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&#10;&lt;a href="https://buildbot.python.org/#/release_status"&gt;buildbots&lt;/a&gt; red&amp;hellip;&lt;/p&gt;&#10;&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&#10;that test for relevant kernel versions in the meantime.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_2076cdb368f0ba41.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_5e461471b3a31fab.webp 1278w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1278"&#10; height="632"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Python Release Status Dashboard: 3.15-3.13 are red and &amp;lsquo;✘ Unreleasable: Tier-1 build failed&amp;rsquo;"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red_hu_2d2f04ebaa5e8086.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-red.png 1278w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_f005d9bd8dd49d9.webp 772w&#10; &#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details_hu_f005d9bd8dd49d9.webp 772w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="772"&#10; height="678"&#10; class="mx-auto my-0 rounded-md"&#10; 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)"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details.png 772w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-details.png 772w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_ca4cc1e6d7a91aa.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_382cf8e78f5b3bf8.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1382"&#10; height="700"&#10; class="mx-auto my-0 rounded-md"&#10; alt="ERROR: test_aead_aes_gcm (test.test_socket.LinuxKernelCryptoAPI.test_aead_aes_gcm)"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_39257eb41152c72d.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/kernel-error_hu_7788575261e2559a.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_f4592446513e783a.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_b143c9c867ddb722.webp 1138w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1138"&#10; height="644"&#10; class="mx-auto my-0 rounded-md"&#10; 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"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green_hu_b17af36243e6f0f3.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/buildbots-green.png 1138w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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;&#10;&lt;p&gt;A new &lt;a href="https://github.com/python/cpython/labels/release-blocker"&gt;release-blocker&lt;/a&gt;&#10;appeared yesterday (because of course) but it can wait until 3.14.1.&lt;/p&gt;&#10;&lt;p&gt;Three &lt;a href="https://github.com/python/cpython/labels/deferred-blocker"&gt;deferred-blockers&lt;/a&gt; are&#10;also waiting until 3.14.1.&lt;/p&gt;&#10;&lt;p&gt;A new tier-2 &lt;a href="https://buildbot.python.org/#/release_status"&gt;buildbot&lt;/a&gt; failure appeared&#10;yesterday (because of course) but it had previously been offline for a month and will&#10;need some reconfiguration. Can ignore.&lt;/p&gt;&#10;&lt;p&gt;OK, let&amp;rsquo;s make a Python!&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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&#10;&lt;a href="https://github.com/python/cpython/pull/139631"&gt;final change&lt;/a&gt; to&#10;&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&#10;latest stable.&lt;/p&gt;&#10;&lt;p&gt;Now start &lt;code&gt;run_release.py&lt;/code&gt;, the main release automation script, which does a bunch of&#10;pre-checks, runs blurb to create a merged changelog, bumps some numbers, and pushes a&#10;branch and tag to my fork. It&amp;rsquo;ll go upstream at the end of a successful build.&lt;/p&gt;&#10;&lt;p&gt;Then kick off the &lt;a href="https://github.com/python/release-tools/actions/runs/18308460797"&gt;CI&lt;/a&gt;&#10;to build source zips, docs and Android binaries.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_c5f3422d466faf5c.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_5fef3a1166d5016b.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1650"&#10; height="720"&#10; class="mx-auto my-0 rounded-md"&#10; 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)."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_8b2ed52792df0c52.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/ci-build_hu_ec0ac4d2f88e1785.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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&#10;an Android test runner update.)&lt;/p&gt;&#10;&lt;p&gt;This build takes about half an hour.&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;ve also informed the Windows and macOS release managers about the tag and they will&#10;start up installer builds.&lt;/p&gt;&#10;&lt;p&gt;This takes a few hours, so I&amp;rsquo;ve got time to finish up the release notes.&lt;/p&gt;&#10;&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&#10;and we don&amp;rsquo;t need to follow it all manually.&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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&#10;&lt;a href="https://dev.azure.com/Python/cpython/_build/results?buildId=164907&amp;amp;view=results"&gt;Windows build&lt;/a&gt;&#10;has been started.&lt;/p&gt;&#10;&lt;p&gt;The jobs with profile-guided optimisation (PGO) build once, then collect a profile by&#10;running the tests, and then build again using that profile, to see how &amp;lsquo;real&amp;rsquo; code&#10;executes and optimises for that.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_40c943f220ccd59c.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_dac2b1933158c521.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="3052"&#10; height="1504"&#10; class="mx-auto my-0 rounded-md"&#10; 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."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_51ab074e9b5647bf.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/windows-build_hu_7e71d3bd21dfa235.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;Meanwhile, the docs+source+Android build has finished and the artifacts have been copied&#10;to where they need to go with SBOMs created.&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_9f38bbf57031b5b.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_2f0414d15dba433d.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1372"&#10; height="1388"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Terminal prompt showing the output of run_release.py with lots of checked tasks and ending with: Waiting for files: Linux ✅ Windows ✅ Mac ❌"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_c4247a1a0d0378e6.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/waiting-for-files_hu_38027238b4c95f16.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_5ffcaeaa7fdc1ac3.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_d59b794606aa4ce7.webp 1170w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1170"&#10; height="128"&#10; class="mx-auto my-0 rounded-md"&#10; 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:"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release_hu_8e7f67d1cb434d9d.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/create-release.png 1170w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; &#10; srcset="https://hugovk.dev/blog/2025/releasing-python-3-14-0/feature_hu_efee4cbe1d39df2c.webp"&#10; &#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="538"&#10; height="507"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Two snakes enjoying a pie with 3.14 on the top and π crimping."&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2025/releasing-python-3-14-0/feature.png"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;&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&#10;&lt;a href="https://blog.python.org/2025/10/python-3140-final-is-here.html"&gt;blog&lt;/a&gt; &amp;amp;&#10;&lt;a href="https://mail.python.org/archives/list/python-list@python.org/thread/UDOUFTTWDO7IXHLPQSHTEW2FWGF7ZY2C/"&gt;mailing lists&lt;/a&gt;,&#10;update the &lt;a href="https://peps.python.org/pep-0745/"&gt;PEP&lt;/a&gt; &amp;amp;&#10;&lt;a href="https://www.python.org/downloads/"&gt;downloads landing page&lt;/a&gt;, fix&#10;&lt;a href="https://discuss.python.org/t/python-3-14-0-final-is-here/104210?u=hugovk"&gt;Discourse post&lt;/a&gt;&#10;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&#10;core team to start landing PRs that didn&amp;rsquo;t need to be in the RC, and eat the pie.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_4aa2808a2ce3f41f.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_8440b9b0753887e8.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="3325"&#10; height="2494"&#10; class="mx-auto my-0 rounded-md"&#10; alt="A circular lemon meringue pie in front of a Mac showing the release logo."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_773b2d1c8c707928.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/releasing-python-3-14-0/pie_hu_edcaa9663bf9bd8c.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;A HUGE thanks to &lt;a href="https://mastodon.social/@sovtechfund"&gt;@sovtechfund&lt;/a&gt;&#10;&lt;a href="https://hugovk.dev/blog/2025/im-excited-to-join-the-sovereign-tech-fellowship/"&gt;Fellowship&lt;/a&gt;&#10;for allowing me to dedicate my time on getting this out 🎉&lt;/p&gt;&#10;&lt;p&gt;&lt;small&gt;&lt;a href="https://mastodon.social/tags/Python"&gt;#Python&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/Python314"&gt;#Python314&lt;/a&gt;&#10;&lt;a href="https://mastodon.social/tags/release"&gt;#release&lt;/a&gt;&lt;br&gt;&#10;&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;&#10;</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&#10;&lt;a href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;It uses uv for managing Python virtual environments and dependencies, is rewritten in&#10;Rust (because of course) and uses the same &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; as pre-commit.&lt;/p&gt;&#10;&lt;p&gt;Its homepage says it&amp;rsquo;s not yet production ready, but&#10;&lt;a href="https://prek.j178.dev/#who-is-using-prek"&gt;several projects like Apache Airflow and PDM&lt;/a&gt;&#10;are already using it. I&amp;rsquo;ve been using it for a while and reporting issues as I find&#10;them; the maintainer is quick with fixes and releases. All the hooks I need are now&#10;supported.&lt;/p&gt;&#10;&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;,&#10;then:&lt;/p&gt;&#10;&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&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;blockquote&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;⚡ About &lt;a href="https://prek.j178.dev/benchmark/"&gt;10x faster&lt;/a&gt; than pre-commit and uses&#10;only a third of disk space.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;This 10x is a comparison of installing hooks using the excellent&#10;&lt;a href="https://github.com/sharkdp/hyperfine"&gt;hyperfine&lt;/a&gt; benchmarking tool.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s my own comparison.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&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&#10;caches. I then ran each tool with &lt;code&gt;run --all-files&lt;/code&gt; in serial on the 126 repos I have&#10;cloned right now, 84 of which have a &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; file. Each then&#10;downloads and installs the hooks when needed, then runs the lint tools.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Second, because running the lint tools should be independent and constant time for&#10;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;&#10;&lt;p&gt;To get an idea of the amount of work for these two tests, pre-commit reported&#10;initialising environments 217 times.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Finally, let&amp;rsquo;s run hyperfine on the&#10;&lt;a href="https://github.com/python-pillow/Pillow/blob/eef4848a0a7ca0ea5ecd1f647603d85526d88ca7/.pre-commit-config.yaml"&gt;Pillow config&lt;/a&gt;,&#10;which installs hooks from 14 repos:&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&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;\&#10;&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;\&#10;&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;\&#10;&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;\&#10;&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;&#10;&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;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;code&gt;pre-commit&lt;/code&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;code&gt;prek&lt;/code&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Times faster&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;run --all-files&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;17m13s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7m59s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.16&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;install-hooks&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10m48s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2m24s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;4.50&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;hyperfine&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;39.841s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;5.539s&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;7.19&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;The hyperfine results:&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Benchmark 1: prek install-hooks&#10; Time (mean ± σ): 5.539 s ± 0.176 s [User: 8.973 s, System: 5.692 s]&#10; Range (min … max): 5.231 s … 5.834 s 10 runs&#10;&#10;Benchmark 2: pre-commit install-hooks&#10; Time (mean ± σ): 39.841 s ± 2.017 s [User: 19.760 s, System: 8.203 s]&#10; Range (min … max): 36.930 s … 43.976 s 10 runs&#10;&#10;Summary&#10; prek install-hooks ran&#10; 7.19 ± 0.43 times faster than pre-commit install-hooks&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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&#10;has no config file&lt;/li&gt;&#10;&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;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/sdasmarchives/51500757435/"&gt;AH-1G&#10;Aircraft Maintenance Test Flight Handbook and handwritten checklist for helicopters&lt;/a&gt;&#10;in the&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/sdasmarchives/"&gt;San&#10;Diego Air and Space Museum Archive&lt;/a&gt;, with&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no&#10;known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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;&#10;&lt;ul&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/ul&gt;&#10;&lt;p&gt;20 July 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by &lt;a href="https://mastodon.social/@georgically/114891414072680058"&gt;Georgi Ker&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;21 July 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;a href="https://wsvincent.com/europython2025-recap/"&gt;EuroPython 2025 in Prague Recap&lt;/a&gt; by&#10;&lt;a href="https://fosstodon.org/@wsvincent/114891651350217561"&gt;Will Vincent&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&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;&#10;by &lt;a href="https://mastodonczech.cz/@honzajavorek/114892035602713856"&gt;Honza Javorek&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;23 July 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://raffaella.bearblog.dev/first-europython/"&gt;First EuroPython&lt;/a&gt; by&#10;&lt;a href="https://mastodon.social/@raffaellasuardini/114908865884572072"&gt;Raffaella Suardini&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;24 July 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://cheuk.dev/posts/2025-07-24-europython25-recap/"&gt;EuroPython 2025 Recap&lt;/a&gt; by&#10;&lt;a href="https://fosstodon.org/@cheukting_ho/114913173274810806/"&gt;Cheuk Ting Ho&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;11 August 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://mathspp.com/blog/personal-highlights-of-europython-2025"&gt;Personal highlights of EuroPython 2025&lt;/a&gt;&#10;by &lt;a href="https://bsky.app/profile/mathspp.com/post/3lw4z7e4nds2p"&gt;Rodrigo Girão Serrão&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;12 August 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by &lt;a href="https://fosstodon.org/@jugmac00/115016290243228831"&gt;Jürgen Gmach&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;17 August 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by Gauden Galea&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;21 August 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://gihyo.jp/article/2025/08/europython-2025-01"&gt;EuroPython 2025開幕までの道のり⁠⁠、Day 1の注目セッション&lt;/a&gt;&#10;by &lt;a href="https://github.com/takanory"&gt;Takanori Suzuki&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;8 September 2025&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://blog.qstars.nl/posts/europython-2025/"&gt;Recap EuroPython 2025&lt;/a&gt; by Victor&#10;Brinkhorst&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;And a bunch of LinkedIn posts:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/activity-7353091873093181442-jUko"&gt;Alicja Kocieniewska&lt;/a&gt;&lt;/li&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/activity-7355237790462148608-VYL-"&gt;Vassiliki Dalakiari&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Finally, the &lt;a href="https://www.flickr.com/photos/europython"&gt;official photos&lt;/a&gt; and&#10;&lt;a href="https://www.youtube.com/@EuroPythonConference"&gt;videos&lt;/a&gt; should be up soon, and here are&#10;&lt;a href="https://www.flickr.com/photos/hugovk/collections/72157724094370661/"&gt;my photos&lt;/a&gt;.&lt;/p&gt;&#10;&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;&#10;&lt;li&gt;2025-09-06: Added Gauden Galea&amp;rsquo;s writeup&lt;/li&gt;&#10;&lt;li&gt;2025-09-09: Added Victor Brinkhorst&amp;rsquo;s writeup&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo: Savannah Bailey&amp;rsquo;s keynote&#10;(&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;CC&#10;BY-NC-SA 2.0&lt;/a&gt;&#10;&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;&#10;</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&#10;&lt;a href="https://nedbatchelder.com/blog/201106/running_coverage_on_your_tests.html"&gt;running&lt;/a&gt;&#10;&lt;a href="https://nedbatchelder.com/blog/201908/dont_omit_tests_from_coverage.html"&gt;coverage&lt;/a&gt;&#10;&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;&#10;&lt;p&gt;Here&amp;rsquo;s a couple of reasons why, from the past couple of months.&lt;/p&gt;&#10;&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&#10;forget to rename the new one (see also: the&#10;&lt;a href="https://pvs-studio.com/en/blog/posts/cpp/0260/"&gt;Last Line Effect&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;For example:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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&#10;clearly shows up as a non-run test in the coverage report. In this&#10;&lt;a href="https://github.com/python/pymanager/pull/17"&gt;case&lt;/a&gt;, we only need to rename one of them,&#10;and both are covered and pass.&lt;/p&gt;&#10;&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&#10;which would cause it to fail, but we just don&amp;rsquo;t know because it&amp;rsquo;s not run.&lt;/p&gt;&#10;&lt;p&gt;&lt;div class="flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900"&gt;&#10; &lt;span class="pe-3 text-primary-400"&gt;&#10; &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;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10; &lt;span class="dark:text-neutral-300"&gt;&lt;strong&gt;Tip 1:&lt;/strong&gt; This can also be found by&#10;&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;&#10;&lt;/div&gt;&#10;   &lt;div class="flex rounded-md bg-primary-100 px-4 py-3 dark:bg-primary-900"&gt;&#10; &lt;span class="pe-3 text-primary-400"&gt;&#10; &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;&#10;&lt;/span&gt;&#10; &lt;/span&gt;&#10; &lt;span class="dark:text-neutral-300"&gt;&lt;strong&gt;Tip 2:&lt;/strong&gt;&#10;&lt;a href="https://docs.pytest.org/en/stable/how-to/parametrize.html"&gt;pytest&amp;rsquo;s parametrize&lt;/a&gt; is a&#10;great way to combine similar test functions with different input data.&lt;/span&gt;&#10;&lt;/div&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_3b6766957c9cd7fd.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_5c396df3c22389e8.webp 1290w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1290"&#10; height="726"&#10; class="mx-auto my-0 rounded-md"&#10; alt="The same code, but Codecov has flagged the last two lines were not covered"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage_hu_a7b7dce21898e4e6.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/run-coverage-on-tests/coverage.png 1290w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;to iterate over in the &lt;code&gt;for&lt;/code&gt; loop &amp;ndash; hence no coverage. The&#10;&lt;a href="https://github.com/python-pillow/Pillow/pull/8979#discussion_r2172250301"&gt;fix&lt;/a&gt; is to&#10;use a list instead of a generator.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo: Misplaced manhole cover&#10;(&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/"&gt;CC&#10;BY-NC-SA 2.0&lt;/a&gt;&#10;&lt;a href="https://www.flickr.com/photos/hugovk/10375735084/"&gt;Hugo van Kemenade&lt;/a&gt;).&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;&lt;a href="https://peps.python.org/"&gt;PEPs&lt;/a&gt;, or Python Enhancement Proposals (edited from&#10;&lt;a href="https://www.youtube.com/embed/7NrPCsH0mBU?start=1662&amp;amp;end=1803"&gt;PyBay 2017&lt;/a&gt;):&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;I like backronyms. For those who don&amp;rsquo;t know: a backronym is where you come up with the&#10;acronym first and then you come up with the thing that the acronym stands for. And I&#10;like funny sounding words, like &lt;a href="https://peps.python.org/pep-0401/"&gt;FLUFL&lt;/a&gt; was one of&#10;those. When we were working for CNRI, they also ran the IETF conferences. The IETF is&#10;the Internet Engineering Task Force, and they&amp;rsquo;re the ones who come up with the RFCs.&#10;If you look at &lt;a href="https://datatracker.ietf.org/doc/html/rfc822"&gt;RFC 822&lt;/a&gt;, it defines&#10;what an email message looks like.&lt;/p&gt;&#10;&lt;p&gt;We got to a point, because we were at CNRI we were more intimately involved in the&#10;IETF and how they do standards and things, we observed at the time that there were so&#10;many interesting ideas coming in being proposed for Python that Guido really just&#10;didn&amp;rsquo;t have time to dive into the details of everything.&lt;/p&gt;&#10;&lt;p&gt;So I thought: well, we have this RFC process, let&amp;rsquo;s try to mirror some of that so that&#10;we can capture the essence of an idea in a document that would serve as a point of&#10;discussion, and that Guido could let people discuss and then come in and read the&#10;summary of the discussion.&lt;/p&gt;&#10;&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&#10;funny sounding word. I came up with the word and then I backronymed it into Python&#10;Enhancement Proposal. And then I wrote &lt;a href="https://peps.python.org/pep-0000/"&gt;PEP 0&lt;/a&gt; and&#10;&lt;a href="https://peps.python.org/pep-0001/"&gt;PEP 1&lt;/a&gt;. PEP 0 was originally handwritten, and so I&#10;was the first PEP author because I came up with the name PEP.&lt;/p&gt;&#10;&lt;p&gt;But the really interesting thing is that you see the E.P. part used in a lot of other&#10;places, like Debian has DEPs now. There&amp;rsquo;s a lot of other communities that have these&#10;enhancement proposals so it&amp;rsquo;s kind of interesting. And then the format of the PEP was&#10;directly from that idea of the RFC&amp;rsquo;s standard.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Acronym&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Name&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AIP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&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;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;AIP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://google.aip.dev/"&gt;API Improvement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;APE&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/astropy/astropy-APEs"&gt;Astropy Proposals for Enhancement&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;BIP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/bitcoin/bips"&gt;Bitcoin Improvement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;CEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/conda-incubator/ceps"&gt;Conda Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;CFEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&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;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://dep-team.pages.debian.net/"&gt;Debian Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;DEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/django/deps"&gt;Django Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;FEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://codeberg.org/fediverse/fep"&gt;Fediverse Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;GLEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&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;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;IPEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&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;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;JEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://openjdk.org/jeps/0"&gt;JDK Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;JEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/jmespath/jmespath.jep"&gt;JMESPath Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;JEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://jupyter.org/enhancement-proposals/README.html"&gt;Jupyter Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;KEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://www.kubernetes.dev/resources/keps/"&gt;Kubernetes Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;NEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://numpy.org/neps/"&gt;NumPy Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;PEEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&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;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;PEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://peps.python.org/"&gt;Python Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;SKIP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://scikit-image.org/docs/stable/skips/"&gt;scikit-image proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;SLEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://scikit-learn-enhancement-proposals.readthedocs.io"&gt;Scikit-learn enhancement proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;SPEC&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://scientific-python.org/specs/"&gt;Scientific Python Ecosystem Coordination&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;TIP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&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;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;WEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/writethedocs/weps"&gt;Write the Docs Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;XEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://xmpp.org/extensions/"&gt;XMPP Extension Protocols&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;YTEP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://ytep.readthedocs.io/"&gt;yt Enhancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;ZAP&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://zensical.org/spark/proposals/"&gt;Zensical Advancement Proposals&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;Are there more? Let me know!&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/library_of_congress/2179931106/"&gt;Grand&#10;Grocery Co., Lincoln, Nebraska, USA (1942)&lt;/a&gt; by&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/library_of_congress/"&gt;The&#10;Library of Congress&lt;/a&gt;, with&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no&#10;known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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;&#10;&lt;p&gt;There are three ways to add it to your test matrix:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;actions/setup-python: &lt;code&gt;t&lt;/code&gt; suffix&lt;/li&gt;&#10;&lt;li&gt;actions/setup-uv: &lt;code&gt;t&lt;/code&gt; suffix&lt;/li&gt;&#10;&lt;li&gt;actions/setup-python: &lt;code&gt;freethreaded&lt;/code&gt; variable&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&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&#10;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;&#10;&lt;p&gt;This is my preferred method, we can clearly see which versions are free-threaded and&#10;it&amp;rsquo;s straightforward to test both regular and free-threaded builds.&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;&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;&#10;&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;&#10;&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;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Python 3.14.0a6 (main, Mar 17 2025, 02:44:29) [GCC 13.3.0]&#10;sys._is_gil_enabled: True&#10;Py_GIL_DISABLED: 0&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And free-threaded builds will output something like:&lt;/p&gt;&#10;&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]&#10;sys._is_gil_enabled: False&#10;Py_GIL_DISABLED: 1&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For example:&#10;&lt;a href="https://github.com/hugovk/test/actions/runs/14057185035"&gt;hugovk/test/actions/runs/14057185035&lt;/a&gt;&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/astral-sh/setup-uv#python-version"&gt;astral/setup-uv&lt;/a&gt; and use that to&#10;set up free-threaded Python using the &lt;code&gt;t&lt;/code&gt; suffix.&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For example:&#10;&lt;a href="https://github.com/hugovk/test/actions/runs/13967959519"&gt;hugovk/test/actions/runs/13967959519&lt;/a&gt;&lt;/p&gt;&#10;&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;&#10;and higher.&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For example:&#10;&lt;a href="https://github.com/hugovk/test/actions/runs/39359291708"&gt;hugovk/test/actions/runs/39359291708&lt;/a&gt;&lt;/p&gt;&#10;&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&#10;after importing a module that doesn&amp;rsquo;t support running without it.&lt;/p&gt;&#10;&lt;p&gt;See&#10;&lt;a href="https://py-free-threading.github.io/running-gil-disabled/"&gt;Running Python with the GIL Disabled&lt;/a&gt;&#10;for more info.&lt;/p&gt;&#10;&lt;p&gt;With the &lt;code&gt;t&lt;/code&gt; suffix:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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&#10;ecosystem and community support. Library maintainers: please test it and where needed,&#10;adapt your code, and publish&#10;&lt;a href="https://hugovk.dev/free-threaded-wheels/"&gt;free-threaded wheels&lt;/a&gt; so others can test&#10;&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;&#10;&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;&#10;&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;&#10;for other ways to test and how to check your build&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://py-free-threading.github.io/"&gt;Python free-threading guide&lt;/a&gt;&lt;/li&gt;&#10;&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;&#10;&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;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/library-company-of-philadelphia/9354604392/"&gt;Spinning&#10;Room, Winding Bobbins with Woolen Yarn for Weaving, Philadelphia, PA&lt;/a&gt;&amp;rdquo; by&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/library-company-of-philadelphia/"&gt;Library&#10;Company of Philadelphia&lt;/a&gt;, with&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no&#10;known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;used in Python projects.&lt;/p&gt;&#10;&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 ::&#10;OSI Approved :: BSD License&amp;rdquo;, which is imprecise (for example, which&#10;&lt;a href="https://en.wikipedia.org/wiki/BSD_licenses"&gt;BSD licence&lt;/a&gt;?), the&#10;&lt;a href="https://spdx.github.io/spdx-spec/v2.2.2/SPDX-license-expressions/"&gt;SPDX licence expression syntax&lt;/a&gt;&#10;is used.&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;I usually use Hatchling as a build backend, and support was added in 1.27:&lt;/p&gt;&#10;&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]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; build-backend = &amp;#34;hatchling.build&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; requires = [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;hatch-vcs&amp;#34;,&#10;&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;,&#10;&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;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ]&#10;&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&#10;&lt;code&gt;license-files&lt;/code&gt; which points to the licence files in the repo. There&amp;rsquo;s often only one,&#10;but if you have more than one, list them all:&lt;/p&gt;&#10;&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]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...&#10;&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; }&#10;&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;&#10;&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; ]&#10;&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;&#10;&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 = [&#10;&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;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;Intended Audience :: Developers&amp;#34;,&#10;&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;,&#10;&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;,&#10;&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;&#10;and &lt;a href="https://github.com/prettytable/prettytable/pull/350"&gt;prettytable#350&lt;/a&gt;.&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;I recommend using &lt;a href="https://docs.pypi.org/trusted-publishers/"&gt;Trusted Publishing&lt;/a&gt; which&#10;I use with &lt;a href="https://github.com/pypa/gh-action-pypi-publish"&gt;pypa/gh-action-pypi-publish&lt;/a&gt;&#10;to deploy from GitHub Actions. I didn&amp;rsquo;t need to make any changes here, just make a&#10;release as usual.&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; &#10; srcset="https://hugovk.dev/blog/2025/improving-licence-metadata/pypi-licence_hu_90fd56d8bbd079d1.webp"&#10; &#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="586"&#10; height="372"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Screenshot of PyPI showing licence expression: BSD-3-Clause"&#10; loading="lazy" decoding="async"&#10; &#10; src="https://hugovk.dev/blog/2025/improving-licence-metadata/pypi-licence.png"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Name: prettytable&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;...&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&lt;a href="https://github.com/pombredanne"&gt;Philippe Ombredanne&lt;/a&gt; for creating the first draft in&#10;2019, to &lt;a href="https://github.com/cam-gerlach"&gt;C.A.M. Gerlach&lt;/a&gt; for the second draft in 2021,&#10;and especially to &lt;a href="https://karolinasurma.eu/"&gt;Karolina Surma&lt;/a&gt; for getting the third&#10;draft over the finish line and helping with the implementation.&lt;/p&gt;&#10;&lt;p&gt;And many projects were updated to support this, thanks to the maintainers and&#10;contributors of at least:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/pypi/warehouse/issues/16620"&gt;PyPI/Warehouse&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://packaging.pypa.io/en/stable/changelog.html"&gt;packaging 24.2&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://hatch.pypa.io/dev/history/hatchling/"&gt;Hatchling 1.27&lt;/a&gt;&lt;/li&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/sdasmarchives/49801923846/"&gt;Amelia&#10;Earhart&amp;rsquo;s 1932 pilot licence&lt;/a&gt; in the&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/sdasmarchives/"&gt;San&#10;Diego Air and Space Museum Archive&lt;/a&gt;, with&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no&#10;known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;&lt;a href="https://www.sovereign.tech/programs/fellowship"&gt;Sovereign Tech Fellowship for Maintainers&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;This is a pilot programme from the &lt;a href="https://www.sovereign.tech/"&gt;Sovereign Tech Agency&lt;/a&gt;&#10;to pay maintainers of critical open source technologies in the public interest.&lt;/p&gt;&#10;&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&#10;focus on &lt;a href="https://github.com/python/cpython"&gt;CPython&lt;/a&gt;, including as release manager for&#10;&lt;a href="https://peps.python.org/pep-0745/"&gt;Python 3.14&lt;/a&gt; (under development with full release&#10;due in October 2025, supported until 2030) and the next one (development starts in May&#10;2025 for release in October 2026, supported until 2031).&lt;/p&gt;&#10;&lt;p&gt;I&amp;rsquo;m delighted to work with the Sovereign Tech Agency as they really get open source.&#10;Supported by Germany&amp;rsquo;s Ministry for Economic Affairs and Climate Action, they recognise&#10;the importance of open source and its place as critical digital infrastructure in the&#10;public interest. In their &lt;a href="https://www.sovereign.tech/mission"&gt;own words&lt;/a&gt;:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;The Sovereign Tech Agency supports the development, improvement, and maintenance of&#10;open digital infrastructure. Our goal is to sustainably strengthen the open source&#10;ecosystem. We focus on security, resilience, technological diversity, and the people&#10;behind the code.&lt;/p&gt;&#10;&lt;p&gt;We often don’t notice how much our lives depend on digital infrastructure until it&#10;stops working. But making it available, accessible and secure is key for&#10;digitalization in the public interest. The Sovereign Tech Agency invests in open&#10;digital infrastructure. It is critical for innovation and economic growth, and forms&#10;the foundation for digitalization across sectors.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;In addition to CPython core development and release management, I&amp;rsquo;ll continue to&#10;maintain other Python projects such as &lt;a href="https://pillow.readthedocs.io/"&gt;Pillow&lt;/a&gt;,&#10;&lt;a href="https://github.com/python-humanize/humanize"&gt;humanize&lt;/a&gt; and&#10;&lt;a href="https://github.com/termcolor/termcolor"&gt;termcolor&lt;/a&gt;, and CLI utilities such as&#10;&lt;a href="https://github.com/hugovk/norwegianblue"&gt;norwegianblue&lt;/a&gt;,&#10;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt; and&#10;&lt;a href="https://github.com/hugovk/pepotron"&gt;pepotron&lt;/a&gt;, and help out more widely in the Python&#10;ecosystem.&lt;/p&gt;&#10;&lt;p&gt;Tasks include:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Bug fixing, issue triaging, PR review and security improvements&lt;/li&gt;&#10;&lt;li&gt;Mentoring new contributors to foster community growth and diversity&lt;/li&gt;&#10;&lt;li&gt;Communicating updates to the wider community via &lt;a href="https://hugovk.dev/"&gt;blog posts&lt;/a&gt; and&#10;&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;&#10;&lt;li&gt;&lt;a href="https://github.com/python/core-workflow"&gt;Workflow&lt;/a&gt;,&#10;&lt;a href="https://github.com/python/cpython/actions/"&gt;CI&lt;/a&gt;, and infrastructure optimisations to&#10;streamline core development tasks&lt;/li&gt;&#10;&lt;li&gt;Maintenance and improvement of &lt;a href="http://peps.python.org/"&gt;PEP&lt;/a&gt; editing infrastructure&#10;and related documentation tools&lt;/li&gt;&#10;&lt;li&gt;Automation and &lt;a href="https://hugovk.dev/dashboard/"&gt;dashboard&lt;/a&gt; creation for triage scripts&#10;to assist other contributors&lt;/li&gt;&#10;&lt;li&gt;Process and workflow improvements for CI and core contributor environments&lt;/li&gt;&#10;&lt;li&gt;Ongoing documentation enhancements, including hosting monthly&#10;&lt;a href="https://docs-community.readthedocs.io/en/latest/"&gt;Documentation Working Group&lt;/a&gt;&#10;meetings, improving build processes, and focusing on accessibility&lt;/li&gt;&#10;&lt;li&gt;Updating and modernising projects for compatibility with the latest Python versions&lt;/li&gt;&#10;&lt;li&gt;Implementing stronger security measures and using&#10;&lt;a href="https://docs.pypi.org/trusted-publishers/"&gt;Trusted Publishing&lt;/a&gt; and&#10;&lt;a href="https://docs.pypi.org/attestations/"&gt;digital attestations&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Adding and maintaining Python support for widely used PyPI packages&lt;/li&gt;&#10;&lt;li&gt;Improving community-wide tooling, documentation, and accessibility&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;Meet my&#10;&lt;a href="https://www.sovereign.tech/news/meet-the-sovereign-tech-fellows"&gt;fellow Sovereign Tech Fellows&lt;/a&gt;,&#10;and by the way, the Sovereign Tech Agency is currently hiring, check out their&#10;&lt;a href="https://www.sovereign.tech/jobs"&gt;open positions&lt;/a&gt;.&lt;/p&gt;&#10;</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;&#10;&lt;a href="https://web.archive.org/web/20220115150826/https://twitter.com/hugovk/status/1482367773577822208"&gt;thread from 15th January 2022&lt;/a&gt;&#10;about my first CPython bug. Eight days from report to fix to merge, not bad!&lt;/p&gt;&#10;&lt;hr&gt;&#10;&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&#10;&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;!&#10;But in a good way! 😇&lt;/p&gt;&#10;&lt;p&gt;Python 3.11 is due out in October, but they make early alpha, beta and release&#10;candidates available for people to help test Python itself and their own code before the&#10;big release.&lt;/p&gt;&#10;&lt;p&gt;So I tested &lt;a href="https://fosstodon.org/@pillow"&gt;Pillow&lt;/a&gt;&amp;hellip;&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_a1d09cfbe34a4759.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_81c5022f200cf688.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1510"&#10; height="1236"&#10; class="mx-auto my-0 rounded-md"&#10; 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 😅"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_87eaf8f0fc13934.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/pablo_hu_9feca2ddccdcd4a.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Next I tried building the documentation with 3.11 ❌&lt;/p&gt;&#10;&lt;p&gt;The docs program, Sphinx, emitted a couple of warnings. Warnings are often missed&#10;because they don&amp;rsquo;t error. But luckily we use the &amp;ldquo;-W&amp;rdquo; option to turn warnings into hard&#10;errors.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_369b70cea750330.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_c8cf603c8b458b54.webp 1200w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1200"&#10; height="885"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Sphinx output showing &amp;ldquo;WARNING: image file not readable&amp;rdquo;"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1_hu_bfd7c0371f39ff5f.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx1.jpg 1200w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_fa1ca5bd5d2b1c31.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_fec4723464028e6f.webp 1082w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1082"&#10; height="1136"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Sphinx output turning the warning into an error"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2_hu_cf46a0ad9ed92dcb.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx2.jpg 1082w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Rather than submitting a report with the full Pillow documentation (lots of files) I&#10;made a&#10;&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;&#10;&lt;p&gt;This makes it easier to investigate what&amp;rsquo;s up.&lt;/p&gt;&#10;&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&#10;problem was that a page in a subdirectory could load an image from one directory, but&#10;not from another, further away directory.&lt;/p&gt;&#10;&lt;p&gt;It occurs for the Python 3.11.0a3 alpha, but not 3.7-3.10.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_db234ca665a369ad.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_5ffef8a2fd350dc.webp 1044w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1044"&#10; height="682"&#10; class="mx-auto my-0 rounded-md"&#10; alt="A screenshot of a GitHub bug report"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3_hu_6eb54c1ec9b797.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx3.jpg 1044w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;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&#10;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&#10;could be a bug in Python.&lt;/p&gt;&#10;&lt;p&gt;He asked me to report it to Python.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_43d7374d0f7b78fc.webp 1024w&#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_cdc5089b58afc047.webp 1200w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1200"&#10; height="824"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Screenshot of Takeshi&amp;rsquo;s reply"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4_hu_6c3cdab9893df6c.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2025/how-to-delay-a-python-release/sphinx4.jpg 1200w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;minimal example.&lt;/p&gt;&#10;&lt;p&gt;Half an hour later Christian pointed out a change which may have caused this.&lt;/p&gt;&#10;&lt;p&gt;I tested and confirmed.&lt;/p&gt;&#10;&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;&#10;&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&#10;testing.&lt;/p&gt;&#10;&lt;p&gt;I didn&amp;rsquo;t know how to fix the bug, but I could write some test cases!&lt;/p&gt;&#10;&lt;p&gt;neonene then took the tests and&#10;&lt;a href="https://github.com/python/cpython/pull/30362"&gt;fixed the bug&lt;/a&gt;! In doing so they found&#10;even more bugs!&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;But it turns out path handling can get pretty complicated in places, so Steve decided to&#10;only fix my bug now to get it released and the others can be sorted later.&lt;/p&gt;&#10;&lt;p&gt;The fix was merged and I confirmed it also worked with Sphinx ✅&lt;/p&gt;&#10;&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;&#10;&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&#10;the world in October. Along the way we found more issues to address.&lt;/p&gt;&#10;&lt;p&gt;Short version: test your code with 3.11 now, you may find issues in your code or in&#10;Python itself 🚀&lt;/p&gt;&#10;&lt;hr&gt;&#10;&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&#10;&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;&#10;</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&#10;&lt;a href="https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/"&gt;Google BigQuery dataset&lt;/a&gt;.&#10;You need a Google account and credentials, and Google gives 1 TiB of free quota per&#10;month.&lt;/p&gt;&#10;&lt;p&gt;Each month, I have automation to fetch the download numbers for the 8,000 most popular&#10;packages over the past 30 days, and make it available as more accessible JSON and CSV&#10;files at &lt;a href="https://hugovk.dev/top-pypi-packages/"&gt;Top PyPI Packages&lt;/a&gt;. This data is&#10;&lt;a href="https://hugovk.dev/top-pypi-packages/#users"&gt;widely used&lt;/a&gt; for&#10;&lt;a href="https://github.com/hugovk/top-pypi-packages/issues/23"&gt;research in academia and industry&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;However, as more packages and releases are uploaded to PyPI, and there are more and more&#10;downloads logged, the amount of billed data increases too.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_b5e55ba342907433.webp 828w&#10; &#10; &#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee_hu_b5e55ba342907433.webp 828w&#10; &#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="828"&#10; height="502"&#10; class="mx-auto my-0 rounded-md"&#10; alt="BigQuery TB billed"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee.png 828w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/sego7uxbpslifzd791ee.png 828w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;This chart shows the amount of data billed per month.&lt;/p&gt;&#10;&lt;p&gt;At first, I was only collecting downloads data for 4,000 packages, and it was fetched&#10;for two queries: downloads over 365 days and over 30 days. But as time passed, it&#10;started using up too much quota to download data for 365 days.&lt;/p&gt;&#10;&lt;p&gt;So I ditched the 365-day data, and increased the 30-day data from 4,000 to 5,000&#10;packages. Later, I checked how much quota was being used and increased from 5,000&#10;packages to 8,000 packages.&lt;/p&gt;&#10;&lt;p&gt;But then I exceeded the BigQuery monthly quota of 1 TiB fetching data for July 2024.&lt;/p&gt;&#10;&lt;p&gt;To fetch the missing data and investigate what&amp;rsquo;s going in, I started Google Cloud&amp;rsquo;s&#10;90-day, $300 (€277.46) free-trial 💸&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s what I found!&lt;/p&gt;&#10;&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&#10;default, it only fetches downloads for pip.&lt;/p&gt;&#10;&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;&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;project&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;download count&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;boto3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;37,251,744&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;aiobotocore&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;16,252,824&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;urllib3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;16,243,278&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;botocore&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;15,687,125&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;requests&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;13,271,314&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;s3fs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;12,865,055&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;s3transfer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;12,014,278&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;fsspec&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;11,982,305&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;charset-normalizer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;11,684,740&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;certifi&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;11,639,584&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;&lt;strong&gt;158,892,247&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&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&#10;installers&lt;/em&gt;:&lt;/p&gt;&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;project&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;download count&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;boto3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;39,495,624&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;botocore&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;17,281,187&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;urllib3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;17,225,121&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;aiobotocore&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;16,430,826&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;requests&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;14,287,965&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;s3fs&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;12,958,516&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;charset-normalizer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;12,781,405&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;certifi&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;12,647,098&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;setuptools&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;12,608,120&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;idna&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;12,510,335&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;&lt;strong&gt;168,226,197&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;So we can see the default pip-only costs an extra 25% data processed and data billed,&#10;and costs an extra 25% in dollars.&lt;/p&gt;&#10;&lt;p&gt;Unsurprisingly, the actual download counts are higher for all installers. The ranking&#10;has changed a bit, but I expect we&amp;rsquo;re still getting more-or-less the same packages in&#10;the top thousands of results.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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&#10;&lt;code&gt;AND details.installer.name = &amp;quot;pip&amp;quot;&lt;/code&gt; condition. It seems reasonable it would cost more&#10;to do extra filtering work.&lt;/p&gt;&#10;&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;&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;installer name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;download count&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;pip&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1,121,198,711&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;uv&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;117,194,833&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;requests&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;29,828,272&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;poetry&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;23,009,454&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;None&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;8,916,745&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;bandersnatch&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;6,171,555&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;setuptools&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1,362,797&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Bazel&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1,280,271&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Browser&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1,096,328&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Nexus&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;593,230&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Homebrew&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;510,247&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Artifactory&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;69,063&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;pdm&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;62,904&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;OS&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;13,108&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;devpi&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;9,530&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;conda&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;2,272&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;pex&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;194&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;&lt;strong&gt;1,311,319,514&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;pip still by far the most popular, and unsurprising uv is up there too, with about 10%&#10;of pip&amp;rsquo;s downloads.&lt;/p&gt;&#10;&lt;p&gt;The others are about 25% or less of uv. A lot of them are mirroring services that we&#10;wanted to exclude before.&lt;/p&gt;&#10;&lt;p&gt;I think given uv&amp;rsquo;s importance, and my expectation that it will continue to take a bigger&#10;share of the pie, plus especially the extra cost for filtering by just pip, means that&#10;we should switch to fetching data for all downloaders. Plus the others don&amp;rsquo;t account for&#10;that much of the pie.&lt;/p&gt;&#10;&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&#10;try and remain under quota. But it turns out it makes no difference how many packages&#10;you query!&lt;/p&gt;&#10;&lt;p&gt;I fetched data for just one day and all installers for different package limits: 1000,&#10;2000, 3000, 4000, 5000, 6000, 7000, 8000. Sample query:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_2e901099213ea8fa.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_8752a6663c015321.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1440"&#10; height="870"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Cost and bytes for 1 day with different package limits are the same"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_9897b458b229b.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/03tmt980ypp67qryra26_hu_33e9d3f4013f1b99.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Repeating with one day but filtering for pip only:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_b5d35fcd7c1b84fe.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_f70325019a5c34a5.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1444"&#10; height="866"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Cost and bytes for 1 day still the same for pip"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_c4041c02334b303.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/al96xqbescc64lavi4y7_hu_cada408b11e7346e.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Let&amp;rsquo;s repeat with all installers, but for 30 days, and this time query in decreasing&#10;limits, in case we were only paying for incremental changes: 8000, 7000, 6000, 5000,&#10;4000, 3000, 2000, 1000:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_8df0048fa369488.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_953e8a4b5dbc6363.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1436"&#10; height="878"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Cost and bytes for 30 day still the same no matter how many packages"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_d23efe3409ba50bc.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/jhci1bcxrgncb34wwldm_hu_bd5d7e9013aaefad.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Well then, let&amp;rsquo;s repeat with the limit increasing by powers of ten, up to 1,000,000!&#10;This last one fetches data for all 531,022 packages on PyPI:&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;limit&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;projects count&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;estimated cost&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;bytes billed&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;bytes processed&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,746,560&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,720,943&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,746,560&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,720,943&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,746,560&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,720,943&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1,000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,746,560&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,720,943&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;8,000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,746,560&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,720,943&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;10,000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,746,560&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,720,943&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;100,000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,746,560&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,720,943&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1000000&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;531,022&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;0.20&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,746,560&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;43,447,720,943&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_616a9f6eb933c6af.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_2449ecc67ce7a3af.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1434"&#10; height="868"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Still same flat cost and bytes for 1 or 10 or 1,000 or 1,000,000 packages"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_2c1872e0d1284c97.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/3pitkptggoit9xdypjp2_hu_e7b36c06a82419e.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Again, same cost, whether for 1 package or 531,022 packages!&lt;/p&gt;&#10;&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&#10;30 days.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the estimated cost and bytes billed (for one package, all installers) between one&#10;and 30 days (&lt;code&gt;f&amp;quot;pypinfo --all --json --indent 0 --days {days} --limit 1 '' project&amp;quot;&lt;/code&gt;),&#10;showing a roughly linear increase:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_22806785ded6e284.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_766570250c718306.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1672"&#10; height="1028"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Cost and bytes increase as the number of days increase"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_262e7c9ff66b5779.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/aa4kq8d5neuxj9mf1g2v_hu_e4eac705f42fa756.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;li&gt;&#10;&lt;p&gt;It doesn&amp;rsquo;t matter how many packages I fetch data for, I might as well fetch all and&#10;make it available to everyone, depending on the size of the data file. It will make&#10;sense to still offer a smaller file with 8,000 or so packages: often you just need a&#10;large-ish yet manageable number.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;It costs more to filter for only downloads from pip, so I&amp;rsquo;ve switched to fetching data&#10;for all installers.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;The number of days affects the cost, so I will need to decrease this in the future to&#10;stay within quota. For example, at some point I may need to switch from 30 to 25 days,&#10;and later from 25 to 20 days.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;More details from the investigation, the scripts and data files can be found at&#10;&lt;a href="https://github.com/hugovk/top-pypi-packages/issues/36"&gt;hugovk/top-pypi-packages#36&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;And let me know if you know any tricks to reduce costs!&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/royalaustralianhistoricalsociety/44366270300/"&gt;The&#10;Balancing Rock, Stonehenge, Near Glen Innes, NSW&lt;/a&gt;&amp;rdquo; by the&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/royalaustralianhistoricalsociety/"&gt;Royal&#10;Australian Historical Society&lt;/a&gt;, with&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no&#10;known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;Actions around 1.5 times as fast.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;&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;&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; python-version: &amp;#34;3.x&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- cache: pip&#10;&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&#10;&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&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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,&#10;which is faster for the initial seed operation when there&amp;rsquo;s no cache.&lt;/p&gt;&#10;&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;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Before&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;After&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Times faster&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;No cache&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635610765/job/32405339441"&gt;60s&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635612034/job/32405343101"&gt;37s&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.62&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;With cache&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635702058/job/32405618322"&gt;11s&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635703339/job/32405622146"&gt;11s&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.00&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;&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&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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&#10;&lt;a href="https://github.com/tox-dev/tox-uv"&gt;tox-uv&lt;/a&gt;:&lt;/p&gt;&#10;&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 }}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; uses: actions/setup-python@v5&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; with:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; python-version: ${{ matrix.python-version }}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; allow-prereleases: true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- cache: pip&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- - name: Install dependencies&#10;&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: |&#10;&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&#10;&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&#10;&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&#10;&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&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - name: Tox tests&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; run: |&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- tox -e py&#10;&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&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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&#10;only need to install uv, and use &lt;code&gt;uvx&lt;/code&gt; to both install tox-uv and run tox, for faster&#10;installs of tox, the virtual environment, and the dependencies within it.&lt;/p&gt;&#10;&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;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Before&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;After&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Times faster&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;No cache&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635722298/usage"&gt;2m 0s&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635723255/usage"&gt;1m 26s&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.40&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;strong&gt;With cache&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635745236/usage"&gt;1m 58s&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635746618/usage"&gt;1m 22s&lt;/a&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;1.44&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&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&#10;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;&#10;&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]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-commands_pre =&#10;&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&#10;&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 =&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pass_env =&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; FORCE_COLOR&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; commands =&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {envpython} -m pytest {posargs}&#10;&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&#10;&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&#10;&lt;code&gt;[testenv]&lt;/code&gt; to inject them.&lt;/p&gt;&#10;&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&#10;in GitHub Actions.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://finna.fi/Record/hkm.BB327C7B-90B8-45DF-90A1-ABC7B74F6BAA?imgid=1"&gt;Road&#10;cycling at the 1952 Helsinki Olympics&lt;/a&gt;&amp;rdquo; by&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/people/nlmhmd/"&gt;Olympia-Kuva&#10;Oy &amp;amp; Helsinki City Museum&lt;/a&gt;,&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/publicdomain/mark/1.0/deed.en"&gt;Public&#10;Domain&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;Meta in Bellevue, WA!&lt;/p&gt;&#10;&lt;p&gt;The idea: bring a bunch of Python core team members, triagers, and special guests to the&#10;same room for a week. It&amp;rsquo;s hugely beneficial and productive, we held many in-depth&#10;discussions that just don&amp;rsquo;t happen when we&amp;rsquo;re all remote and async, and got to work on&#10;many different things together.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_c6e4bafd78f30a28.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_21c2782493cd289b.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4096"&#10; height="1305"&#10; class="mx-auto my-0 rounded-md"&#10; alt="A panoramic shot of a room of people, some sitting at desks, some standing, some working on laptops, some in discussion."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_cc8f7a11aa9c14dc.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/5rpkesswa2xbczkuwoq7_hu_27eeee51c9e761a6.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;The sprint room&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;During the week, I reviewed 39 PRs, created 15, merged 10, updated 4, and closed 2&#10;issues.&lt;/p&gt;&#10;&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&#10;with &lt;a href="https://fosstodon.org/@brettcannon"&gt;Brett Cannon&lt;/a&gt; one of his project ideas which&#10;will come after lock files, and after the next big one.&lt;/p&gt;&#10;&lt;p&gt;Also as RM, discussed with &lt;a href="https://cloudisland.nz/@freakboy3742"&gt;Russell Keith-Magee&lt;/a&gt;,&#10;Ned Deily, &lt;a href="https://mastodon.social/@ambv"&gt;Łukasz Langa&lt;/a&gt; and&#10;&lt;a href="https://social.coop/@Yhg1s"&gt;Thomas Wouters&lt;/a&gt; about&#10;&lt;a href="https://beeware.org/news/buzz/2024q4-roadmap/"&gt;including official binaries for iOS and Android&lt;/a&gt;,&#10;which wandered into ideas about security releases.&lt;/p&gt;&#10;&lt;p&gt;I did some maintenance of our PyPI projects, adding&#10;&lt;a href="https://peps.python.org/pep-0740/"&gt;PEP 740 attestations&lt;/a&gt;, support for the&#10;&lt;a href="https://peps.python.org/pep-0719/"&gt;new Python 3.13&lt;/a&gt; and dropping support for the&#10;&lt;a href="https://peps.python.org/pep-0569/"&gt;very-nearly-EOL 3.8&lt;/a&gt;.&lt;/p&gt;&#10;&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&#10;&lt;a href="https://fosstodon.org/@AlexWaygood"&gt;Alex Waygood&lt;/a&gt;, who on Wednesday narrowed it down to&#10;a problem with the&#10;&lt;a href="https://github.com/python/cpython/issues/124567"&gt;new incremental garbage collector&lt;/a&gt;,&#10;which would go on to be reverted by Friday and result in&#10;&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;.&#10;Not ideal, but much better to discover these things before the big release.&lt;/p&gt;&#10;&lt;p&gt;We had a Q&amp;amp;A session with the Steering Council:&#10;&lt;a href="https://mastodon.social/@pumpichank"&gt;Barry Warsaw&lt;/a&gt;, Emily Morehouse,&#10;&lt;a href="https://infosec.exchange/@gpshead"&gt;Gregory P. Smith&lt;/a&gt;, Pablo Galindo Salgado and Thomas.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_a238f7da663fc1de.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_4a0421d43ff5fc00.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; alt="The steering council: Barry, Emily, Greg with a microphone, Pablo and Thomas sitting on high stools."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_2d5fddaecae1b09c.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/g7yyzyqtztoednyacr1r_hu_16a66ffa3eebb7cb.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;The Python Steering Council&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;Proofread Guido van Rossum&amp;rsquo;s&#10;&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;&#10;for electing future steering councils.&lt;/p&gt;&#10;&lt;p&gt;Discussed with Eric Snow his novel method for displaying many code samples in a table,&#10;using &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; disclosures to prevent the table being too wide. Looks like a good&#10;solution!&lt;/p&gt;&#10;&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&#10;&lt;a href="https://peps.python.org/pep-2026/"&gt;PEP 2026&lt;/a&gt; (Calendar versioning for Python) and Barry&#10;gave it a final review. Ready for submission!&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://fosstodon.org/@sethmlarson"&gt;Seth Larson&lt;/a&gt;, the PSF&#10;&lt;a href="https://sethmlarson.dev/security-developer-in-residence"&gt;Security Developer-in-Residence&lt;/a&gt;,&#10;wasn&amp;rsquo;t at the sprint but we discussed our plan to stop providing GPG signatures for&#10;CPython and rely on SigStore instead. Expect a PEP soon!&lt;/p&gt;&#10;&lt;p&gt;Also not at the sprint, I recommended PSF&#10;&lt;a href="https://pyfound.blogspot.com/2024/07/announcing-our-new-infrastructure.html"&gt;Infrastructure Engineer&lt;/a&gt;&#10;&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&#10;aboard!&lt;/p&gt;&#10;&lt;p&gt;The whole room discussed including&#10;&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;&#10;&lt;p&gt;We had a Q&amp;amp;A session with two of the three Developers-in-Residence, Łukasz and&#10;&lt;a href="https://mastodon.social/@encukou"&gt;Petr Viktorin&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_5755e30f547f4ea2.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_1f27c95c34e04a13.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Łukasz (with a microphone) and Petr sitting on high stools."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_52ac56b1a576ab76.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/vbn0yvsz7t9t2j7yfd2j_hu_53952b3d19b37af4.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;Q&amp;A with Łukasz and Petr&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;Discussed&#10;&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;&#10;with &lt;a href="https://fosstodon.org/@mariatta"&gt;Mariatta&lt;/a&gt;, Greg and Thomas.&lt;/p&gt;&#10;&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&#10;stickers he generously designed and printed up for us. Thanks!&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_9d5765cf975d3705.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_95b68a05a5853e9e.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; 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."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_ad3b4c4f90380f59.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/arqsq2qg7h1giiko2g40_hu_20f93f96423f3c8d.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;PCDS 2024 stickers by Larry&lt;/small&gt;&lt;/center&gt;&#10;&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&#10;&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;&#10;&lt;p&gt;Brett discussed whether we should update&#10;&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;&#10;&lt;p&gt;Brandt Bucher gave us all an update on the progress of the Just-in-Time (JIT) compiler&#10;(&amp;ldquo;we went from 0% slower to 0% faster!&amp;rdquo;) and we discussed plans for Python 3.14.&lt;/p&gt;&#10;&lt;p&gt;Because I couldn&amp;rsquo;t attend Thursday&amp;rsquo;s&#10;&lt;a href="https://helsinki-python.github.io/"&gt;Helsinki Python meetup&lt;/a&gt; due to being at another&#10;kind of Python meetup on the other side of the world, I gave the famous HelPy quiz to&#10;the assembled core devs. Unsurprisingly they did pretty well, but the most incorrect&#10;answer was a pleasant surprise: we&amp;rsquo;ve had ~400 not ~80 new contributors to Python 3.13!&lt;/p&gt;&#10;&lt;p&gt;Pablo performed card tricks!&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_82de655e177e8097.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_c7bcb7cab8c9b252.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Pablo splaying open a deck of PyCon US playing cards and Greg picking one."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_d2759b7d766d1253.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/tlusli7ylxa3di48nmct_hu_77f874c8fdc729d1.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;Magic from Pablo&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;Meta took us out for a delicious dinner at a local fish restaurant. Thank you!&lt;/p&gt;&#10;&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&#10;tools to create a modern, interactive tutorial.&lt;/p&gt;&#10;&lt;p&gt;Also during the week, continued work with Adam Turner on improving the&#10;&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&#10;tag-teamed PR reviews overnight. After&#10;&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;,&#10;we&amp;rsquo;ve got the full HTML build loop for 13 languages × 3 versions down from over 40 hours&#10;to just under 9 hours, with more improvements coming.&lt;/p&gt;&#10;&lt;p&gt;Made a &lt;a href="https://hugovk-cpython.readthedocs.io/en/pydata-sphinx-theme/"&gt;demo&lt;/a&gt; of the&#10;CPython docs using the&#10;&lt;a href="https://pydata-sphinx-theme.readthedocs.io/en/stable/"&gt;PyData Sphinx Theme&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Along with around 25 others, I was on Łukasz and Pablo&amp;rsquo;s&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_5148b041d531e001.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_a62537ed55085d50.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="4000"&#10; height="3000"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Łukasz and Pablo in their ad-hoc podcast studio in a Meta meeting room."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_b2988225b852219c.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/obqejp8n5acztsb8knaq_hu_757224b2a6d15927.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Itamar gave us cake for the podcast&amp;rsquo;s first birthday!&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_b01ace4d19c3b8fc.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_e97ea3d6d206c0bd.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="2439"&#10; height="1747"&#10; class="mx-auto my-0 rounded-md"&#10; 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."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_99bc277cff288229.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/python-core-sprint/8gz97wzjn5khsjg4anln_hu_6f4c9c58311527ca.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;cake.py. Photo by Itamar Oren.&lt;/small&gt;&lt;/center&gt;&#10;&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&#10;hosting!&lt;/p&gt;&#10;&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&#10;recommend the&#10;&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;&#10;with short interviews with some 25 attendees! Łukasz and Pablo were also guests on the&#10;&lt;a href="https://changelog.com/podcast/611"&gt;Changelog podcast&lt;/a&gt; during the sprint.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo by Itamar Oren&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;&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;&#10;&lt;p&gt;22nd May 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by &lt;a href="https://mastodon.social/@nedbat@hachyderm.io/112479219635272740"&gt;Ned Batchelder&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;24th May 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;a href="https://wagtail.org/blog/pycon-2024/"&gt;Wagtailers spread their wings at PyCon 2024&lt;/a&gt; by&#10;&lt;a href="https://fosstodon.org/@vossisboss"&gt;Meagen Voss&lt;/a&gt; (@vossisboss)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&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&#10;Fitsner&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;27th May 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by &lt;a href="https://mastodon.social/@tomyhsieh"&gt;Tomy Hsieh&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;28th May 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;a href="https://treyhunner.com/2024/05/pycon-2024-reflection/"&gt;PyCon 2024 Reflection&lt;/a&gt; by&#10;&lt;a href="https://mastodon.social/@treyhunner/112520554890667118"&gt;Trey Hunner&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;&lt;a href="https://simonwillison.net/2024/May/28/weeknotes/"&gt;Weeknotes: PyCon US 2024&lt;/a&gt; by&#10;&lt;a href="https://fedi.simonwillison.net/@simon/112520529116346191"&gt;Simon Willison&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&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;&#10;by Luis Juncal &amp;amp; Yolanda Robla&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;30th May 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&#10;&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;&#10;by &lt;a href="https://fosstodon.org/@leahawasser/112554205196871020"&gt;Leah Wasser&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&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;&#10;by &lt;a href="https://hachyderm.io/@muheuenga/112530136789502549"&gt;Ngazetungue Muheue&lt;/a&gt;&#10;(@ngazetungue)&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;1st June 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://katherinemichel.github.io/portfolio/pycon-us-2024-recap.html"&gt;PyCon US 2024 Recap&lt;/a&gt;&#10;by &lt;a href="https://fosstodon.org/@kati/112542145378019538"&gt;Kati Michel&lt;/a&gt; (@katherinemichel)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;4th June 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://mangoumbrella.com/post/pycon-us-2024"&gt;PyCon US 2024&lt;/a&gt; by&#10;&lt;a href="https://mastodon.social/@y2mango/112556835094650562"&gt;Yilei Yang&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;13th June 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by &lt;a href="https://fosstodon.org/@sethmlarson"&gt;Seth Michael Larson&lt;/a&gt; (@sethmlarson)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;14th June 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by &lt;a href="https://fosstodon.org/@sethmlarson"&gt;Seth Michael Larson&lt;/a&gt; (@sethmlarson)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;16th June 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by &lt;a href="https://mastodon.world/@davidfstr"&gt;David Foster&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;19th June 2024&lt;/p&gt;&#10;&lt;ul&gt;&#10;&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;&#10;by &lt;a href="https://mastodon.social/@monics"&gt;Monica Oyugi&lt;/a&gt; (@monicaoyugi)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo: Downtown Pittsburgh seen between the Andy Warhol Bridge and Roberto&#10;Clemente Bridge&lt;/small&gt;&lt;/p&gt;&#10;</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;&#10;&lt;p&gt;The Python 3.13 beta is out! 🎉&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://peps.python.org/pep-0719/#release-schedule"&gt;PEP 719&lt;/a&gt; defines the release&#10;schedule for Python 3.13.0:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The first beta candidate came out on 8th May 2024&lt;/li&gt;&#10;&lt;li&gt;The first release candidate is set for 30th July 2024&lt;/li&gt;&#10;&lt;li&gt;And the full release is set for 1st October 2024&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In his&#10;&lt;a href="https://discuss.python.org/t/python-3-13-0b1-now-available/52891?u=hugovk"&gt;announcement&lt;/a&gt;,&#10;Thomas Wouters, release manager for Python 3.12 and 3.13, said:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;We &lt;strong&gt;strongly encourage&lt;/strong&gt; maintainers of third-party Python projects to test with 3.13&#10;during the beta phase and report issues found to the&#10;&lt;a href="https://github.com/python/cpython/issues"&gt;Python bug tracker&lt;/a&gt; as soon as possible.&#10;While the release is planned to be feature complete entering the beta phase, it is&#10;possible that features may be modified or, in rare cases, deleted up until the start&#10;of the release candidate phase (Tuesday 2024-07-30). Our goal is to have no ABI&#10;changes after beta 4 and as few code changes as possible after 3.13.0rc1, the first&#10;release candidate. To achieve that, it will be &lt;strong&gt;extremely important&lt;/strong&gt; to get as much&#10;exposure for 3.13 as possible during the beta phase.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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.&#10;There&amp;rsquo;s two big benefits:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;There have been&#10;&lt;a href="https://docs.python.org/3.13/whatsnew/3.13.html#removed"&gt;removals and changes&lt;/a&gt; in&#10;Python 3.13. Testing now helps us make our code compatible and avoid any big&#10;surprises (for us and our users) at the big launch in October.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;We might find bugs in Python itself! Reporting those will help get them fixed and&#10;help everyone.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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&#10;&lt;a href="https://github.com/actions/setup-python#supported-version-syntax"&gt;actions/setup-python&lt;/a&gt;,&#10;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;&#10;&lt;p&gt;For example:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;a bit neater, and when 3.13.0 final is released in October, it will continue testing&#10;with full release versions.)&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/deadsnakes/action"&gt;deadsnakes/action&lt;/a&gt; to test the latest nightly&#10;build:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&lt;a href="https://pypi.org/classifiers/"&gt;Trove classifier&lt;/a&gt;? Some&#10;&lt;a href="https://pyreadiness.org/3.13/"&gt;projects already have&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p&gt;If you have a pure Python project, you can release now.&lt;/p&gt;&#10;&lt;p&gt;If you have C extensions and other projects depend on yours, a preview release with&#10;wheels will help them test and prepare. I&amp;rsquo;ve already started releasing these.&lt;/p&gt;&#10;&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&#10;rebuild your wheels and upload them to an existing PyPI release by adding an optional&#10;&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;&#10;to the filename:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;The wheel filename is&#10;&lt;code&gt;{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl&lt;/code&gt;.&lt;/p&gt;&#10;&lt;p&gt;build tag: [&amp;hellip;] Acts as a tie-breaker if two wheel file names are the same in all&#10;other respects (i.e. name, version, and other tags).&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;For example, this updates the filename and metadata with build number 1, and removes the&#10;original file:&lt;/p&gt;&#10;&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;&#10;&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&#10;&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;&#10;&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&#10;&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;&#10;&lt;p&gt;In any case, let&amp;rsquo;s start testing 3.13 now! 🚀&lt;/p&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&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;&#10;&lt;a href="https://mastodon.social/@Lorenanicole/112468770670490719"&gt;PyLadies Auction&lt;/a&gt;: &amp;ldquo;A pair of&#10;hand-woven snakes (&lt;a href="https://www.pylatam.org/"&gt;PyCon Latam&lt;/a&gt; 2023 edition), donated by the&#10;PyCon Latam Organizers. This is a souvenir from PyCon Latam held in Mexica 2023 that&#10;represents the snakes of the PyLatam community logo. They are made completely by&#10;hand.&amp;quot;&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;scribble on, and stuff into a pocket (technical term: &lt;em&gt;the affordances of paper&lt;/em&gt;).&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s some custom CSS and JavaScript to make it nicely printable.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Install the&#10;&lt;a href="https://chrome.google.com/webstore/detail/styler/bogdgcfoocbajfkjjolkmcdcnnellpkb?hl=en"&gt;Styler browser extension&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&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&#10;click the Styler extension&amp;rsquo;s S icon&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Paste this CSS into the upper box:&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;&#10;&lt;li&gt;Paste this JavaScript into the lower box:&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;&#10;&lt;li&gt;Print!&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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&#10;individual presentation pages such as&#10;&lt;code&gt;https://us.pycon.org/2024/schedule/presentation/61/&lt;/code&gt;&lt;/p&gt;&#10;&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&#10;original.&lt;/p&gt;&#10;&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&#10;site so you can browse the original version.&lt;/p&gt;&#10;&lt;p&gt;See also:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="../2023/printable-pycon-2023-schedule"&gt;Printable PyCon 2023 schedule&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</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&#10;underway to implement &lt;em&gt;experimental support&lt;/em&gt; for&#10;&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;&#10;&lt;p&gt;See also&#10;&lt;a href="https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython"&gt;Free-threaded CPython&lt;/a&gt;&#10;in &amp;ldquo;What&amp;rsquo;s New in Python 3.13?&amp;rdquo;&lt;/p&gt;&#10;&lt;p&gt;As the Steering Council noted in their&#10;&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;,&#10;to succeed it&amp;rsquo;s important to have community support.&lt;/p&gt;&#10;&lt;p&gt;Projects will need to test their code with free-threaded (aka &amp;ldquo;nogil&amp;rdquo; but&#10;&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;)&#10;Python builds to help us find bugs in CPython, and to check their code is compatible.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s some ways to test.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Official installers&lt;/li&gt;&#10;&lt;li&gt;Build it yourself&lt;/li&gt;&#10;&lt;li&gt;deadsnakes&lt;/li&gt;&#10;&lt;li&gt;GitHub Actions?&lt;/li&gt;&#10;&lt;li&gt;Fedora&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&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&#10;the free-threading binaries, which also installs &lt;code&gt;python3.13t&lt;/code&gt; alongside the regular&#10;&lt;code&gt;python3.13&lt;/code&gt;:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/python/cpython/issues/120098"&gt;python/cpython#120098&lt;/a&gt;&lt;/li&gt;&#10;&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;&#10;&lt;/ul&gt;&#10;&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&#10;the&#10;&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;&#10;&lt;p&gt;For example, on macOS I run:&lt;/p&gt;&#10;&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;\&#10;&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; \&#10;&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 \&#10;&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 \&#10;&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; \&#10;&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&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;False&#10;&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;&#10;&lt;ul&gt;&#10;&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;&#10;&lt;/ul&gt;&#10;&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;&#10;&lt;p&gt;For example:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;&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&#10;&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;&#10;&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;&#10;&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&#10;&lt;a href="https://github.com/actions/setup-python/issues/771"&gt;actions/setup-python#771&lt;/a&gt;, they&amp;rsquo;re&#10;looking into it 🤞&lt;/p&gt;&#10;&lt;p&gt;In the meantime, give it an upvote, and use deadsnakes/action ⤴️&lt;/p&gt;&#10;&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,&#10;included free-threaded builds.&lt;/p&gt;&#10;&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&#10;&lt;code&gt;python3.13 - 3.13.0~a2-1+jammy1&lt;/code&gt; at&#10;&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;&#10;&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&#10;&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;&#10;&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;&#10;&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&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;False&#10;&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&#10;(starting in beta 2):&lt;/p&gt;&#10;&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;&#10;&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&#10;&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)]&#10;&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;&#10;&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&#10;&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)]&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;0&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;1&#10;&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&#10;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;.&#10;You can check with &lt;code&gt;sys._is_gil_enabled()&lt;/code&gt;:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;True&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;False&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;True&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;True&#10;&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;&#10;&lt;ul&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/ul&gt;&#10;&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;&#10;&lt;li&gt;&lt;a href="../../2024/help-test-python-313/"&gt;Help test Python 3.13!&lt;/a&gt;&lt;/li&gt;&#10;&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;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://py-free-threading.github.io/"&gt;py-free-threading&lt;/a&gt;&lt;/li&gt;&#10;&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;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/nlmhmd/8616809942/"&gt;George&#10;Mayerle test chart&lt;/a&gt;&amp;rdquo; by&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/people/nlmhmd/"&gt;US&#10;National Library of Medicine&lt;/a&gt;, with&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no&#10;known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;&lt;a href="https://pillow.readthedocs.io/en/latest/installation/basic-installation.html"&gt;Pillow documentation&lt;/a&gt;&#10;we have some pages with tabs for different operating systems:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_1d2f9524750f9848.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_adf4b8fd531c59d2.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1550"&#10; height="776"&#10; class="mx-auto my-0 rounded-md"&#10; 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."&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_208bd901fbf9c61a.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/knze2ghlj0qmjucz354t_hu_380bd23e7cb8554f.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;It&amp;rsquo;s possible to add some JavaScript so that the matching tab is activated based on the&#10;visitor&amp;rsquo;s operating system.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s how!&lt;/p&gt;&#10;&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;&#10;extension to your docs'&#10;&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;&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sphinx-inline-tabs&amp;gt;=2023.4.21&#10;&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&#10;&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;&#10;to your &lt;code&gt;_static/&lt;/code&gt; directory:&lt;/p&gt;&#10;&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&#10;&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&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&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&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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&#10;&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;&#10;&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&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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&#10;&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/conf.py#L15"&gt;extension&lt;/a&gt;&#10;and&#10;&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/conf.py#L49-L51"&gt;JavaScript&lt;/a&gt;&#10;to your &lt;code&gt;conf.py&lt;/code&gt;:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]&lt;/span&gt;&#10;&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;&#10;&lt;p&gt;Add tabs to the reStructuredText files.&lt;/p&gt;&#10;&lt;p&gt;For&#10;&lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/testing/run-write-tests.rst#L58-L74"&gt;example&lt;/a&gt;,&#10;here we have three different commands; one for Unix, one for macOS, and one for Windows:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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;&#10;&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&#10;&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;&#10;to activate the correct tab:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; });&lt;span class="err"&gt;&#10;&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;&#10;&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&#10;&lt;a href="https://devguide.python.org/testing/run-write-tests/#running"&gt;here&lt;/a&gt;. When the page&#10;loads, the browser finds the browser name, and activates the tabs with a matching name.&lt;/p&gt;&#10;&lt;p&gt;If you have many sets of tabs on a page, the corresponding OS tab will be activated for&#10;all. And if you click on another OS tab, all the others with the same name are&#10;activated.&lt;/p&gt;&#10;&lt;p&gt;Happy tabbing!&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/nationalgalleries/3110282571/"&gt;The&#10;Great Pyramid and the Sphinx&lt;/a&gt;&amp;rdquo; by&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/nationalgalleries/"&gt;National&#10;Galleries of Scotland&lt;/a&gt;, with&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no&#10;known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;doesn&amp;rsquo;t really make sense to test it.&lt;/p&gt;&#10;&lt;p&gt;For example, maybe you want to exclude:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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:&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;But since&#10;&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;&#10;you can use &lt;code&gt;exclude_also&lt;/code&gt; instead and skip that pragma:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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:&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Which is:&lt;/p&gt;&#10;&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]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; # Regexes for lines to exclude from consideration&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-exclude_lines =&#10;&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:&#10;&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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;-&#10;&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 =&#10;&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:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; if __name__ == .__main__.:&#10;&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;&#10;for the tip.&lt;/p&gt;&#10;&lt;p&gt;To &lt;a href="https://nedbatchelder.com/"&gt;Ned Batchelder&lt;/a&gt; for maintaining&#10;&lt;a href="https://coverage.readthedocs.io"&gt;Coverage.py&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;To the Library of Congress and Flickr Commons for the photo of a&#10;&lt;a href="https://www.flickr.com/photos/library_of_congress/52303625278/"&gt;covered wagon&lt;/a&gt;.&lt;/p&gt;&#10;</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&#10;2023-06-27 (&lt;a href="https://peps.python.org/pep-0537/"&gt;PEP 537&lt;/a&gt;).&lt;/p&gt;&#10;&lt;p&gt;This means it is no longer receiving security updates and you should upgrade to a newer&#10;version (at least 3.8, but preferably 3.11):&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_cdb22eb89583d3f9.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_d2c4c505ac08b52e.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1530"&#10; height="950"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Chart showing when different Python versions reached end-of-life"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_270602c327f8d52d.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/nzjga7b8p4eo1t4ja9qd_hu_b3d448dc9018fdc5.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&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:&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_6aca2eb3b6793d60.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_6dd46e23f5d80008.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Python download share over time"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_db24a20e68729e05.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/fkoa8vurrwrj71jde2f3_hu_e6135fcf842691a6.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;Source: &lt;a href="https://hugovk.dev/pypi-tools/charts.html"&gt;pypi-tools&lt;/a&gt;&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;But why does such an old Python version have so many downloads?&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt;, which helps us analyse the&#10;&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;&#10;&lt;p&gt;This command fetches all of yesterday&amp;rsquo;s downloads per Python version:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;pypinfo --days 1 --percent --markdown &amp;quot;&amp;quot; pyversion&lt;/code&gt;&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Python version&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;percent&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;download count&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;25.00%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;189,678,872&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;23.35%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;177,150,010&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.9&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;20.25%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;153,663,903&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;15.52%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;117,751,108&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.11&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;6.90%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;52,381,884&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;6.29%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;47,749,879&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;2.32%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;17,602,650&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.5&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.23%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1,778,388&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.4&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.10%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;770,135&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.12&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.03%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;224,223&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.13&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;3,920&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.3&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1,165&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;57&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.2&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;35&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;None&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.00%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;3&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Total&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;758,756,232&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&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;&#10;&lt;p&gt;This command gives us the top 20:&lt;/p&gt;&#10;&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;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;system name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;distro name&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;Python version&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;percent&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: right"&gt;download count&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ubuntu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;18.87%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;128,991,062&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Amazon Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;14.44%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;98,738,952&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ubuntu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.9&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;12.14%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;83,019,828&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ubuntu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;9.66%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;66,019,309&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ubuntu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;5.89%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;40,257,060&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Debian GNU/Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;5.70%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;38,958,367&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Debian GNU/Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.9&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;5.63%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;38,482,436&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Debian GNU/Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;4.25%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;29,035,532&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Debian GNU/Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;4.15%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;28,348,346&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Debian GNU/Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;3.14%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;21,441,883&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ubuntu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.11&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;3.01%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;20,570,619&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Debian GNU/Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.11&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;2.72%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;18,588,584&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Amazon Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.9&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;2.43%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;16,593,595&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;CentOS Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1.70%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;11,605,142&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Amazon Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.8&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1.47%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;10,035,087&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Amazon Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1.46%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;9,969,514&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ubuntu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;2.7&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;1.02%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;6,960,697&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Amazon Linux AMI&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.79%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;5,390,823&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Linux&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Ubuntu&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.6&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.79%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;5,388,370&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Windows&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;None&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;3.10&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;0.76%&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;5,227,519&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;Total&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: right"&gt;683,622,725&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;We can see Ubuntu with 3.8 is responsible for the largest share of 17%. (That&amp;rsquo;s fine,&#10;3.8 is supported until 2024-10-14.)&lt;/p&gt;&#10;&lt;p&gt;The next is Amazon Linux with 3.7, responsible for a whopping 15% of all downloads!&lt;/p&gt;&#10;&lt;p&gt;The others responsible for 3.7 have a much lower share: Ubuntu (6%) and Debian (4%).&lt;/p&gt;&#10;&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&#10;package, for example:&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;pypinfo --days 1 --limit 20 --percent --markdown requests system distro pyversion&lt;/code&gt;&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo: Space shuttle Discovery landing at Edwards Air Force Base,&#10;California, 9th December, 1992&#10;(&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;&#10;</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;&#10;&lt;p&gt;The &lt;em&gt;third and final&lt;/em&gt; Python 3.12 release candidate is out! 🎉&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://peps.python.org/pep-0693/#release-schedule"&gt;PEP 693&lt;/a&gt; defines the release&#10;schedule for Python 3.12.0:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The first release candidate came out on 6th August 2023&lt;/li&gt;&#10;&lt;li&gt;The second &lt;del&gt;and final&lt;/del&gt; release candidate came out on 6th September 2023&lt;/li&gt;&#10;&lt;li&gt;The third and final release candidate came out on 19th September 2023&lt;/li&gt;&#10;&lt;li&gt;And the full release is set for &lt;strong&gt;2nd October 2023&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In his&#10;&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;,&#10;Thomas Wouters, release manager for Python 3.12 and 3.13, said:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;We strongly encourage maintainers of third-party Python projects to prepare their&#10;projects for 3.12 compatibilities during this phase, and where necessary publish&#10;Python 3.12 wheels on PyPI to be ready for the final release of 3.12.0. Any binary&#10;wheels built against Python 3.12.0rc3 will work with future versions of Python 3.12.&#10;As always, report any issues to&#10;&lt;a href="https://github.com/python/cpython/issues"&gt;the Python bug tracker&lt;/a&gt;.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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.&#10;There&amp;rsquo;s two big benefits:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;There have been&#10;&lt;a href="https://docs.python.org/3.12/whatsnew/3.12.html#removed"&gt;removals and changes&lt;/a&gt; in&#10;Python 3.12. Testing now helps us make our code compatible and avoid any big&#10;surprises (for us and our users) at the big launch in October.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;We might find bugs in Python itself! Reporting those will help get them fixed and&#10;help everyone.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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&#10;&lt;a href="https://github.com/actions/setup-python#supported-version-syntax"&gt;actions/setup-python&lt;/a&gt;,&#10;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;&#10;&lt;p&gt;For example:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;a bit neater, and when 3.12.0 final is released in October, it will continue testing&#10;with full release versions.)&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/deadsnakes/action"&gt;deadsnakes/action&lt;/a&gt; to test the latest nightly&#10;build:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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&#10;writing it&amp;rsquo;s pointing to 3.12.0a3+ from 2022-12-07, which is better than nothing.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&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&#10;&lt;code&gt;Programming Language :: Python :: 3.12&lt;/code&gt;&#10;&lt;a href="https://pypi.org/classifiers/"&gt;Trove classifier&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Especially if you have C extensions and other projects depend on yours, a release with&#10;wheels will help them test and prepare.&lt;/p&gt;&#10;&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;&#10;&lt;blockquote&gt;&#10;&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&#10;intent is for the final release of 3.12.0, scheduled for Monday, 2023-10-02, to be&#10;identical to this release candidate. &lt;strong&gt;This really is the last chance to find critical&#10;problems in Python 3.12.&lt;/strong&gt;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;Let&amp;rsquo;s start testing and releasing for 3.12 now! 🚀&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo: The carpet of the Salt Palace Convention Center grand ballroom,&#10;host of &lt;a href="https://us.pycon.org/2023/"&gt;PyCon 2023&lt;/a&gt;, with a couple of googly eyes added to&#10;make them Pythony (&lt;a href="https://mastodon.social/@hugovk/110239504743679454"&gt;source&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;&#10;&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&#10;RC2&lt;/small&gt;&lt;br&gt;&lt;small&gt;2023-09-19: Updated for RC3&lt;/small&gt;&lt;/p&gt;&#10;</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&#10;easy to scribble on, and stuff into a pocket.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_2803aef5d4471444.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_c59fc197ea5e1348.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="3000"&#10; height="3753"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Printed PyCons schedule"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_d93934ac97530c05.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/2mjm7whei9m178ljcrhx_hu_35ad344eaa706a14.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s some custom CSS and JavaScript to make it nicely printable.&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Install the&#10;&lt;a href="https://chrome.google.com/webstore/detail/styler/bogdgcfoocbajfkjjolkmcdcnnellpkb?hl=en"&gt;Styler browser extension&lt;/a&gt;&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&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&#10;click the Styler extension&amp;rsquo;s S icon&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;Paste this CSS into the upper box:&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#9;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="4"&gt;&#10;&lt;li&gt;Paste this JavaScript into the lower box:&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="5"&gt;&#10;&lt;li&gt;Print!&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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&#10;individual presentation pages such as&#10;&lt;code&gt;https://us.pycon.org/2023/schedule/presentation/88/&lt;/code&gt;&lt;/p&gt;&#10;&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&#10;site so you can browse the original version.&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_4574c03e546b6440.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_42e43d17039b4e92.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="3000"&#10; height="3725"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Printed PyCons schedule"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_4153de02ea7c6223.jpg 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2023/printable-pycon-2023-schedule/59l815p02j0yk0jy377c_hu_67bbcd5fc715014c.jpg 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;</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&#10;language. But first it&amp;rsquo;s a good idea to get an idea of how much the old bits are used.&#10;Searching the&#10;&lt;a href="https://hugovk.dev/top-pypi-packages/"&gt;5,000 most-popular projects on PyPI&lt;/a&gt; is a&#10;helpful proxy to gauge community use.&lt;/p&gt;&#10;&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&#10;of useful scripts that live in his &lt;a href="https://github.com/vstinner/misc"&gt;misc&lt;/a&gt; repo.&lt;/p&gt;&#10;&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;&#10;&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&#10;&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/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://github.com/vstinner/misc&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m pip install termcolor&#10;&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;&#10;&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&#10;&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/&#10;&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 .&#10;&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&#10;&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&#10;&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)&#10;&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)&#10;&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)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;...&#10;&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)&#10;&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)&#10;&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&#10;&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&#10;&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)&#10;&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)&#10;&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)&#10;&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)&#10;&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)&#10;&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)&#10;&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)&#10;&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&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_3f7e937dc37195a2.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_8e23c93da540b596.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1938"&#10; height="1378"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Tail end of downloading files: successful downloads in green, unsuccessful downloads in red"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_d7f9120696dbade3.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/v41t4zfbxoybdfxuhg6m_hu_532197e5b51519af.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;⏳ This will take a bit of time. Some projects don&amp;rsquo;t have sdists, nothing to worry&#10;about, we will still end up with a good number. At the time of writing (2022-12-09), it&#10;took me just under 27 minutes to download 4,748 files, taking up 5.37 GB of space.&lt;/p&gt;&#10;&lt;p&gt;If you want to download fewer, specify how many:&lt;/p&gt;&#10;&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;&#10;&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&#10;them!&lt;/p&gt;&#10;&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&#10;&lt;a href="https://peps.python.org/pep-0392/"&gt;Python 3.2&lt;/a&gt; (released February 2011), but only in&#10;docs and without raising a&#10;&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;&#10;&lt;p&gt;How much is it used in the top 5k?&lt;/p&gt;&#10;&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/&#10;&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;&#10;&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;,&#10;&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):&#10;&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;,&#10;&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):&#10;&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): ...&#10;&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;,&#10;&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):&#10;&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;,&#10;&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):&#10;&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):&#10;&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):&#10;&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):&#10;&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;,&#10;&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):&#10;&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;,&#10;&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;,&#10;&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):&#10;&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;,&#10;&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):&#10;&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;&#10;&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,&#10;&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;,&#10;&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):&#10;&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):&#10;&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):&#10;&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):&#10;&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()&#10;&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()&#10;&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): ...&#10;&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): ...&#10;&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): ...&#10;&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):&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;&#10;&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&#10;&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&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_1e0051929dbfa1d2.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_808d6685147e5536.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1340"&#10; height="774"&#10; class="mx-auto my-0 rounded-md"&#10; alt="Same output as above but with the source filename in purple and LegacyInterpolation in orange"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_5b7e785b73498331.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2022/how-to-search-5000-python-projects/ele6hbe4kix8s0pb6ngu_hu_a699325602be0384.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;Answer: very little, mostly backports and type stubs. This told us it&amp;rsquo;s a good candidate&#10;for removal, so a proper &lt;code&gt;DeprecationWarning&lt;/code&gt; was&#10;&lt;a href="https://docs.python.org/3/whatsnew/3.11.html#standard-library"&gt;added in Python 3.11&lt;/a&gt;&#10;(released October 2022) and it will be&#10;&lt;a href="https://peps.python.org/pep-0387/"&gt;removed in Python 3.13&lt;/a&gt; (October 2024).&lt;/p&gt;&#10;&lt;p&gt;The tool searches using a regex, so you can look for more complicated things like&#10;&lt;code&gt;&amp;quot;\b(currentThread|activeCount|notifyAll|isSet|isDaemon|setDaemon)\b&amp;quot;&lt;/code&gt;, and it can also&#10;log to file. See &lt;code&gt;--help&lt;/code&gt; for other options.&lt;/p&gt;&#10;&lt;p&gt;Happy searching! 🔎&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&amp;ldquo;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/24029425@N06/11237637113"&gt;The&#10;card index department&lt;/a&gt;&amp;rdquo; by&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/24029425@N06"&gt;Boston&#10;Public Library&lt;/a&gt; is licensed under&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC&#10;BY 2.0&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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;&#10;&lt;p&gt;Python 3.11 is in beta! 🎉&lt;/p&gt;&#10;&lt;p&gt;&lt;a href="https://peps.python.org/pep-0664/#release-schedule"&gt;PEP 664&lt;/a&gt; defines the release&#10;schedule for Python 3.11.0:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The second beta came out on 31st May 2022&lt;/li&gt;&#10;&lt;li&gt;The first release candidate is set for 1st August 2022&lt;/li&gt;&#10;&lt;li&gt;And the full release is set for 3rd October 2022&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;In his&#10;&lt;a href="https://discuss.python.org/t/python-3-11-0b2-is-now-available/16111?u=hugovk"&gt;announcement&lt;/a&gt;,&#10;Pablo Galindo Salgado, release manager for Python 3.10 and 3.11, said:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;We &lt;strong&gt;strongly encourage&lt;/strong&gt; maintainers of third-party Python projects to &lt;strong&gt;test with&#10;3.11&lt;/strong&gt; during the beta phase and report issues found to the&#10;&lt;a href="https://github.com/python/cpython/issues"&gt;Python bug tracker&lt;/a&gt; as soon as possible.&#10;While the release is planned to be feature complete entering the beta phase, it is&#10;possible that features may be modified or, in rare cases, deleted up until the start&#10;of the release candidate phase (Monday, 2021-08-02). Our goal is have no ABI changes&#10;after beta 4 and as few code changes as possible after 3.11.0rc1, the first release&#10;candidate. To achieve that, it will be &lt;strong&gt;extremely important&lt;/strong&gt; to get as much exposure&#10;for 3.11 as possible during the beta phase.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&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&#10;need to declare support and release for 3.11 yet, but there&amp;rsquo;s two big benefits to&#10;testing with 3.11 on CI:&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;There have been removals and changes in Python 3.11. Testing now will help you make&#10;your code compatible and avoid any big surprises (for you and your users) at the big&#10;launch in October.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&#10;&lt;p&gt;You might find bugs in Python itself! Reporting those will help get them fixed and&#10;help everyone.&lt;/p&gt;&#10;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&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&#10;&lt;a href="https://github.com/actions/setup-python#available-versions-of-python"&gt;actions/setup-python&lt;/a&gt;,&#10;add &lt;code&gt;3.11-dev&lt;/code&gt; to your workflow matrix.&lt;/p&gt;&#10;&lt;p&gt;For example:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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&#10;&lt;a href="https://github.com/deadsnakes/action"&gt;deadsnakes/action&lt;/a&gt; to test the latest nightly&#10;build:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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&#10;writing it&amp;rsquo;s pointing to 3.11.0a3 from 2021-12-08, which is better than nothing.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;a href="https://pypi.org/classifiers/"&gt;Trove classifier&lt;/a&gt;?&lt;/p&gt;&#10;&lt;p&gt;First of all, make sure your tests pass on 3.11 beta. One option is waiting until 3.11.0&#10;final is released.&lt;/p&gt;&#10;&lt;p&gt;Or, as mentioned above:&lt;/p&gt;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Our goal is have no ABI changes after beta 4 and as few code changes as possible after&#10;3.11.0rc1, the first release candidate.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p&gt;If you have a pure Python project, you could release now.&lt;/p&gt;&#10;&lt;p&gt;If you have C extensions, you might want to wait until the release candidate phase,&#10;although if other projects depend on yours, a preview release would help them test and&#10;prepare.&lt;/p&gt;&#10;&lt;p&gt;In any case, start testing 3.11 now!&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&lt;a href="https://en.wikipedia.org/wiki/Uppland_Runic_Inscription_53"&gt;Uppland Runic Inscription 53&lt;/a&gt;,&#10;a 1,000 year old runestone in the old town of Stockholm&#10;(&lt;a href="https://www.flickr.com/photos/hugovk/3490246425/"&gt;source&lt;/a&gt;)&lt;/small&gt;&lt;/p&gt;&#10;</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.dev/top-pypi-packages/"&gt;Top PyPI Packages&lt;/a&gt; is a website that creates a&#10;monthly dump of the 5,000 most-downloaded packages from the&#10;&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;. It provides a human-readable list and&#10;a machine-readable JSON file for programmatic use.&lt;/p&gt;&#10;&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&#10;academic papers, covering research on topics such as software supply-chain attacks,&#10;genetic algorithms, neural type hints and technical debt.&lt;/p&gt;&#10;&lt;p&gt;Websites also use the data for analysing things like community adoption of Python 3 and&#10;of wheels package files, and for automated dependency updates.&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;Last but not least, CPython core developers often use the data for community analysis,&#10;for example to check how widespread certain language features are when considering&#10;deprecations and removals, or API changes of Python itself.&lt;/p&gt;&#10;&lt;p&gt;Some examples:&lt;/p&gt;&#10;&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;&#10;&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&#10;Python Software Foundation, and builds the JSON file from that. The data is&#10;automatically committed from the droplet back to the GitHub repository, and GitHub&#10;Actions is used to automatically tag and create a release, which then creates a Digital&#10;Object Identifier (DOI) at Zenodo to help make it citable for researchers.&lt;/p&gt;&#10;&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&#10;Linux server. I&amp;rsquo;m using the cheapest $5/month running Ubuntu 20.04 (LTS) plus $1/month&#10;for automated backups. (In July 2022, I&amp;rsquo;ll switch to the new&#10;&lt;a href="https://www.digitalocean.com/try/new-pricing"&gt;$4/month Droplet&lt;/a&gt;.)&lt;/p&gt;&#10;&lt;p&gt;On the first of the month, it runs a cron job:&lt;/p&gt;&#10;&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&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This&#10;&lt;a href="https://github.com/hugovk/top-pypi-packages/blob/c8101970cbfde3aee2eb133ae0d2aa8eb8846a58/top-pypi-packages.sh"&gt;script&lt;/a&gt;&#10;runs&#10;&lt;a href="https://github.com/hugovk/top-pypi-packages/blob/c8101970cbfde3aee2eb133ae0d2aa8eb8846a58/build.sh"&gt;some&lt;/a&gt;&#10;&lt;a href="https://github.com/hugovk/top-pypi-packages/blob/c8101970cbfde3aee2eb133ae0d2aa8eb8846a58/generate.sh"&gt;others&lt;/a&gt;&#10;to call &lt;a href="https://github.com/ofek/pypinfo/"&gt;pypinfo&lt;/a&gt;:&lt;/p&gt;&#10;&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&#10;&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;&#10;which are accessible as a public dataset. Google provides a free amount of queries per&#10;month, and I&amp;rsquo;ve been adjusting the amount of data fetched to stay within the free quota&#10;(e.g. changing from the top 5k packages to 4k; it used to get data for top packages over&#10;the past 30 days and 365 days, but now only for 30 days; and bumping back up to 5k&#10;packages).&lt;/p&gt;&#10;&lt;p&gt;pypinfo is a handy command-line interface (CLI) to access this BigQuery data and dump it&#10;to a JSON file.&lt;/p&gt;&#10;&lt;p&gt;Another handy CLI tool called &lt;a href="https://stedolan.github.io/jq/"&gt;jq&lt;/a&gt; minifies the JSON&#10;data:&lt;/p&gt;&#10;&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&#10;&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;&#10;(e.g. &lt;a href="https://github.com/hugovk/top-pypi-packages/releases/tag/2022.05"&gt;2022-05&lt;/a&gt;) and&#10;pushed.&#10;&lt;a href="https://github.com/hugovk/top-pypi-packages/blob/c8101970cbfde3aee2eb133ae0d2aa8eb8846a58/.github/workflows/release.yml"&gt;GitHub Actions&lt;/a&gt;&#10;creates &lt;a href="https://github.com/hugovk/top-pypi-packages/releases"&gt;a release&lt;/a&gt; from the tag.&lt;/p&gt;&#10;&lt;p&gt;This then creates a&#10;&lt;a href="https://zenodo.org/badge/latestdoi/116806538"&gt;Digital Object Identifier (DOI) at Zenodo&lt;/a&gt;&#10;to help make it citable for researchers.&lt;/p&gt;&#10;&lt;p&gt;The &lt;a href="https://hugovk.dev/top-pypi-packages/"&gt;website&lt;/a&gt;, on GitHub Pages, reads in the&#10;generated JSON file and shows the top 100 (or 1,000 or 5,000) packages in human-readable&#10;form. It&amp;rsquo;s based on &lt;a href="https://pythonwheels.com"&gt;Python Wheels&lt;/a&gt;, which in nice circular&#10;fashion, uses the JSON data from this project.&lt;/p&gt;&#10;&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&#10;&lt;a href="https://cloud.google.com/bigquery/"&gt;Google BigQuery&lt;/a&gt; for the data;&#10;&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&#10;the tools; &lt;a href="https://pythonwheels.com/"&gt;Python Wheels&lt;/a&gt; for making their code open source;&#10;and &lt;a href="https://m.do.co/c/431978e0c3e9"&gt;DigitalOcean&lt;/a&gt; for sponsoring this project&amp;rsquo;s&#10;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;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&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;&#10;by&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/49889874@N05"&gt;marc&#10;falardeau&lt;/a&gt; is licensed under&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by/2.0/?ref=openverse"&gt;CC&#10;BY 2.0&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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;&#10;&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&#10;when you put this in YAML:&lt;/p&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;:scream:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&lt;/span&gt;&lt;/span&gt;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🇳🇴 ✅&lt;/p&gt;&#10;&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&#10;&lt;a href="https://peps.python.org/pep-0619/"&gt;Python 3.10 comes out&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p&gt;Why?&lt;/p&gt;&#10;&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&#10;a float. This:&lt;/p&gt;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;&#10;&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;&#10;&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;&#10;&lt;p&gt;:scream:&lt;/p&gt;&#10;&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&#10;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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;...&lt;/span&gt;&#10;&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🐍 ✅&lt;/p&gt;&#10;&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&#10;find Python 3.10 and other bugs caused by assumptions about the length of version&#10;numbers when using &lt;code&gt;sys.version&lt;/code&gt; and &lt;code&gt;sys.version_info&lt;/code&gt;.&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;p&gt;&lt;small&gt;Header photo:&#10;&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;&#10;by&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://www.flickr.com/photos/49968232@N00"&gt;Leo&#10;Reynolds&lt;/a&gt; is licensed under&#10;&lt;a target="_blank" rel="noopener noreferrer" href="https://creativecommons.org/licenses/by-nc-sa/2.0/?ref=openverse"&gt;CC&#10;BY-NC-SA 2.0&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;&#10;</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;&#10;on &lt;a href="https://peps.python.org/pep-0373/"&gt;1st January 2020&lt;/a&gt;, here’s some statistics showing&#10;how much different Python versions have been used over four years.&lt;/p&gt;&#10;&lt;p&gt;Here’s the pip installs for all packages from the&#10;&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and December&#10;2019:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_d7077bd84dd1430d.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_615e62dfa4e0a45c.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="all"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_a6dc1e9f1d61e5e.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2020/python-version-share-over-time-6/pypi_hu_ecb1f03ebdc07096.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a&#10;wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; and&#10;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt; to fetch all monthly downloads from the&#10;PyPI database on Google BigQuery and save them as JSON files. Data was downloaded over&#10;several days as getting all months uses up a lot of free BigQuery quota. Then&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots a&#10;chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is in&#10;the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,&#10;1&lt;/a&gt; (January&#10;2016 — June 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-2/"&gt;Python version share over time,&#10;2&lt;/a&gt; (January&#10;2016 — October 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-3/"&gt;Python version share over time, 3&lt;/a&gt;&#10;(January 2016 — December 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-4/"&gt;Python version share over time, 4&lt;/a&gt;&#10;(January 2016 — March 2019)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-5/"&gt;Python version share over time, 5&lt;/a&gt;&#10;(January 2016 — October 2019)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,&#10;without needing to sign up for BigQuery&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data&#10;from PyPI Stats&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</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&#10;&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;&#10;on &lt;a href="https://peps.python.org/pep-0569/"&gt;14th October 2019&lt;/a&gt;, and with&#10;&lt;a href="https://python2woop.pw/"&gt;less than two months left for Python 2&lt;/a&gt;, here’s some&#10;statistics showing how much different Python versions have been used over nearly four&#10;years.&lt;/p&gt;&#10;&lt;p&gt;Here’s the pip installs for all packages from the&#10;&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and October 2019:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_a3ded4c1e5d347be.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_8a6822638a378a2a.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pypi"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_11a21e899c6dab30.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-5/pypi_hu_a7dacaa6e985ac37.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &#10;&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;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a&#10;wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; and&#10;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt; to fetch all monthly downloads from the&#10;PyPI database on Google BigQuery and save them as JSON files. Data was downloaded over&#10;several days as getting all months uses up a lot of free BigQuery quota. Then&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots a&#10;chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is in&#10;the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,&#10;1&lt;/a&gt; (January&#10;2016 — June 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-2/"&gt;Python version share over time,&#10;2&lt;/a&gt; (January&#10;2016 — October 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-3/"&gt;Python version share over time, 3&lt;/a&gt;&#10;(January 2016 — December 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-4/"&gt;Python version share over time, 4&lt;/a&gt;&#10;(January 2016 — March 2019)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,&#10;without needing to sign up for BigQuery&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data&#10;from PyPI Stats&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</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&#10;&lt;a href="https://www.python.org/downloads/release/python-373/"&gt;Python 3.7.3&lt;/a&gt; on&#10;&lt;a href="https://peps.python.org/pep-0537/"&gt;25th March 2019&lt;/a&gt;, and with under&#10;&lt;a href="https://python2woop.pw/"&gt;nine months left for Python 2&lt;/a&gt;, here’s some statistics showing&#10;how much different Python versions have been used over the past three years.&lt;/p&gt;&#10;&lt;p&gt;Here’s the pip installs for all packages from the&#10;&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and March 2019:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_93b42a2cf8454afb.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_bff5e2cae1fa6088.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pypi"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_915c5274993cbd19.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/4wqa702ngppsv6dxh3zq_hu_4dc1a6155a879aff.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_54bcb3a15c983066.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_c00555a486290249.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pip"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_c005ffab43ee357c.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2kncm0d1gnytc8dox1vr_hu_5645317f5a162b70.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_cd1da94454a76852.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_8fc4e74c7c65c17f.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="six"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_58f5123a376ee557.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/2s8ienw3purly7d563g3_hu_36938c059f3de55d.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_20bca4707adfaa29.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_21a9de1ce1830c9b.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="NumPy"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_bab126983d155b4.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/slcl0es81ltkdlduthea_hu_7bffe0984b470a4b.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_1fed580acd727d27.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_743d19ab54d5a61e.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pytest"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_d2ef5f5bfc31fe17.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/x9u45cebj930u52lhob2_hu_4cf9d32ed16b2f8c.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_45bb75e5185cdadc.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_4922bc9542c7b69f.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="coverage"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_7888bf93fa72fdb4.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/lacortkwvi92z4zw0m6b_hu_b28b1fd83c6cf354.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_701b9640f27a9e10.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_f8676fb04952ed49.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pillow"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_8364ab1bb6bc925c.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/msqm643wek2w0qmgdxlu_hu_bab3111c3cfeb4f0.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_ae3b193c3e32f183.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_18b04cca1fbcaede.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="django"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_3fe2e707c159b46a.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/omjea829j1h83fxp65ge_hu_fc48b09a0a300846.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_a2a2d74bb68e617.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_baf9e8a27f3ccff8.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="matplotlib"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_10cb82b2ea564070.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/7kqrb64d2lmrnm2jw2lu_hu_e8763cc54de70d1d.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_7f7efeee8eacc5b7.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_b376839d06ac005e.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="flake8"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_c9477dbded216dfe.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/102lu27lpme6gtdb3q9i_hu_d2c32cf39471cfc9.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_2515fea6b95310db.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_5605662e8972b3d2.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pylint"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_578383a04751bc50.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/yxc1911cdltvfrjs0eop_hu_c2b0f54a3152dbd4.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_e266d4541a8daa81.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_29987d98ea89c351.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pylast"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_1e1ebb8d8e089656.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-4/ulztdisgv3zmuzif8nbf_hu_7c4b7ac32166644.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a&#10;wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; to fetch all monthly downloads&#10;from the PyPI database on Google BigQuery and save them as JSON files. Data was&#10;downloaded over several days as getting all months uses up a lot of free BigQuery quota.&#10;Then &lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots&#10;a chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is&#10;in the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,&#10;1&lt;/a&gt; (January&#10;2016 — June 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-2/"&gt;Python version share over time,&#10;2&lt;/a&gt; (January&#10;2016 — October 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="../../2019/python-version-share-over-time-3/"&gt;Python version share over time, 3&lt;/a&gt;&#10;(January 2016 — December 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,&#10;without needing to sign up for BigQuery&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data&#10;from PyPI Stats&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</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&#10;&lt;a href="https://www.python.org/downloads/release/python-372/"&gt;Python 3.7.2&lt;/a&gt; on&#10;&lt;a href="https://peps.python.org/pep-0537/"&gt;Christmas Eve 2018&lt;/a&gt;, and with&#10;u&lt;a href="https://hugovk.dev/python2-progress-bar/"&gt;nder a year left for Python 2&lt;/a&gt;, here&amp;rsquo;s some&#10;statistics showing how much different Python versions have been used over the past three&#10;years.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the pip installs for all packages from the&#10;&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and December&#10;2018:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_d1f8a7090ff16763.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_15082e628ac86f8b.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pypi"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_9f0cd80934ca8368.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pypi_hu_132f9c73735d06cf.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/numpy/numpy"&gt;NumPy&lt;/a&gt; scientific computing library:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_ffa4d215fdbf1213.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_a0946d70a8ed20d1.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="NumPy"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_67deb915c4a7ec44.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/numpy_hu_95f0fbde6f5c051c.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/pytest-dev/pytest"&gt;pytest&lt;/a&gt; testing framework:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_20cea6928172c311.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_6e3d279c1398a129.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pytest"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_15f1fdd043bad908.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pytest_hu_3382a792c337845c.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt; imaging library:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_f2bf3af5060e46e3.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_1a9e6fd6ea8ef938.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pillow"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_7ca825b96cbeffd.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pillow_hu_e5fde998471ca438.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/django/django"&gt;Django&lt;/a&gt; web framework:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_66231d11724c2b1f.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_5be1cab6ce9a22d0.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="django"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_60178940828ebc4b.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/django_hu_3e9095f56f719ddd.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt; 2D plotting library:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_c3b25dfdff89bdc4.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_6a298d4ab3c99953.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="matplotlib"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_3042fad692d468a6.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/matplotlib_hu_97bcbf6365b31263.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/PyCQA/pylint"&gt;Pylint&lt;/a&gt; linter:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_4118aaeab1ce4ac8.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_ddbc510793fc30fb.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pylint"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_c1aa841f95201908.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylint_hu_f12f9e61a91bc675.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_20bd13b334f6f66e.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_98038669afce3132.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pylast"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_b2e391430b907729.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2019/python-version-share-over-time-3/pylast_hu_7042c596e1cb51f1.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&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&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt; a&#10;wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; to fetch all monthly downloads&#10;from the PyPI database on Google BigQuery and save them as JSON files. Data was&#10;downloaded over several days as getting all months uses up a lot of free BigQuery quota.&#10;Then &lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots&#10;a chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is&#10;in the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,&#10;1&lt;/a&gt; (January&#10;2016 — June 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-2/"&gt;Python version share over time,&#10;2&lt;/a&gt; (January&#10;2016 — October 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,&#10;without needing to sign up for BigQuery&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data&#10;from PyPI Stats&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</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&#10;&lt;a href="https://www.python.org/downloads/release/python-371/"&gt;Python 3.7.1&lt;/a&gt; on&#10;&lt;a href="https://peps.python.org/pep-0537/"&gt;20th October 2018&lt;/a&gt;, here’s some statistics showing&#10;how much different Python versions have been used over the past two and five-sixths&#10;years.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the pip installs for all packages from the&#10;&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and October 2018:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_5f7fdf46a8178145.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_6795ab95c9f31d18.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pypi"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_90c3599a10fb09db.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pypi_hu_a83fc75ddb557e1f.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/numpy/numpy"&gt;NumPy&lt;/a&gt; scientific computing library:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_67f70e6fbce25701.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_2b9376db4171ab9.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="NumPy"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_60262fc17f9dee72.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/numpy_hu_87b1196698f36b9f.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;Numpy's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/python-pillow/Pillow"&gt;Pillow&lt;/a&gt; imaging library:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_d33515c925f1989b.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_9ea77602db39bd68.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pillow"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_7eb20f520ce2e96.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pillow_hu_fc69ec737f7aa8c3.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;Pillow's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;&#10;&lt;p&gt;For the &lt;a href="https://github.com/django/django"&gt;Django&lt;/a&gt; web framework:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_9c6ae8859f05f0bf.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_3995ce0f7610a6f6.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="django"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_b658b437a8aabcc0.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/django_hu_944f29479ec21bf9.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;pylast's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_2d180a564169297e.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_1625e575b418b340.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1536"&#10; height="1152"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pylast"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_b5c90c65440f3130.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-2/pylast_hu_dc669d7619106aed.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;Pillow's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;&#10;&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&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a&#10;wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; to fetch all monthly downloads&#10;from the PyPI database on Google BigQuery and save them as JSON files. Data was&#10;downloaded over several days as getting all months uses up a lot of free BigQuery quota.&#10;Then &lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; plots&#10;a chart using &lt;a href="https://github.com/matplotlib/matplotlib"&gt;matplotlib&lt;/a&gt;. Raw JSON data is&#10;in the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;&#10;&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;&#10;&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;&#10;&lt;li&gt;&lt;a href="https://hugovk.dev/blog/2018/python-version-share-over-time-1/"&gt;Python version share over time,&#10;1&lt;/a&gt; (January&#10;2016 — June 2018)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://pypistats.org/"&gt;PyPI Stats&lt;/a&gt;: See package download data for the past 180 days,&#10;without needing to sign up for BigQuery&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/hugovk/pypistats"&gt;pypistats&lt;/a&gt;: A command-line tool to access data&#10;from PyPI Stats&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</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;&#10;on &lt;a href="https://peps.python.org/pep-0537/"&gt;27th June 2018&lt;/a&gt;, here’s some statistics showing&#10;how much different Python versions have been used over the past two and a half years.&lt;/p&gt;&#10;&lt;p&gt;Here&amp;rsquo;s the pip installs for all packages from the&#10;&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;, between January 2016 and June 2018:&lt;/p&gt;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_4cfc9be767ea3dda.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_ee012f2a76af287a.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1756"&#10; height="1093"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pypi"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_57e72c760b5719a0.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pypi_hu_b784aa405a5bb4c6.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;&#10;&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;&#10;&lt;p&gt;&#10;&#10;&#10;&#10;&#10;&#10;&lt;figure&gt;&#10; &#10; &#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10;&#10; &#10; &lt;picture&#10; class="mx-auto my-0 rounded-md"&#10; &#10; &gt;&#10; &#10; &#10; &#10; &#10; &lt;source&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_60f56cd848c31475.webp 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_971b1e4ed0519d1e.webp 1320w&#10; "&#10; &#10; sizes="100vw"&#10; type="image/webp"&#10; /&gt;&#10; &#10; &lt;img&#10; width="1495"&#10; height="1093"&#10; class="mx-auto my-0 rounded-md"&#10; alt="pillow"&#10; loading="lazy" decoding="async"&#10; &#10; 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&#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_a4ae32d8f9f04b7e.png 1024w&#10; &#10; &#10; ,https://hugovk.dev/blog/2018/python-version-share-over-time-1/pillow_hu_ee3dcbcc6230ca0f.png 1320w&#10; "&#10; sizes="100vw"&#10; &#10; /&gt;&#10; &lt;/picture&gt;&#10; &#10;&#10;&#10;&lt;/figure&gt;&#10;&lt;/p&gt;&#10;&lt;center&gt;&lt;small&gt;Pillow's pip installs from PyPI over time, by Python version&lt;/small&gt;&lt;/center&gt;&#10;&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&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/pypi-trends.py"&gt;pypi-trends.py&lt;/a&gt;, a&#10;wrapper around &lt;a href="https://github.com/ofek/pypinfo"&gt;pypinfo&lt;/a&gt; to fetch all monthly downloads&#10;from the PyPI database on Google BigQuery and save them as JSON files. Data was&#10;downloaded over three or four days as getting all months uses up a lot of free BigQuery&#10;quota. Then&#10;&lt;a href="https://github.com/hugovk/pypi-tools/blob/master/jsons2csv.py"&gt;jsons2csv.py&lt;/a&gt; converts&#10;them into a single CSV file for chart-wrangling in Excel. Raw CSV and JSON data is in&#10;the &lt;a href="https://github.com/hugovk/pypi-tools/tree/master/data"&gt;repo&lt;/a&gt;.&lt;/p&gt;&#10;&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&#10;&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;&#10;&lt;blockquote&gt;&#10;&lt;p&gt;Data ingestion into the BigQuery data set was spotty prior to June 2016*, but you can&#10;see a significant uptick in Python 3 based downloads over 2016.&#10;&lt;a href="https://frinkiac.com/caption/S08E11/289555"&gt;If these trends continue…&lt;/a&gt;&lt;/p&gt;&#10;&lt;p&gt;* But it shouldn’t be biased, so these percentages are likely to be accurate.&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;</description></item></channel></rss>