<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Hugo van Kemenade</title>
    <description>The latest articles on DEV Community by Hugo van Kemenade (@hugovk).</description>
    <link>https://dev.to/hugovk</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F151968%2F5a7b51af-96f8-41e6-9896-679e87846de1.png</url>
      <title>DEV Community: Hugo van Kemenade</title>
      <link>https://dev.to/hugovk</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hugovk"/>
    <language>en</language>
    <item>
      <title>Moved to hugovk.dev</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Thu, 02 Jan 2025 19:51:13 +0000</pubDate>
      <link>https://dev.to/hugovk/moved-to-hugovkdev-l4k</link>
      <guid>https://dev.to/hugovk/moved-to-hugovkdev-l4k</guid>
      <description>&lt;h1&gt;
  
  
  🚀 See you over at &lt;a href="https://hugovk.dev/" rel="noopener noreferrer"&gt;hugovk.dev&lt;/a&gt;!
&lt;/h1&gt;

</description>
    </item>
    <item>
      <title>A surprising thing about PyPI's BigQuery data</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Sun, 24 Nov 2024 20:45:31 +0000</pubDate>
      <link>https://dev.to/hugovk/a-surprising-thing-about-pypis-bigquery-data-2g9o</link>
      <guid>https://dev.to/hugovk/a-surprising-thing-about-pypis-bigquery-data-2g9o</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2024/a-surprising-thing-about-pypis-bigquery-data/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;You can get download numbers for PyPI packages (or projects) from a &lt;a href="https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/" rel="noopener noreferrer"&gt;Google BigQuery dataset&lt;/a&gt;. You need a Google account and credentials, and Google gives 1 TiB of free quota per month.&lt;/p&gt;

&lt;p&gt;Each month, I have automation to fetch the download numbers for the 8,000 most popular packages over the past 30 days, and make it available as more accessible JSON and CSV files at &lt;a href="https://hugovk.github.io/top-pypi-packages/" rel="noopener noreferrer"&gt;Top PyPI Packages&lt;/a&gt;. This data is &lt;a href="https://hugovk.github.io/top-pypi-packages/#users" rel="noopener noreferrer"&gt;widely used&lt;/a&gt; for &lt;a href="https://github.com/hugovk/top-pypi-packages/issues/23" rel="noopener noreferrer"&gt;research in academia and industry&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;However, as more packages and releases are uploaded to PyPI, and there are more and more downloads logged, the amount of billed data increases too.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsego7uxbpslifzd791ee.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsego7uxbpslifzd791ee.png" alt="BigQuery TB billed" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart shows the amount of data billed per month.&lt;/p&gt;

&lt;p&gt;At first, I was only collecting downloads data for 4,000 packages, and it was fetched for two queries: downloads over 365 days and over 30 days. But as time passed, it started using up too much quota to download data for 365 days.&lt;/p&gt;

&lt;p&gt;So I ditched the 365-day data, and increased the 30-day data from 4,000 to 5,000 packages. Later, I checked how much quota was being used and increased from 5,000 packages to 8,000 packages.&lt;/p&gt;

&lt;p&gt;But then I exceeded the BigQuery monthly quota of 1 TiB fetching data for July 2024.&lt;/p&gt;

&lt;p&gt;To fetch the missing data and investigate what's going in, I started Google Cloud's 90-day, $300 (€277.46) free-trial 💸&lt;/p&gt;

&lt;p&gt;Here's what I found!&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding: it costs more to get data for downloads from only pip than from all installers
&lt;/h2&gt;

&lt;p&gt;I use the &lt;a href="https://github.com/ofek/pypinfo" rel="noopener noreferrer"&gt;pypinfo&lt;/a&gt; client to help query BigQuery. By default, it only fetches downloads for pip.&lt;/p&gt;

&lt;h3&gt;
  
  
  Only pip
&lt;/h3&gt;

&lt;p&gt;This command gets one day's download data for the top 10 packages, &lt;em&gt;for pip only&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pypinfo &lt;span class="nt"&gt;--limit&lt;/span&gt; 10 &lt;span class="nt"&gt;--days&lt;/span&gt; 1 &lt;span class="s2"&gt;""&lt;/span&gt; project
&lt;span class="go"&gt;Served from cache: False
Data processed: 58.21 GiB
Data billed: 58.21 GiB
&lt;/span&gt;&lt;span class="gp"&gt;Estimated cost: $&lt;/span&gt;0.29
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Results:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;project&lt;/th&gt;
&lt;th&gt;download count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;boto3&lt;/td&gt;
&lt;td&gt;37,251,744&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiobotocore&lt;/td&gt;
&lt;td&gt;16,252,824&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;urllib3&lt;/td&gt;
&lt;td&gt;16,243,278&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;botocore&lt;/td&gt;
&lt;td&gt;15,687,125&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;requests&lt;/td&gt;
&lt;td&gt;13,271,314&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;s3fs&lt;/td&gt;
&lt;td&gt;12,865,055&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;s3transfer&lt;/td&gt;
&lt;td&gt;12,014,278&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;fsspec&lt;/td&gt;
&lt;td&gt;11,982,305&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;charset-normalizer&lt;/td&gt;
&lt;td&gt;11,684,740&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;certifi&lt;/td&gt;
&lt;td&gt;11,639,584&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;158,892,247&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  All installers
&lt;/h3&gt;

&lt;p&gt;Adding the &lt;code&gt;--all&lt;/code&gt; flag gets one day's download data for the top 10 packages, &lt;em&gt;for all installers&lt;/em&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pypinfo &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--limit&lt;/span&gt; 10 &lt;span class="nt"&gt;--days&lt;/span&gt; 1 &lt;span class="s2"&gt;""&lt;/span&gt; project
&lt;span class="go"&gt;Served from cache: False
Data processed: 46.63 GiB
Data billed: 46.63 GiB
&lt;/span&gt;&lt;span class="gp"&gt;Estimated cost: $&lt;/span&gt;0.23
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;project&lt;/th&gt;
&lt;th&gt;download count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;boto3&lt;/td&gt;
&lt;td&gt;39,495,624&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;botocore&lt;/td&gt;
&lt;td&gt;17,281,187&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;urllib3&lt;/td&gt;
&lt;td&gt;17,225,121&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;aiobotocore&lt;/td&gt;
&lt;td&gt;16,430,826&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;requests&lt;/td&gt;
&lt;td&gt;14,287,965&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;s3fs&lt;/td&gt;
&lt;td&gt;12,958,516&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;charset-normalizer&lt;/td&gt;
&lt;td&gt;12,781,405&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;certifi&lt;/td&gt;
&lt;td&gt;12,647,098&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setuptools&lt;/td&gt;
&lt;td&gt;12,608,120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;idna&lt;/td&gt;
&lt;td&gt;12,510,335&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;168,226,197&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So we can see the default pip-only costs an extra 25% data processed and data billed, and costs an extra 25% in dollars.&lt;/p&gt;

&lt;p&gt;Unsurprisingly, the actual download counts are higher for all installers. The ranking has changed a bit, but I expect we're still getting more-or-less the same packages in the top thousands of results.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queries
&lt;/h3&gt;

&lt;p&gt;It sends a query like this to BigQuery for only pip:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`bigquery-public-data.pypi.file_downloads`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&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="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&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="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;AND&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="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;"pip"&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;download_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And for all installers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`bigquery-public-data.pypi.file_downloads`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&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="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&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="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;download_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These queries are the same, except the default has an extra &lt;code&gt;AND details.installer.name = "pip"&lt;/code&gt; condition. It seems reasonable it would cost more to do extra filtering work.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installers
&lt;/h3&gt;

&lt;p&gt;Let's look at the installers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;pypinfo &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;--limit&lt;/span&gt; 100 &lt;span class="nt"&gt;--days&lt;/span&gt; 1 &lt;span class="s2"&gt;""&lt;/span&gt; installer
&lt;span class="go"&gt;Served from cache: False
Data processed: 29.49 GiB
Data billed: 29.49 GiB
&lt;/span&gt;&lt;span class="gp"&gt;Estimated cost: $&lt;/span&gt;0.15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;installer name&lt;/th&gt;
&lt;th&gt;download count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pip&lt;/td&gt;
&lt;td&gt;1,121,198,711&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;uv&lt;/td&gt;
&lt;td&gt;117,194,833&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;requests&lt;/td&gt;
&lt;td&gt;29,828,272&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;poetry&lt;/td&gt;
&lt;td&gt;23,009,454&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;8,916,745&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bandersnatch&lt;/td&gt;
&lt;td&gt;6,171,555&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;setuptools&lt;/td&gt;
&lt;td&gt;1,362,797&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bazel&lt;/td&gt;
&lt;td&gt;1,280,271&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;1,096,328&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nexus&lt;/td&gt;
&lt;td&gt;593,230&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Homebrew&lt;/td&gt;
&lt;td&gt;510,247&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Artifactory&lt;/td&gt;
&lt;td&gt;69,063&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pdm&lt;/td&gt;
&lt;td&gt;62,904&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OS&lt;/td&gt;
&lt;td&gt;13,108&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;devpi&lt;/td&gt;
&lt;td&gt;9,530&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;conda&lt;/td&gt;
&lt;td&gt;2,272&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pex&lt;/td&gt;
&lt;td&gt;194&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,311,319,514&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;pip still by far the most popular, and unsurprising uv is up there too, with about 10% of pip's downloads.&lt;/p&gt;

&lt;p&gt;The others are about 25% or less of uv. A lot of them are mirroring services that we wanted to exclude before.&lt;/p&gt;

&lt;p&gt;I think given uv's importance, and my expectation that it will continue to take a bigger share of the pie, plus especially the extra cost for filtering by just pip, means that we should switch to fetching data for all downloaders. Plus the others don't account for that much of the pie.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding: the number of packages doesn't affect the cost
&lt;/h2&gt;

&lt;p&gt;This was the biggest surprise. Earlier I'd been increasing or decreasing the number to try and remain under quota. But it turns out it makes no difference how many packages you query!&lt;/p&gt;

&lt;p&gt;I fetched data for just one day and all installers for different package limits: 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000. Sample query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&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="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;download_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="nv"&gt;`bigquery-public-data.pypi.file_downloads`&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="nb"&gt;timestamp&lt;/span&gt; &lt;span class="k"&gt;BETWEEN&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="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AND&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="n"&gt;INTERVAL&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;DAY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;project&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt;
  &lt;span class="n"&gt;download_count&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;
&lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03tmt980ypp67qryra26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F03tmt980ypp67qryra26.png" alt="Cost and bytes for 1 day with different package limits are the same" width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Interestingly, the cost is the same for all limits (1000-8000): $0.31.&lt;/p&gt;

&lt;p&gt;Repeating with one day but filtering for pip only:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fal96xqbescc64lavi4y7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fal96xqbescc64lavi4y7.png" alt="Cost and bytes for 1 day still the same for pip" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Cost increased to $0.39 but again the same for all limits.&lt;/p&gt;

&lt;p&gt;Let's repeat with all installers, but for 30 days, and this time query in decreasing limits, in case we were only paying for incremental changes: 8000, 7000, 6000, 5000, 4000, 3000, 2000, 1000:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhci1bcxrgncb34wwldm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjhci1bcxrgncb34wwldm.png" alt="Cost and bytes for 30 day still the same no matter how many packages" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Again, the cost is the same regardless of package limit: $4.89 per query.&lt;/p&gt;

&lt;p&gt;Well then, let's repeat with the limit increasing by powers of ten, up to 1,000,000! This last one fetches data for all 531,022 packages on PyPI:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;limit&lt;/th&gt;
&lt;th&gt;projects count&lt;/th&gt;
&lt;th&gt;estimated cost&lt;/th&gt;
&lt;th&gt;bytes billed&lt;/th&gt;
&lt;th&gt;bytes processed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;43,447,746,560&lt;/td&gt;
&lt;td&gt;43,447,720,943&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;43,447,746,560&lt;/td&gt;
&lt;td&gt;43,447,720,943&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;100&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;43,447,746,560&lt;/td&gt;
&lt;td&gt;43,447,720,943&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000&lt;/td&gt;
&lt;td&gt;1,000&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;43,447,746,560&lt;/td&gt;
&lt;td&gt;43,447,720,943&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8000&lt;/td&gt;
&lt;td&gt;8,000&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;43,447,746,560&lt;/td&gt;
&lt;td&gt;43,447,720,943&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10000&lt;/td&gt;
&lt;td&gt;10,000&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;43,447,746,560&lt;/td&gt;
&lt;td&gt;43,447,720,943&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;100000&lt;/td&gt;
&lt;td&gt;100,000&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;43,447,746,560&lt;/td&gt;
&lt;td&gt;43,447,720,943&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1000000&lt;/td&gt;
&lt;td&gt;531,022&lt;/td&gt;
&lt;td&gt;0.20&lt;/td&gt;
&lt;td&gt;43,447,746,560&lt;/td&gt;
&lt;td&gt;43,447,720,943&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pitkptggoit9xdypjp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3pitkptggoit9xdypjp2.png" alt="Still same flat cost and bytes for 1 or 10 or 1,000 or 1,000,000 packages" width="800" height="484"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Again, same cost, whether for 1 package or 531,022 packages!&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding: the number of days affects the cost
&lt;/h2&gt;

&lt;p&gt;No surprise. I'd earlier noticed 365 days too took much quota, and I could continue with 30 days.&lt;/p&gt;

&lt;p&gt;Here's the estimated cost and bytes billed (for one package, all installers) between one and 30 days (&lt;code&gt;f"pypinfo --all --json --indent 0 --days {days} --limit 1 '' project"&lt;/code&gt;), showing a roughly linear increase:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faa4kq8d5neuxj9mf1g2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faa4kq8d5neuxj9mf1g2v.png" alt="Cost and bytes increase as the number of days increase" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It doesn't matter how many packages I fetch data for, I might as well fetch all and make it available to everyone, depending on the size of the data file. It will make sense to still offer a smaller file with 8,000 or so packages: often you just need a large-ish yet manageable number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It costs more to filter for only downloads from pip, so I've switched to fetching data for all installers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The number of days affects the cost, so I will need to decrease this in the future to stay within quota. For example, at some point I may need to switch from 30 to 25 days, and later from 25 to 20 days.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;More details from the investigation, the scripts and data files can be found at&lt;br&gt;
&lt;a href="https://github.com/hugovk/top-pypi-packages/issues/36" rel="noopener noreferrer"&gt;hugovk/top-pypi-packages#36&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And let me know if you know any tricks to reduce costs!&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Header photo: "&lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/royalaustralianhistoricalsociety/44366270300/"&gt;The Balancing Rock, Stonehenge, Near Glen Innes, NSW&lt;/a&gt;" by the &lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/royalaustralianhistoricalsociety/"&gt;Royal Australian Historical Society&lt;/a&gt;, with &lt;a rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pypi</category>
      <category>bigquery</category>
      <category>google</category>
    </item>
    <item>
      <title>Speed up CI with uv ⚡</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Sat, 02 Nov 2024 13:11:22 +0000</pubDate>
      <link>https://dev.to/hugovk/speed-up-ci-with-uv-53n1</link>
      <guid>https://dev.to/hugovk/speed-up-ci-with-uv-53n1</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2024/speed-up-ci-with-uv/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2024/speed-up-ci-with-uv/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;We can use &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;uv&lt;/a&gt; to make linting and testing on GitHub Actions around 1.5 times as fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Linting
&lt;/h2&gt;

&lt;p&gt;When using &lt;a href="https://pre-commit.com/" rel="noopener noreferrer"&gt;pre-commit&lt;/a&gt; for linting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;FORCE_COLOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;persist-credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.x"&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pre-commit/action@v3.0.1&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" rel="noopener noreferrer"&gt;pre-commit/action&lt;/a&gt; with &lt;a href="https://github.com/tox-dev/action-pre-commit-uv" rel="noopener noreferrer"&gt;tox-dev/action-pre-commit-uv&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;       - uses: actions/setup-python@v5
         with:
           python-version: "3.x"
&lt;span class="gd"&gt;-          cache: pip
-      - uses: pre-commit/action@v3.0.1
&lt;/span&gt;&lt;span class="gi"&gt;+      - uses: tox-dev/action-pre-commit-uv@v1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Lint&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;FORCE_COLOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;lint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;persist-credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.x"&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tox-dev/action-pre-commit-uv@v1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means uv will create virtual environments and install packages for pre-commit, which is faster for the initial seed operation when there's no cache.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lint comparison
&lt;/h3&gt;

&lt;p&gt;For example: &lt;a href="https://github.com/python/blurb/pull/32" rel="noopener noreferrer"&gt;python/blurb#32&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Times faster&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;No cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635610765/job/32405339441" rel="noopener noreferrer"&gt;60s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635612034/job/32405343101" rel="noopener noreferrer"&gt;37s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1.62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;With cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635702058/job/32405618322" rel="noopener noreferrer"&gt;11s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635703339/job/32405622146" rel="noopener noreferrer"&gt;11s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;When testing with tox:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;FORCE_COLOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.10"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.13"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.14"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;persist-credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.python-version }}&lt;/span&gt;
          &lt;span class="na"&gt;allow-prereleases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pip&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python --version&lt;/span&gt;
          &lt;span class="s"&gt;python -m pip install -U pip&lt;/span&gt;
          &lt;span class="s"&gt;python -m pip install -U tox&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tox tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;tox -e py&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" rel="noopener noreferrer"&gt;tox&lt;/a&gt; with &lt;a href="https://github.com/tox-dev/tox-uv" rel="noopener noreferrer"&gt;tox-uv&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;       - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v5
         with:
           python-version: ${{ matrix.python-version }}
           allow-prereleases: true
&lt;span class="gd"&gt;-          cache: pip
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="gd"&gt;-      - name: Install dependencies
-        run: |
-          python --version
-          python -m pip install -U pip
-          python -m pip install -U tox
&lt;/span&gt;&lt;span class="gi"&gt;+      - name: Install uv
+        uses: hynek/setup-cached-uv@v2
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;       - name: Tox tests
         run: |
&lt;span class="gd"&gt;-          tox -e py
&lt;/span&gt;&lt;span class="gi"&gt;+          uvx --with tox-uv tox -e py
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Test&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;FORCE_COLOR&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.9"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.10"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.13"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;persist-credentials&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.python-version }}&lt;/span&gt;
          &lt;span class="na"&gt;allow-prereleases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install uv&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hynek/setup-cached-uv@v2&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tox tests&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;uvx --with tox-uv tox -e py&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;tox-uv is tox plugin to replace virtualenv and pip with uv in your tox environments. We only need to install uv, and use &lt;code&gt;uvx&lt;/code&gt; to both install tox-uv and run tox, for faster installs of tox, the virtual environment, and the dependencies within it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test comparison
&lt;/h3&gt;

&lt;p&gt;For example: &lt;a href="https://github.com/python/blurb/pull/32" rel="noopener noreferrer"&gt;python/blurb#32&lt;/a&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Times faster&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;No cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635722298/usage" rel="noopener noreferrer"&gt;2m 0s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635723255/usage" rel="noopener noreferrer"&gt;1m 26s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1.40&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;With cache&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635745236/usage" rel="noopener noreferrer"&gt;1m 58s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/hugovk/blurb/actions/runs/11635746618/usage" rel="noopener noreferrer"&gt;1m 22s&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1.44&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  No pip with tox-uv
&lt;/h3&gt;

&lt;p&gt;One difference with uv environments compared to regular venv/virtualenv ones is that they do not have pip. This means calls to pip need replacing, for example in &lt;code&gt;tox.ini&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; [testenv]
&lt;span class="gd"&gt;-commands_pre =
-    {envpython} -m pip install -U -r requirements.txt
&lt;/span&gt;&lt;span class="gi"&gt;+deps =
+    -r requirements.txt
&lt;/span&gt; pass_env =
     FORCE_COLOR
 commands =
     {envpython} -m pytest {posargs}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you still need pip (or setuptools or wheel), add &lt;a href="https://github.com/tox-dev/tox-uv#environment-creation" rel="noopener noreferrer"&gt;&lt;code&gt;uv_seed = True&lt;/code&gt;&lt;/a&gt; to your &lt;code&gt;[testenv]&lt;/code&gt; to inject them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus tip
&lt;/h2&gt;

&lt;p&gt;Run the new tool &lt;a href="https://github.com/woodruffw/zizmor" rel="noopener noreferrer"&gt;zizmor&lt;/a&gt; to find security issues in GitHub Actions.&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Header photo: "&lt;a rel="noopener noreferrer" href="https://finna.fi/Record/hkm.BB327C7B-90B8-45DF-90A1-ABC7B74F6BAA?imgid=1"&gt;Road cycling at the 1952 Helsinki Olympics&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://www.flickr.com/people/nlmhmd/"&gt;Olympia-Kuva Oy &amp;amp; Helsinki City Museum&lt;/a&gt;, &lt;a rel="noopener noreferrer" href="https://creativecommons.org/publicdomain/mark/1.0/deed.en"&gt;Public Domain&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>githubactions</category>
      <category>uv</category>
    </item>
    <item>
      <title>Python Core Developer Sprint 2024</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Sat, 05 Oct 2024 19:39:48 +0000</pubDate>
      <link>https://dev.to/hugovk/python-core-developer-sprint-2024-3dp6</link>
      <guid>https://dev.to/hugovk/python-core-developer-sprint-2024-3dp6</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2024/python-core-developer-sprint-2024/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2024/python-core-developer-sprint-2024/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;🐍🏃The week before last was the annual Python Core Dev Sprint, graciously hosted by Meta in Bellevue, WA!&lt;/p&gt;

&lt;p&gt;The idea: bring a bunch of Python core team members, triagers, and special guests to the same room for a week. It's hugely beneficial and productive, we held many in-depth discussions that just don't happen when we're all remote and async, and got to work on many different things together.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rpkesswa2xbczkuwoq7.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5rpkesswa2xbczkuwoq7.jpeg" alt="A panoramic shot of a room of people, some sitting at desks, some standing, some working on laptops, some in discussion." width="800" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;The sprint room&lt;/small&gt;&lt;/center&gt;

&lt;p&gt;During the week, I reviewed 39 PRs, created 15, merged 10, updated 4, and closed 2 issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monday highlights
&lt;/h2&gt;

&lt;p&gt;As release manager for &lt;a href="https://peps.python.org/pep-0745/" rel="noopener noreferrer"&gt;Python 3.14&lt;/a&gt;, I discussed with &lt;a href="https://fosstodon.org/@brettcannon" rel="noopener noreferrer"&gt;Brett Cannon&lt;/a&gt; one of his project ideas which will come after lock files, and after the next big one.&lt;/p&gt;

&lt;p&gt;Also as RM, discussed with &lt;a href="https://cloudisland.nz/@freakboy3742" rel="noopener noreferrer"&gt;Russell Keith-Magee&lt;/a&gt;, Ned Deily, &lt;a href="https://mastodon.social/@ambv" rel="noopener noreferrer"&gt;Łukasz Langa&lt;/a&gt; and &lt;a href="https://social.coop/@Yhg1s" rel="noopener noreferrer"&gt;Thomas Wouters&lt;/a&gt; about &lt;a href="https://beeware.org/news/buzz/2024q4-roadmap/" rel="noopener noreferrer"&gt;including official binaries for iOS and Android&lt;/a&gt;, which wandered into ideas about security releases.&lt;/p&gt;

&lt;p&gt;I did some maintenance of our PyPI projects, adding &lt;a href="https://peps.python.org/pep-0740/" rel="noopener noreferrer"&gt;PEP 740 attestations&lt;/a&gt;, support for the &lt;a href="https://peps.python.org/pep-0719/" rel="noopener noreferrer"&gt;new Python 3.13&lt;/a&gt; and dropping support for the &lt;a href="https://peps.python.org/pep-0569/" rel="noopener noreferrer"&gt;very-nearly-EOL 3.8&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuesday highlights
&lt;/h2&gt;

&lt;p&gt;Started investigating slow doctest on 3.13+ with &lt;a href="https://fosstodon.org/@AlexWaygood" rel="noopener noreferrer"&gt;Alex Waygood&lt;/a&gt;, who on Wednesday narrowed it down to a problem with the &lt;a href="https://github.com/python/cpython/issues/124567" rel="noopener noreferrer"&gt;new incremental garbage collector&lt;/a&gt;, which would go on to be reverted by Friday and result in &lt;a href="https://discuss.python.org/t/python-3-12-7-and-3-13-0rc3-released/66306?u=hugovk" rel="noopener noreferrer"&gt;Python 3.13's Monday release to be postponed and replaced with an extra release candidate&lt;/a&gt;. Not ideal, but much better to discover these things before the big release.&lt;/p&gt;

&lt;p&gt;We had a Q&amp;amp;A session with the Steering Council: &lt;a href="https://mastodon.social/@pumpichank" rel="noopener noreferrer"&gt;Barry Warsaw&lt;/a&gt;, Emily Morehouse, &lt;a href="https://infosec.exchange/@gpshead" rel="noopener noreferrer"&gt;Gregory P. Smith&lt;/a&gt;, Pablo Galindo Salgado and Thomas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7yyzyqtztoednyacr1r.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg7yyzyqtztoednyacr1r.jpg" alt="The steering council: Barry, Emily, Greg with a microphone, Pablo and Thomas sitting on high stools." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;The Python Steering Council&lt;/small&gt;&lt;/center&gt;

&lt;p&gt;Proofread Guido van Rossum's &lt;a href="https://discuss.python.org/t/changing-pep-13-to-adopt-bloc-star-voting/64971?u=hugovk" rel="noopener noreferrer"&gt;STAR voting proposal&lt;/a&gt; for electing future steering councils.&lt;/p&gt;

&lt;p&gt;Discussed with Eric Snow his novel method for displaying many code samples in a table, using &lt;code&gt;&amp;lt;details&amp;gt;&lt;/code&gt; disclosures to prevent the table being too wide. Looks like a good solution!&lt;/p&gt;

&lt;h2&gt;
  
  
  Wednesday highlights
&lt;/h2&gt;

&lt;p&gt;I applied the &lt;a href="https://github.com/python/peps/pull/3995" rel="noopener noreferrer"&gt;finishing touches&lt;/a&gt; to &lt;a href="https://peps.python.org/pep-2026/" rel="noopener noreferrer"&gt;PEP 2026&lt;/a&gt; (Calendar versioning for Python) and Barry gave it a final review. Ready for submission!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://fosstodon.org/@sethmlarson" rel="noopener noreferrer"&gt;Seth Larson&lt;/a&gt;, the PSF &lt;a href="https://sethmlarson.dev/security-developer-in-residence" rel="noopener noreferrer"&gt;Security Developer-in-Residence&lt;/a&gt;, wasn't at the sprint but we discussed our plan to stop providing GPG signatures for CPython and rely on SigStore instead. Expect a PEP soon!&lt;/p&gt;

&lt;p&gt;Also not at the sprint, I recommended PSF &lt;a href="https://pyfound.blogspot.com/2024/07/announcing-our-new-infrastructure.html" rel="noopener noreferrer"&gt;Infrastructure Engineer&lt;/a&gt; &lt;a href="https://fosstodon.org/@Monorepo" rel="noopener noreferrer"&gt;Jacob Coffee&lt;/a&gt; as a &lt;a href="https://dev.toCPython"&gt;CPython triager&lt;/a&gt;. Welcome aboard!&lt;/p&gt;

&lt;p&gt;The whole room discussed including &lt;a href="https://discuss.python.org/t/static-type-annotations-in-cpython/65068?u=hugovk" rel="noopener noreferrer"&gt;static type annotations in CPython&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We had a Q&amp;amp;A session with two of the three Developers-in-Residence, Łukasz and &lt;a href="https://mastodon.social/@encukou" rel="noopener noreferrer"&gt;Petr Viktorin&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbn0yvsz7t9t2j7yfd2j.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvbn0yvsz7t9t2j7yfd2j.jpg" alt="Łukasz (with a microphone) and Petr sitting on high stools." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;Q&amp;amp;A with Łukasz and Petr&lt;/small&gt;&lt;/center&gt;

&lt;p&gt;Discussed &lt;a href="https://discuss.python.org/t/collecting-feedback-about-expanding-the-voter-pool-for-sc-elections?u=hugovk" rel="noopener noreferrer"&gt;expanding the voter pool for Steering Council elections&lt;/a&gt; with &lt;a href="https://fosstodon.org/@mariatta" rel="noopener noreferrer"&gt;Mariatta&lt;/a&gt;, Greg and Thomas.&lt;/p&gt;

&lt;p&gt;Larry Hastings handed out, in return for &lt;em&gt;oohs&lt;/em&gt; and &lt;em&gt;aahs&lt;/em&gt;, some nice P.C.D.S. 2024 stickers he generously designed and printed up for us. Thanks!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farqsq2qg7h1giiko2g40.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Farqsq2qg7h1giiko2g40.jpg" 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." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;PCDS 2024 stickers by Larry&lt;/small&gt;&lt;/center&gt;

&lt;h2&gt;
  
  
  Thursday highlights
&lt;/h2&gt;

&lt;p&gt;On the 26th September, at 10:26 Bellevue time (20:26 Helsinki time), I &lt;a href="https://github.com/python/steering-council/issues/255" rel="noopener noreferrer"&gt;submitted PEP 2026 to the Steering Council!&lt;/a&gt;🤞&lt;/p&gt;

&lt;p&gt;Brett discussed whether we should update &lt;a href="https://discuss.python.org/t/updating-pep-387-to-prefer-5-year-deprecations-instead-of-2-years/65166?u=hugovk" rel="noopener noreferrer"&gt;PEP 387 to prefer 5 year deprecations instead of 2 years&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Brandt Bucher gave us all an update on the progress of the Just-in-Time (JIT) compiler ("we went from 0% slower to 0% faster!") and we discussed plans for Python 3.14.&lt;/p&gt;

&lt;p&gt;Because I couldn't attend Thursday's &lt;a href="https://helsinki-python.github.io/" rel="noopener noreferrer"&gt;Helsinki Python meetup&lt;/a&gt; due to being at another kind of Python meetup on the other side of the world, I gave the famous HelPy quiz to the assembled core devs. Unsurprisingly they did pretty well, but the most incorrect answer was a pleasant surprise: we've had ~400 not ~80 new contributors to Python 3.13!&lt;/p&gt;

&lt;p&gt;Pablo performed card tricks!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlusli7ylxa3di48nmct.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftlusli7ylxa3di48nmct.jpg" alt="Pablo splaying open a deck of PyCon US playing cards and Greg picking one." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;Magic from Pablo&lt;/small&gt;&lt;/center&gt;

&lt;p&gt;Meta took us out for a delicious dinner at a local fish restaurant. Thank you!&lt;/p&gt;

&lt;h2&gt;
  
  
  Friday highlights
&lt;/h2&gt;

&lt;p&gt;Mariatta presented ideas to Jelle Zijlstra, Petr, Russell and me about to use modern tools to create a modern, interactive tutorial.&lt;/p&gt;

&lt;p&gt;Also during the week, continued work with Adam Turner on improving the &lt;a href="https://docs.python.org/3/" rel="noopener noreferrer"&gt;docs.python.org&lt;/a&gt; build. Adam wasn't at the sprint, so tag-teamed PR reviews overnight. After &lt;a href="https://github.com/python/docsbuild-scripts/issues/169#issuecomment-2389743956" rel="noopener noreferrer"&gt;much work straddling many teams, projects and repos&lt;/a&gt;, we've got the full HTML build loop for 13 languages × 3 versions down from over 40 hours to just under 9 hours, with more improvements coming.&lt;/p&gt;

&lt;p&gt;Made a &lt;a href="https://hugovk-cpython.readthedocs.io/en/pydata-sphinx-theme/" rel="noopener noreferrer"&gt;demo&lt;/a&gt; of the CPython docs using the &lt;a href="https://pydata-sphinx-theme.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;PyData Sphinx Theme&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Along with around 25 others, I was on Łukasz and Pablo's &lt;a href="https://podcasters.spotify.com/pod/show/corepy/episodes/Episode-15-Core-sprint-at-Meta-e2p64tc" rel="noopener noreferrer"&gt;core.py podcast&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobqejp8n5acztsb8knaq.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobqejp8n5acztsb8knaq.jpg" alt="Łukasz and Pablo in their ad-hoc podcast studio in a Meta meeting room." width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;Łukasz and Pablo in their ad-hoc podcast studio in a Meta meeting room&lt;/small&gt;&lt;/center&gt;

&lt;p&gt;Itamar gave us cake for the podcast's first birthday!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gz97wzjn5khsjg4anln.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8gz97wzjn5khsjg4anln.jpg" alt="A big white cake, with decorations of Łukasz and Pablo's faces, along with the Core.py logo, a big digit 1, headphones, microphone, bananas, and " width="800" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;cake.py. Photo by Itamar Oren.&lt;/small&gt;&lt;/center&gt;

&lt;h2&gt;
  
  
  Thank you
&lt;/h2&gt;

&lt;p&gt;It was a hugely productive week, big thanks to Itamar Oren and Meta for organising and hosting!&lt;/p&gt;

&lt;p&gt;See also Mariatta's excellent &lt;a href="https://mariatta.ca/tags/sprint/" rel="noopener noreferrer"&gt;blog posts&lt;/a&gt;, and I recommend the &lt;a href="https://podcasters.spotify.com/pod/show/corepy/episodes/Episode-15-Core-sprint-at-Meta-e2p64tc" rel="noopener noreferrer"&gt;core.py podcast&lt;/a&gt; with short interviews with some 25 attendees! Łukasz and Pablo were also guests on the &lt;a href="https://changelog.com/podcast/611" rel="noopener noreferrer"&gt;Changelog podcast&lt;/a&gt; during the sprint.&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Header photo by Itamar Oren&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>sprint</category>
      <category>coredevsprint</category>
      <category>2024</category>
    </item>
    <item>
      <title>PyCon US 2024: A roundup of writeups</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Thu, 13 Jun 2024 14:41:55 +0000</pubDate>
      <link>https://dev.to/hugovk/pycon-us-2024-a-roundup-of-writeups-26hj</link>
      <guid>https://dev.to/hugovk/pycon-us-2024-a-roundup-of-writeups-26hj</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2024/pycon-us-2024-a-roundup-of-writeups/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2024/pycon-us-2024-a-roundup-of-writeups/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;If you read just one, check &lt;a href="https://katherinemichel.github.io/portfolio/pycon-us-2024-recap.html" rel="noopener noreferrer"&gt;Kati's thorough recap&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;22nd May 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://nedbatchelder.com/blog/202405/echos_of_the_people_api_user_guide.html" rel="noopener noreferrer"&gt;Echos of the People API user guide&lt;/a&gt; by &lt;a href="https://mastodon.social/@nedbat@hachyderm.io/112479219635272740" rel="noopener noreferrer"&gt;Ned Batchelder&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;24th May 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://wagtail.org/blog/pycon-2024/" rel="noopener noreferrer"&gt;Wagtailers spread their wings at PyCon 2024&lt;/a&gt; by &lt;a href="https://fosstodon.org/@vossisboss" rel="noopener noreferrer"&gt;Meagen Voss&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/vossisboss"&gt;@vossisboss&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://flet.dev/blog/flet-at-pycon-us-2024/" rel="noopener noreferrer"&gt;Flet at PyCon US 2024&lt;/a&gt; by Feodor Fitsner&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;27th May 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://blog.tomy.me/en/posts/pycon-us-2024/" rel="noopener noreferrer"&gt;PyCon US 2024: My First PyCon in US 🫶🏻&lt;/a&gt; by &lt;a href="https://mastodon.social/@tomyhsieh" rel="noopener noreferrer"&gt;Tomy Hsieh&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;28th May 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://treyhunner.com/2024/05/pycon-2024-reflection/" rel="noopener noreferrer"&gt;PyCon 2024 Reflection&lt;/a&gt; by &lt;a href="https://mastodon.social/@treyhunner/112520554890667118" rel="noopener noreferrer"&gt;Trey Hunner&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://simonwillison.net/2024/May/28/weeknotes/" rel="noopener noreferrer"&gt;Weeknotes: PyCon US 2024&lt;/a&gt; by &lt;a href="https://fedi.simonwillison.net/@simon/112520529116346191" rel="noopener noreferrer"&gt;Simon Willison&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://stacklok.com/blog/3-key-takeaways-from-pycon-us-2024" rel="noopener noreferrer"&gt;3 key takeaways from PyCon US 2024&lt;/a&gt; by Luis Juncal &amp;amp; Yolanda Robla&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;30th May 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://www.pyopensci.org/blog/recap-pyos-pyconus-2024.html" rel="noopener noreferrer"&gt;pyOpenSci at PyCon US 2024 - Python packaging and community&lt;br&gt;
&lt;/a&gt; by &lt;a href="https://fosstodon.org/@leahawasser/112554205196871020" rel="noopener noreferrer"&gt;Leah Wasser&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://kafkai.com/en/blog/our-experience-at-pycon-us-2024-in-pittsburgh/" rel="noopener noreferrer"&gt;Our Experience at PyCon US 2024 in Pittsburgh&lt;/a&gt; by &lt;a href="https://hachyderm.io/@muheuenga/112530136789502549" rel="noopener noreferrer"&gt;Ngazetungue Muheue&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/ngazetungue"&gt;@ngazetungue&lt;/a&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1st June 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://katherinemichel.github.io/portfolio/pycon-us-2024-recap.html" rel="noopener noreferrer"&gt;PyCon US 2024 Recap&lt;/a&gt;
by &lt;a href="https://fosstodon.org/@kati/112542145378019538" rel="noopener noreferrer"&gt;Kati Michel&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/katherinemichel"&gt;@katherinemichel&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;4th June 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://mangoumbrella.com/post/pycon-us-2024" rel="noopener noreferrer"&gt;PyCon US 2024&lt;/a&gt; by &lt;a href="https://mastodon.social/@y2mango/112556835094650562" rel="noopener noreferrer"&gt;Yilei Yang&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;13th June 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://sethmlarson.dev/security-developer-in-residence-report-37" rel="noopener noreferrer"&gt;PyCon US 2024 as Security Developer-in-Residence&lt;/a&gt; by &lt;a href="https://fosstodon.org/@sethmlarson" rel="noopener noreferrer"&gt;Seth Michael Larson&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/sethmlarson"&gt;@sethmlarson&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;14th June 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://pyfound.blogspot.com/2024/06/python-language-summit-2024.html" rel="noopener noreferrer"&gt;The Python Language Summit 2024&lt;/a&gt; by &lt;a href="https://fosstodon.org/@sethmlarson" rel="noopener noreferrer"&gt;Seth Michael Larson&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/sethmlarson"&gt;@sethmlarson&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;16th June 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dafoster.net/articles/2024/06/16/pycon-us-2024-highlights/" rel="noopener noreferrer"&gt;PyCon US 2024 Highlights&lt;/a&gt; by &lt;a href="https://mastodon.world/@davidfstr" rel="noopener noreferrer"&gt;David Foster&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;19th June 2024&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://medium.com/@monicaoyugi/from-pittsburgh-to-new-york-a-pycon-us-2024-adventure-1727c952509c" rel="noopener noreferrer"&gt;From Pittsburgh to New York: A PyCon US 2024 Adventure&lt;/a&gt; by &lt;a href="https://mastodon.social/@monics" rel="noopener noreferrer"&gt;Monica Oyugi&lt;/a&gt; (&lt;a class="mentioned-user" href="https://dev.to/monicaoyugi"&gt;@monicaoyugi&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;Header photo: Downtown Pittsburgh seen between the Andy Warhol Bridge and Roberto Clemente Bridge&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>pycon</category>
      <category>pyconus</category>
      <category>2024</category>
    </item>
    <item>
      <title>Help test Python 3.13!</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Tue, 04 Jun 2024 13:04:03 +0000</pubDate>
      <link>https://dev.to/hugovk/help-test-python-313-14j1</link>
      <guid>https://dev.to/hugovk/help-test-python-313-14j1</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2024/help-test-python-313/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2024/help-test-python-313/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Calling all Python library maintainers! 🐍&lt;/p&gt;

&lt;p&gt;The Python 3.13 beta is out! 🎉&lt;/p&gt;

&lt;p&gt;&lt;a href="https://peps.python.org/pep-0719/#release-schedule" rel="noopener noreferrer"&gt;PEP 719&lt;/a&gt; defines the release schedule for Python 3.13.0:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first beta candidate came out on 8th May 2024&lt;/li&gt;
&lt;li&gt;The first release candidate is set for 30th July 2024&lt;/li&gt;
&lt;li&gt;And the full release is set for 1st October 2024&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In his &lt;a href="https://discuss.python.org/t/python-3-13-0b1-now-available/52891?u=hugovk" rel="noopener noreferrer"&gt;announcement&lt;/a&gt;, Thomas Wouters, release manager for Python 3.12 and 3.13, said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We &lt;strong&gt;strongly encourage&lt;/strong&gt; maintainers of third-party Python projects to test with 3.13 during the beta phase and report issues found to the &lt;a href="https://github.com/python/cpython/issues" rel="noopener noreferrer"&gt;Python bug tracker&lt;/a&gt; as soon as possible. While the release is planned to be feature complete entering the beta phase, it is possible that features may be modified or, in rare cases, deleted up until the start of the release candidate phase (Tuesday 2024-07-30). Our goal is to have no ABI changes after beta 4 and as few code changes as possible after 3.13.0rc1, the first release candidate. To achieve that, it will be &lt;strong&gt;extremely important&lt;/strong&gt; to get as much exposure for 3.13 as possible during the beta phase.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Test with 3.13
&lt;/h2&gt;

&lt;p&gt;It's now time for us library maintainers to start testing our projects with 3.13. There's two big benefits:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;There have been &lt;a href="https://docs.python.org/3.13/whatsnew/3.13.html#removed" rel="noopener noreferrer"&gt;removals and changes&lt;/a&gt; in Python 3.13. Testing now helps us make our code compatible and avoid any big surprises (for us and our users) at the big launch in October.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;We might find bugs in Python itself! Reporting those will help get them fixed and help everyone.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions: setup-python
&lt;/h3&gt;

&lt;p&gt;To test the latest alpha, beta or release candidate with &lt;a href="https://github.com/actions/setup-python#supported-version-syntax" rel="noopener noreferrer"&gt;actions/setup-python&lt;/a&gt;, add &lt;code&gt;3.13&lt;/code&gt; and &lt;code&gt;allow-prereleases: true&lt;/code&gt; to your workflow matrix.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.10"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.13"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.python-version }}&lt;/span&gt;
          &lt;span class="na"&gt;allow-prereleases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(We can instead use &lt;code&gt;3.13-dev&lt;/code&gt; and omit &lt;code&gt;allow-prereleases: true&lt;/code&gt;, but I find the above a bit neater, and when 3.13.0 final is released in October, it will continue testing with full release versions.)&lt;/p&gt;

&lt;h3&gt;
  
  
  GitHub Actions: deadsnakes
&lt;/h3&gt;

&lt;p&gt;For the bleeding edge, we can use &lt;a href="https://github.com/deadsnakes/action" rel="noopener noreferrer"&gt;deadsnakes/action&lt;/a&gt; to test the latest nightly build:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fail-fast&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.10"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.11"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.12"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3.13-dev"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python ${{ matrix.python-version }}&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!endsWith(matrix.python-version,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'-dev')"&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.python-version }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deadsnakes/action@v3.1.0&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up Python ${{ matrix.python-version }} (deadsnakes)&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;endsWith(matrix.python-version, '-dev')&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.python-version }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  When to support 3.13?
&lt;/h2&gt;

&lt;p&gt;When should you declare support and add the &lt;code&gt;Programming Language :: Python :: 3.13&lt;/code&gt; &lt;a href="https://pypi.org/classifiers/" rel="noopener noreferrer"&gt;Trove classifier&lt;/a&gt;? Some &lt;a href="https://pyreadiness.org/3.13/" rel="noopener noreferrer"&gt;projects already have&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;If you have a pure Python project, you can release now.&lt;/p&gt;

&lt;p&gt;If you have C extensions and other projects depend on yours, a preview release with wheels will help them test and prepare. I've already started releasing these.&lt;/p&gt;

&lt;h3&gt;
  
  
  ABI breaks?
&lt;/h3&gt;

&lt;p&gt;ABI breaks during the beta are infrequent and unintentional. If they happen, you can rebuild your wheels and upload them to an existing PyPI release by adding an optional &lt;a href="https://packaging.python.org/en/latest/specifications/binary-distribution-format/#file-format" rel="noopener noreferrer"&gt;&lt;em&gt;build tag&lt;/em&gt;&lt;/a&gt; to the filename:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The wheel filename is &lt;code&gt;{distribution}-{version}(-{build tag})?-{python tag}-{abi tag}-{platform tag}.whl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;build tag: [...] Acts as a tie-breaker if two wheel file names are the same in all other respects (i.e. name, version, and other tags).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For example, this updates the filename and metadata with build number 1, and removes the original file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"wheel&amp;gt;=0.4.0"&lt;/span&gt;
wheel tags &lt;span class="nt"&gt;--build&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;--remove&lt;/span&gt; sampleproject-5.0.0-cp313-cp313-macosx_10_10_x86_64.whl
&lt;span class="c"&gt;# this creates a file named sampleproject-5.0.0-1-cp313-cp313-macosx_10_10_x86_64.whl&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upload it, and the new file will be used instead of the old one. See also &lt;a href="https://snarky.ca/what-to-do-when-you-botch-a-release-on-pypi/#a-wheel-file-wasnt-compiled-properly" rel="noopener noreferrer"&gt;Brett Cannon's advice on making new wheels&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In any case, let's start testing 3.13 now! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  See also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hugovk/help-us-test-free-threaded-python-without-the-gil-1hgf"&gt;Help us test free-threaded Python without the GIL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/whatsnew/3.13.html" rel="noopener noreferrer"&gt;What’s New In Python 3.13&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;Header photo: Lot 044 from the &lt;a href="https://us.pycon.org/2024/" rel="noopener noreferrer"&gt;PyCon 2024&lt;/a&gt; &lt;a href="https://mastodon.social/@Lorenanicole/112468770670490719" rel="noopener noreferrer"&gt;PyLadies Auction&lt;/a&gt;: "A pair of hand-woven snakes (&lt;a href="https://www.pylatam.org/" rel="noopener noreferrer"&gt;PyCon Latam&lt;/a&gt; 2023 edition), donated by the PyCon Latam Organizers. This is a souvenir from PyCon Latam held in Mexica 2023 that represents the snakes of the PyLatam community logo. They are made completely by hand."&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>ci</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Printable PyCon 2024 schedule</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Sat, 11 May 2024 11:01:33 +0000</pubDate>
      <link>https://dev.to/hugovk/printable-pycon-2024-schedule-4pdp</link>
      <guid>https://dev.to/hugovk/printable-pycon-2024-schedule-4pdp</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2024/printable-pycon-2024-schedule/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2024/printable-pycon-2024-schedule/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Want to print out the PyCon US schedule? Paper doesn't run out of batteries, is easy to scribble on, and stuff into a pocket (technical term: &lt;em&gt;the affordances of paper&lt;/em&gt;).&lt;/p&gt;

&lt;p&gt;Here's some custom CSS and JavaScript to make it nicely printable.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install the &lt;a href="https://chrome.google.com/webstore/detail/styler/bogdgcfoocbajfkjjolkmcdcnnellpkb?hl=en" rel="noopener noreferrer"&gt;Styler browser extension&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;View a PyCon schedule page such as  &lt;a href="https://us.pycon.org/2024/schedule/talks/" rel="noopener noreferrer"&gt;https://us.pycon.org/2024/schedule/talks/&lt;/a&gt; and click the Styler extension's S icon&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Paste this CSS into the upper box:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="nt"&gt;body&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="nc"&gt;.internal-page-header&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;body&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="nc"&gt;.panel-heading&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nc"&gt;.pycon-schedule&lt;/span&gt; &lt;span class="nt"&gt;footer&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="nt"&gt;body&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="nc"&gt;.badges&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nc"&gt;.pycon-schedule&lt;/span&gt; &lt;span class="nc"&gt;.container&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;max-width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fit-content&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;body&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="nc"&gt;.content&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;margin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nc"&gt;.pycon-schedule&lt;/span&gt; &lt;span class="nc"&gt;.calendar&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;auto&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;100%&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;body&lt;/span&gt;&lt;span class="nc"&gt;.pycon-schedule&lt;/span&gt; &lt;span class="nc"&gt;.slot&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;@media&lt;/span&gt; &lt;span class="n"&gt;print&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="py"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="err"&gt;{&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&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="nd"&gt;:after&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;none&lt;/span&gt; &lt;span class="cp"&gt;!important&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Paste this JavaScript into the lower box:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&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="nf"&gt;ready&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&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="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024/schedule/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; 
       &lt;span 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="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;2024/schedule/presentation/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;body&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;addClass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;pycon-schedule&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Print!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It'll run on any of the &lt;code&gt;https://us.pycon.org/2024/schedule/*&lt;/code&gt; pages, but not the individual presentation pages such as &lt;code&gt;https://us.pycon.org/2024/schedule/presentation/61/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It's not perfect, the right edge is slightly cut off, but it's more printable than the original.&lt;/p&gt;

&lt;p&gt;Once printed, you can click the Styler icon and the &lt;code&gt;x&lt;/code&gt; button to disable Styler for the site so you can browse the original version.&lt;/p&gt;

&lt;p&gt;See also:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hugovk/printable-pycon-2023-schedule-251d"&gt;Printable PyCon 2023 schedule
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>pycon</category>
      <category>python</category>
      <category>print</category>
      <category>css</category>
    </item>
    <item>
      <title>Help us test free-threaded Python without the GIL</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Fri, 10 May 2024 12:18:27 +0000</pubDate>
      <link>https://dev.to/hugovk/help-us-test-free-threaded-python-without-the-gil-1hgf</link>
      <guid>https://dev.to/hugovk/help-us-test-free-threaded-python-without-the-gil-1hgf</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2023/help-us-test-free-threaded-python-without-the-gil/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2023/help-us-test-free-threaded-python-without-the-gil/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Python 3.13 is &lt;a href="https://peps.python.org/pep-0719/" rel="noopener noreferrer"&gt;due out in October 2024&lt;/a&gt; and work is underway to implement &lt;em&gt;experimental support&lt;/em&gt; for &lt;a href="https://peps.python.org/pep-0703/" rel="noopener noreferrer"&gt;PEP 703 - Making the Global Interpreter Lock Optional in CPython&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;See also &lt;a href="https://docs.python.org/3.13/whatsnew/3.13.html#free-threaded-cpython" rel="noopener noreferrer"&gt;Free-threaded CPython&lt;/a&gt; in "What's New in Python 3.13?"&lt;/p&gt;

&lt;p&gt;As the Steering Council noted in their &lt;a href="https://discuss.python.org/t/pep-703-making-the-global-interpreter-lock-optional-in-cpython-acceptance/37075?u=hugovk" rel="noopener noreferrer"&gt;acceptance of the PEP&lt;/a&gt;, to succeed it's important to have community support.&lt;/p&gt;

&lt;p&gt;Projects will need to test their code with free-threaded (aka "nogil" but &lt;a href="https://discuss.python.org/t/pep-703-making-the-global-interpreter-lock-optional-in-cpython-acceptance/37075?u=hugovk" rel="noopener noreferrer"&gt;don't call it that!&lt;/a&gt;) Python builds to help us find bugs in CPython, and to check their code is compatible.&lt;/p&gt;

&lt;p&gt;Here's some ways to test.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Official installers&lt;/li&gt;
&lt;li&gt;Build it yourself&lt;/li&gt;
&lt;li&gt;deadsnakes&lt;/li&gt;
&lt;li&gt;GitHub Actions?&lt;/li&gt;
&lt;li&gt;Fedora&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Official installers
&lt;/h2&gt;

&lt;p&gt;The official macOS (starting in beta 2) and Windows installers has an option to install the free-threading binaries, which also installs &lt;code&gt;python3.13t&lt;/code&gt; alongside the regular &lt;code&gt;python3.13&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/python/cpython/issues/120098" rel="noopener noreferrer"&gt;python/cpython#120098&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/using/windows.html#installing-free-threaded-binaries" rel="noopener noreferrer"&gt;https://docs.python.org/3.13/using/windows.html#installing-free-threaded-binaries&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Build it yourself
&lt;/h2&gt;

&lt;p&gt;Check out the CPython Git repo and &lt;a href="https://devguide.python.org/" rel="noopener noreferrer"&gt;build yourself&lt;/a&gt; using the &lt;a href="https://docs.python.org/3.13/using/configure.html#cmdoption-disable-gil" rel="noopener noreferrer"&gt;&lt;code&gt;--disable-gil&lt;/code&gt; configuration flag&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;For example, on macOS I run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&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;"-I&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt; gdbm&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/include"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="gp"&gt;  GDBM_LIBS="-L$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt; gdbm&lt;span class="o"&gt;)&lt;/span&gt;/lib &lt;span class="nt"&gt;-lgdbm&lt;/span&gt;&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;  ./configure --config-cache \
              --with-system-libmpdec \
&lt;/span&gt;&lt;span class="gp"&gt;              --with-openssl="$&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt; openssl@3.0&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;" &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;
&lt;/span&gt;&lt;span class="go"&gt;              --disable-gil &amp;amp;&amp;amp; make -s -j10
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./python.exe &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"&lt;/span&gt;
&lt;span class="go"&gt;1
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;./python.exe &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys._is_gil_enabled())"&lt;/span&gt;
&lt;span class="go"&gt;False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;More details in the devguide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devguide.python.org/getting-started/setup-building/" rel="noopener noreferrer"&gt;https://devguide.python.org/getting-started/setup-building/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  GitHub Actions: deadsnakes/action
&lt;/h2&gt;

&lt;p&gt;We can use the &lt;a href="https://github.com/deadsnakes/action" rel="noopener noreferrer"&gt;deadsnakes/action&lt;/a&gt; to test Ubuntu.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;3.12&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;3.13-dev&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-python@v5&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!endsWith(matrix.python-version,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;'-dev')"&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.python-version }}&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;deadsnakes/action@v3.1.0&lt;/span&gt;
        &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;endsWith(matrix.python-version, '-dev')&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;python-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ matrix.python-version }}&lt;/span&gt;
          &lt;span class="na"&gt;nogil&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;python --version --version&lt;/span&gt;
          &lt;span class="s"&gt;which python&lt;/span&gt;
          &lt;span class="s"&gt;python -c "import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"&lt;/span&gt;
          &lt;span class="s"&gt;python -c "import sys; print(sys._is_gil_enabled())"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub Actions: actions/setup-python
&lt;/h2&gt;

&lt;p&gt;I've asked for support at &lt;a href="https://github.com/actions/setup-python/issues/771" rel="noopener noreferrer"&gt;actions/setup-python#771&lt;/a&gt;, they're looking into it 🤞&lt;/p&gt;

&lt;p&gt;In the meantime, give it an upvote, and use deadsnakes/action ⤴️&lt;/p&gt;

&lt;h2&gt;
  
  
  deadsnakes: PPA
&lt;/h2&gt;

&lt;p&gt;The deadsnakes project provides Personal Package Archives of Python packaged for Ubuntu, included free-threaded builds.&lt;/p&gt;

&lt;p&gt;For example, &lt;code&gt;python3.13-nogil&lt;/code&gt; under "python3.13 - 3.13.0~a2-1+focal1" and "python3.13 - 3.13.0~a2-1+jammy1" at &lt;a href="https://launchpad.net/%7Edeadsnakes/+archive/ubuntu/ppa/+packages" rel="noopener noreferrer"&gt;https://launchpad.net/~deadsnakes/+archive/ubuntu/ppa/+packages&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fedora
&lt;/h2&gt;

&lt;p&gt;Fedora uses the &lt;code&gt;python3.13t&lt;/code&gt; executable name &lt;a href="https://github.com/python/steering-council/issues/221#issuecomment-1841593283" rel="noopener noreferrer"&gt;as decided by the Steering Council&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;dnf &lt;span class="nb"&gt;install &lt;/span&gt;python3.13-freethreading
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Run&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/python3.13t &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"&lt;/span&gt;
&lt;span class="go"&gt;1
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;/usr/bin/python3.13t &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys._is_gil_enabled())"&lt;/span&gt;
&lt;span class="go"&gt;False
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to check your build
&lt;/h2&gt;

&lt;p&gt;To confirm if you're using a free-threaded build, check the double &lt;code&gt;--version&lt;/code&gt; option (starting in beta 2):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Regular build&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3.13 &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;Python 3.13.0b2 (v3.13.0b2:3a83b172af, Jun  5 2024, 12:50:24) [Clang 15.0.0 (clang-1500.3.9.4)]
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Free-threaded build&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3.13t &lt;span class="nt"&gt;--version&lt;/span&gt; &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;span class="go"&gt;Python 3.13.0b2 experimental free-threading build (v3.13.0b2:3a83b172af, Jun  5 2024, 12:57:31) [Clang 15.0.0 (clang-1500.3.9.4)]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or the &lt;code&gt;Py_GIL_DISABLED&lt;/code&gt; macro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Regular build&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3.13 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"&lt;/span&gt;
&lt;span class="go"&gt;0
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Free-threaded build&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3.13t &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sysconfig; print(sysconfig.get_config_var('Py_GIL_DISABLED'))"&lt;/span&gt;
&lt;span class="go"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the GIL is disabled for free-threaded builds, and can be re-enabled by setting the &lt;code&gt;PYTHON_GIL&lt;/code&gt; environment variable to &lt;code&gt;1&lt;/code&gt; or running Python with &lt;code&gt;-X gil 1&lt;/code&gt;. You can check with &lt;code&gt;sys._is_gil_enabled()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Regular build: GIL is always enabled&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3.13 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys._is_gil_enabled())"&lt;/span&gt;
&lt;span class="go"&gt;True
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Free-threaded build: GIL is disabled by default&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3.13t &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys._is_gil_enabled())"&lt;/span&gt;
&lt;span class="go"&gt;False
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Free-threaded build: re-enable with -X&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;python3.13t &lt;span class="nt"&gt;-X&lt;/span&gt; &lt;span class="nv"&gt;gil&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys._is_gil_enabled())"&lt;/span&gt;
&lt;span class="go"&gt;True
&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Free-threaded build: re-enable with env var&lt;/span&gt;
&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;PYTHON_GIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 python3.13t &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import sys; print(sys._is_gil_enabled())"&lt;/span&gt;
&lt;span class="go"&gt;True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;References:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/using/configure.html#cmdoption-disable-gil" rel="noopener noreferrer"&gt;https://docs.python.org/3.13/using/configure.html#cmdoption-disable-gil&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/using/cmdline.html#envvar-PYTHON_GIL" rel="noopener noreferrer"&gt;https://docs.python.org/3.13/using/cmdline.html#envvar-PYTHON_GIL&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/using/cmdline.html#cmdoption-X" rel="noopener noreferrer"&gt;https://docs.python.org/3.13/using/cmdline.html#cmdoption-X&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  See also
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/hugovk/help-test-python-313-14j1"&gt;Help test Python 3.13!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3.13/howto/free-threading-extensions.html" rel="noopener noreferrer"&gt;C API Extension Support for Free Threading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://labs.quansight.org/blog/free-threaded-python-rollout" rel="noopener noreferrer"&gt;Free-threaded CPython is ready to experiment with!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://py-free-threading.github.io/" rel="noopener noreferrer"&gt;py-free-threading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Quansight-Labs/free-threaded-compatibility" rel="noopener noreferrer"&gt;Improving Ecosystem Compatibility with Free-Threaded Python&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;Header photo: "&lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/nlmhmd/8616809942/"&gt;George Mayerle test chart&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://www.flickr.com/people/nlmhmd/"&gt;US National Library of Medicine&lt;/a&gt;, with &lt;a rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>freethreaded</category>
      <category>testing</category>
      <category>cpython</category>
    </item>
    <item>
      <title>Sphinx docs: How to activate tabs for your OS</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Tue, 02 Apr 2024 18:41:30 +0000</pubDate>
      <link>https://dev.to/hugovk/sphinx-docs-how-to-activate-tabs-for-your-os-pd3</link>
      <guid>https://dev.to/hugovk/sphinx-docs-how-to-activate-tabs-for-your-os-pd3</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2024/sphinx-docs-how-to-activate-tabs-for-your-os/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;On the &lt;a href="https://devguide.python.org/" rel="noopener noreferrer"&gt;Python Developer's Guide&lt;/a&gt; and &lt;a href="https://pillow.readthedocs.io/en/latest/installation/basic-installation.html" rel="noopener noreferrer"&gt;Pillow documentation&lt;/a&gt; we have some pages with tabs for different operating systems:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknze2ghlj0qmjucz354t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknze2ghlj0qmjucz354t.png" 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's a macOS-specific note underneath." width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's possible to add some JavaScript so that the matching tab is activated based on the visitor's operating system.&lt;/p&gt;

&lt;p&gt;Here's how!&lt;/p&gt;

&lt;h2&gt;
  
  
  Sphinx Inline Tabs
&lt;/h2&gt;

&lt;p&gt;First add the &lt;a href="https://github.com/pradyunsg/sphinx-inline-tabs" rel="noopener noreferrer"&gt;Sphinx Inline Tabs&lt;/a&gt; extension to your docs' &lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/requirements.txt#L4" rel="noopener noreferrer"&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# requirements.txt
sphinx-inline-tabs&amp;gt;=2023.4.21
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  JavaScript
&lt;/h1&gt;

&lt;p&gt;Next, add &lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/_static/activate_tab.js" rel="noopener noreferrer"&gt;&lt;code&gt;activate_tab.js&lt;/code&gt;&lt;/a&gt; to your &lt;code&gt;_static/&lt;/code&gt; directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// activate_tab.js&lt;/span&gt;
&lt;span class="c1"&gt;// Based on https://stackoverflow.com/a/38241481/724176&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOS&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span 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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;userAgentData&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="nb"&gt;navigator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span 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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;macOS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Macintosh&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MacIntel&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MacPPC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Mac68K&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Win32&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Win64&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Windows&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WinCE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iPhone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iPad&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iPod&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&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;macosPlatforms&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;macOS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;iOS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Windows&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userAgent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Android&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unix&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;activateTab&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Find all label elements containing the specified tab name&lt;/span&gt;
  &lt;span class="kd"&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="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.tab-label&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nx"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&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;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="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabName&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Find the associated input element using the 'for' attribute&lt;/span&gt;
      &lt;span class="kd"&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="nf"&gt;getAttribute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;for&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&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="nf"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tabInputId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="c1"&gt;// Check if the input element exists before attempting to set the "checked" attribute&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;tabInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Activate the tab by setting its "checked" attribute to true&lt;/span&gt;
        &lt;span class="nx"&gt;tabInput&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;checked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  &lt;code&gt;conf.py&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;Add the &lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/conf.py#L15" rel="noopener noreferrer"&gt;extension&lt;/a&gt; and &lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/conf.py#L49-L51" rel="noopener noreferrer"&gt;JavaScript&lt;/a&gt; to your &lt;code&gt;conf.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# conf.py
&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sphinx_inline_tabs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;html_js_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;activate_tab.js&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  reStructuredText
&lt;/h2&gt;

&lt;p&gt;Almost there!&lt;/p&gt;

&lt;p&gt;Add tabs to the reStructuredText files.&lt;/p&gt;

&lt;p&gt;For &lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/testing/run-write-tests.rst#L58-L74" rel="noopener noreferrer"&gt;example&lt;/a&gt;, here we have three different commands; one for Unix, one for macOS, and one for Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.. tab:: Unix

    .. code-block:: shell

        ./python -m test -h

.. tab:: macOS

    .. code-block:: shell

        ./python.exe -m test -h

.. tab:: Windows

    .. code-block:: dosbatch

        .\python.bat -m test -h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, add the JavaScript &lt;a href="https://github.com/python/devguide/blob/d92fda3beb5506eb42d53fee1c52d31180e9d77a/testing/run-write-tests.rst#L8-L14" rel="noopener noreferrer"&gt;to the same reST page&lt;/a&gt; to activate the correct tab:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.. raw:: html

    &amp;lt;script&amp;gt;
    document.addEventListener('DOMContentLoaded', function() {
      activateTab(getOS());
    });
    &amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can see the results &lt;a href="https://devguide.python.org/testing/run-write-tests/#running" rel="noopener noreferrer"&gt;here&lt;/a&gt;. When the page loads, the browser finds the browser name, and activates the tabs with a matching name.&lt;/p&gt;

&lt;p&gt;If you have many sets of tabs on a page, the corresponding OS tab will be activated for all. And if you click on another OS tab, all the others with the same name are activated.&lt;/p&gt;

&lt;p&gt;Happy tabbing!&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Header photo: "&lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/nationalgalleries/3110282571/"&gt;The Great Pyramid and the Sphinx&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/nationalgalleries/"&gt;National Galleries of Scotland&lt;/a&gt;, with &lt;a rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>documentation</category>
      <category>sphinx</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Tech style guides</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Sat, 02 Mar 2024 15:25:54 +0000</pubDate>
      <link>https://dev.to/hugovk/tech-style-guides-mg1</link>
      <guid>https://dev.to/hugovk/tech-style-guides-mg1</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2023/tech-style-guides/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2023/tech-style-guides/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Here's some tech style guides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://developers.google.com/style" rel="noopener noreferrer"&gt;Google developer documentation style guide&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/style/word-list" rel="noopener noreferrer"&gt;Word list&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/style-guide/welcome/" rel="noopener noreferrer"&gt;Microsoft Writing Style Guide&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://stylepedia.net/" rel="noopener noreferrer"&gt;Red Hat Technical Writing Style Guide&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://redhat-documentation.github.io/supplementary-style-guide/" rel="noopener noreferrer"&gt;Red Hat supplementary style guide for product documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://bishopfox.com/cybersecurity-style-guide" rel="noopener noreferrer"&gt;Bishop Fox Cybersecurity Style Guide&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://images.bishopfox.com/prod-1437/Documents/Guides/Bishop-Fox-Cybersecurity-Style-Guide-V2.pdf" rel="noopener noreferrer"&gt;PDF&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://support.apple.com/en-gb/guide/applestyleguide/welcome/web" rel="noopener noreferrer"&gt;Apple Style Guide&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;&lt;a href="https://devguide.python.org/documentation/style-guide/" rel="noopener noreferrer"&gt;Python docs style guide&lt;/a&gt;&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://docs.ubuntu.com/styleguide/en/" rel="noopener noreferrer"&gt;Canonical Documentation Style Guide&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://canonical-documentation-with-sphinx-and-readthedocscom.readthedocs-hosted.com/style-guide/" rel="noopener noreferrer"&gt;Canonical reStructuredText style guide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;And some other style guides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.theguardian.com/guardian-observer-style-guide-a" rel="noopener noreferrer"&gt;Guardian and Observer style guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Wikipedia:Manual_of_Style" rel="noopener noreferrer"&gt;Wikipedia Manual of Style&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://styleguide.mailchimp.com/" rel="noopener noreferrer"&gt;Mailchimp Content Style Guide&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://styleguide.mailchimp.com/writing-for-accessibility/" rel="noopener noreferrer"&gt;Writing for Accessibility&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;a href="https://english.meta.stackexchange.com/a/2579/9001" rel="noopener noreferrer"&gt;and more&lt;/a&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;More on style guides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.writethedocs.org/guide/writing/style-guides/" rel="noopener noreferrer"&gt;Write the Docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;Header photo: "&lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/statelibraryofnsw/48910246623"&gt;Cootamundra Cycle Club, Cootamundra, New South Wales, c. 1900&lt;/a&gt;" by &lt;a rel="noopener noreferrer" href="https://www.flickr.com/photos/24029425@N06"&gt;Mitchell Library, State Library of New South Wales&lt;/a&gt;, with &lt;a rel="noopener noreferrer" href="https://www.flickr.com/commons/usage/"&gt;no known copyright restrictions&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

</description>
      <category>styleguide</category>
      <category>writing</category>
      <category>documentation</category>
      <category>reference</category>
    </item>
    <item>
      <title>TIL: exclude_also with coverage.py</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Mon, 06 Nov 2023 20:05:55 +0000</pubDate>
      <link>https://dev.to/hugovk/til-excludealso-with-coveragepy-2hkm</link>
      <guid>https://dev.to/hugovk/til-excludealso-with-coveragepy-2hkm</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2023/til-excludealso-with-coveragepy/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2023/til-excludealso-with-coveragepy/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Sometimes you have code you want to exclude from the test coverage report, because it doesn't really make sense to test it.&lt;/p&gt;

&lt;p&gt;For example, maybe you want to exclude:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&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;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[report]&lt;/span&gt;
&lt;span class="c"&gt;# Regexes for lines to exclude from consideration
&lt;/span&gt;&lt;span class="py"&gt;exclude_lines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c"&gt;# Have to re-enable the standard pragma:
&lt;/span&gt;    &lt;span class="err"&gt;pragma:&lt;/span&gt; &lt;span class="err"&gt;no&lt;/span&gt; &lt;span class="err"&gt;cover&lt;/span&gt;

    &lt;span class="c"&gt;# Don't complain if non-runnable code isn't run:
&lt;/span&gt;    &lt;span class="err"&gt;if&lt;/span&gt; &lt;span class="py"&gt;__name__&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;= .__main__.:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But since &lt;a href="https://coverage.readthedocs.io/en/7.3.2/changes.html#version-7-2-0-2023-02-22" rel="noopener noreferrer"&gt;coverage.py 7.2.0 (2023-02-22)&lt;/a&gt; you can use &lt;code&gt;exclude_also&lt;/code&gt; instead and skip that pragma:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[report]&lt;/span&gt;
&lt;span class="c"&gt;# Regexes for lines to exclude from consideration
&lt;/span&gt;&lt;span class="py"&gt;exclude_also&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="c"&gt;# Don't complain if non-runnable code isn't run:
&lt;/span&gt;    &lt;span class="err"&gt;if&lt;/span&gt; &lt;span class="py"&gt;__name__&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;= .__main__.:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt; [report]
 # Regexes for lines to exclude from consideration
&lt;span class="gd"&gt;-exclude_lines =
-    # Have to re-enable the standard pragma:
-    pragma: no cover
-
&lt;/span&gt;&lt;span class="gi"&gt;+exclude_also =
&lt;/span&gt;     # Don't complain if non-runnable code isn't run:
     if __name__ == .__main__.:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Thanks
&lt;/h2&gt;

&lt;p&gt;To &lt;a href="https://mastodon.social/@brianokken@fosstodon.org/111360201593749157" rel="noopener noreferrer"&gt;Brian Okken&lt;/a&gt; for the tip.&lt;/p&gt;

&lt;p&gt;To &lt;a href="https://nedbatchelder.com/" rel="noopener noreferrer"&gt;Ned Batchelder&lt;/a&gt; for maintaining &lt;a href="https://coverage.readthedocs.io" rel="noopener noreferrer"&gt;Coverage.py&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To the Library of Congress and Flickr Commons for the photo of a &lt;a href="https://www.flickr.com/photos/library_of_congress/52303625278/" rel="noopener noreferrer"&gt;covered wagon&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>todayilearned</category>
      <category>coverage</category>
      <category>coveragepy</category>
    </item>
    <item>
      <title>Why are there still so many downloads for EOL Python 3.7?</title>
      <dc:creator>Hugo van Kemenade</dc:creator>
      <pubDate>Tue, 22 Aug 2023 19:25:29 +0000</pubDate>
      <link>https://dev.to/hugovk/why-are-there-still-so-many-downloads-for-eol-python-37-30cp</link>
      <guid>https://dev.to/hugovk/why-are-there-still-so-many-downloads-for-eol-python-37-30cp</guid>
      <description>&lt;h1&gt;
  
  
  Moved to &lt;a href="https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/" rel="noopener noreferrer"&gt;https://hugovk.dev/blog/2023/why-are-there-still-so-many-downloads-for-eol-python-37/&lt;/a&gt;
&lt;/h1&gt;

&lt;p&gt;Python 3.7 was first released on 2018-06-27 and recently reached end-of-life on 2023-06-27 (&lt;a href="https://peps.python.org/pep-0537/" rel="noopener noreferrer"&gt;PEP 537&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This means it is no longer receiving security updates and you should upgrade to a newer version (at least 3.8, but preferably 3.11):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzjga7b8p4eo1t4ja9qd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnzjga7b8p4eo1t4ja9qd.png" alt="Chart showing when different Python versions reached end-of-life" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;Source: &lt;a href="https://devguide.python.org/versions/" rel="noopener noreferrer"&gt;Python Developer's Guide&lt;/a&gt;&lt;/small&gt;&lt;/center&gt;


&lt;p&gt;However, if you look at download numbers from PyPI, 3.7 still accounts for a large share. 3.7 accounted for 25% of all downloads from PyPI in July 2023, compared with 27% for 3.8:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkoa8vurrwrj71jde2f3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkoa8vurrwrj71jde2f3.png" alt="Python download share over time" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;center&gt;&lt;small&gt;Source: &lt;a href="https://hugovk.github.io/pypi-tools/charts.html" rel="noopener noreferrer"&gt;pypi-tools&lt;/a&gt;&lt;/small&gt;&lt;/center&gt;

&lt;p&gt;But why does such an old Python version have so many downloads?&lt;/p&gt;

&lt;h2&gt;
  
  
  All downloads
&lt;/h2&gt;

&lt;p&gt;Let's dig into the numbers using a handy tool called &lt;a href="https://github.com/ofek/pypinfo" rel="noopener noreferrer"&gt;pypinfo&lt;/a&gt;, which helps us analyse the &lt;a href="https://packaging.python.org/en/latest/guides/analyzing-pypi-package-downloads/" rel="noopener noreferrer"&gt;PyPI data from Google BigQuery&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This command fetches all of yesterday's downloads per Python version:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pypinfo --days 1 --percent --markdown "" pyversion&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Python version&lt;/th&gt;
&lt;th&gt;percent&lt;/th&gt;
&lt;th&gt;download count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3.8&lt;/td&gt;
&lt;td&gt;25.00%&lt;/td&gt;
&lt;td&gt;189,678,872&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.7&lt;/td&gt;
&lt;td&gt;23.35%&lt;/td&gt;
&lt;td&gt;177,150,010&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.9&lt;/td&gt;
&lt;td&gt;20.25%&lt;/td&gt;
&lt;td&gt;153,663,903&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.10&lt;/td&gt;
&lt;td&gt;15.52%&lt;/td&gt;
&lt;td&gt;117,751,108&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.11&lt;/td&gt;
&lt;td&gt;6.90%&lt;/td&gt;
&lt;td&gt;52,381,884&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;td&gt;6.29%&lt;/td&gt;
&lt;td&gt;47,749,879&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.7&lt;/td&gt;
&lt;td&gt;2.32%&lt;/td&gt;
&lt;td&gt;17,602,650&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.5&lt;/td&gt;
&lt;td&gt;0.23%&lt;/td&gt;
&lt;td&gt;1,778,388&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.4&lt;/td&gt;
&lt;td&gt;0.10%&lt;/td&gt;
&lt;td&gt;770,135&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.12&lt;/td&gt;
&lt;td&gt;0.03%&lt;/td&gt;
&lt;td&gt;224,223&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.13&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;td&gt;3,920&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.3&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;td&gt;1,165&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2.8&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3.2&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;0.00%&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;758,756,232&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  All downloads by OS
&lt;/h2&gt;

&lt;p&gt;But what happens if we check which distros are responsible for those downloads?&lt;/p&gt;

&lt;p&gt;This command gives us the top 20:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pypinfo --days 1 --limit 20 --percent --markdown "" system distro pyversion&lt;/code&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;system name&lt;/th&gt;
&lt;th&gt;distro name&lt;/th&gt;
&lt;th&gt;Python version&lt;/th&gt;
&lt;th&gt;percent&lt;/th&gt;
&lt;th&gt;download count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;3.8&lt;/td&gt;
&lt;td&gt;18.87%&lt;/td&gt;
&lt;td&gt;128,991,062&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Amazon Linux&lt;/td&gt;
&lt;td&gt;3.7&lt;/td&gt;
&lt;td&gt;14.44%&lt;/td&gt;
&lt;td&gt;98,738,952&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;3.9&lt;/td&gt;
&lt;td&gt;12.14%&lt;/td&gt;
&lt;td&gt;83,019,828&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;3.10&lt;/td&gt;
&lt;td&gt;9.66%&lt;/td&gt;
&lt;td&gt;66,019,309&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;3.7&lt;/td&gt;
&lt;td&gt;5.89%&lt;/td&gt;
&lt;td&gt;40,257,060&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Debian GNU/Linux&lt;/td&gt;
&lt;td&gt;3.8&lt;/td&gt;
&lt;td&gt;5.70%&lt;/td&gt;
&lt;td&gt;38,958,367&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Debian GNU/Linux&lt;/td&gt;
&lt;td&gt;3.9&lt;/td&gt;
&lt;td&gt;5.63%&lt;/td&gt;
&lt;td&gt;38,482,436&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Debian GNU/Linux&lt;/td&gt;
&lt;td&gt;3.7&lt;/td&gt;
&lt;td&gt;4.25%&lt;/td&gt;
&lt;td&gt;29,035,532&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Debian GNU/Linux&lt;/td&gt;
&lt;td&gt;3.10&lt;/td&gt;
&lt;td&gt;4.15%&lt;/td&gt;
&lt;td&gt;28,348,346&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Debian GNU/Linux&lt;/td&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;td&gt;3.14%&lt;/td&gt;
&lt;td&gt;21,441,883&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;3.11&lt;/td&gt;
&lt;td&gt;3.01%&lt;/td&gt;
&lt;td&gt;20,570,619&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Debian GNU/Linux&lt;/td&gt;
&lt;td&gt;3.11&lt;/td&gt;
&lt;td&gt;2.72%&lt;/td&gt;
&lt;td&gt;18,588,584&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Amazon Linux&lt;/td&gt;
&lt;td&gt;3.9&lt;/td&gt;
&lt;td&gt;2.43%&lt;/td&gt;
&lt;td&gt;16,593,595&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;CentOS Linux&lt;/td&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;td&gt;1.70%&lt;/td&gt;
&lt;td&gt;11,605,142&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Amazon Linux&lt;/td&gt;
&lt;td&gt;3.8&lt;/td&gt;
&lt;td&gt;1.47%&lt;/td&gt;
&lt;td&gt;10,035,087&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Amazon Linux&lt;/td&gt;
&lt;td&gt;3.10&lt;/td&gt;
&lt;td&gt;1.46%&lt;/td&gt;
&lt;td&gt;9,969,514&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;2.7&lt;/td&gt;
&lt;td&gt;1.02%&lt;/td&gt;
&lt;td&gt;6,960,697&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Amazon Linux AMI&lt;/td&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;td&gt;0.79%&lt;/td&gt;
&lt;td&gt;5,390,823&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;Ubuntu&lt;/td&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;td&gt;0.79%&lt;/td&gt;
&lt;td&gt;5,388,370&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;3.10&lt;/td&gt;
&lt;td&gt;0.76%&lt;/td&gt;
&lt;td&gt;5,227,519&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Total&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;683,622,725&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We can see Ubuntu with 3.8 is responsible for the largest share of 17%. (That's fine, 3.8 is supported until 2024-10-14.)&lt;/p&gt;

&lt;p&gt;The next is Amazon Linux with 3.7, responsible for a whopping 15% of all downloads!&lt;/p&gt;

&lt;p&gt;The others responsible for 3.7 have a much lower share: Ubuntu (6%) and Debian (4%).&lt;/p&gt;

&lt;p&gt;Tip: replace &lt;code&gt;""&lt;/code&gt; in the commands above with a package name to get data for just that package, for example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;pypinfo --days 1 --limit 20 --percent --markdown requests system distro pyversion&lt;/code&gt;&lt;/p&gt;




&lt;p&gt;&lt;small&gt;Header photo: Space shuttle Discovery landing at Edwards Air Force Base, California, 9th December, 1992 (&lt;a href="https://www.flickr.com/photos/nasacommons/30498961044" rel="noopener noreferrer"&gt;source: NASA on The Commons&lt;/a&gt;&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>eol</category>
      <category>pypi</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
